<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A; JavaScript</title><link>https://academy.hsoub.com/programming/javascript/page/5/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A; JavaScript</description><language>ar</language><item><title>&#x645;&#x637;&#x627;&#x628;&#x642;&#x629; &#x639;&#x62F;&#x629; &#x645;&#x62C;&#x645;&#x648;&#x639;&#x627;&#x62A; &#x646;&#x645;&#x637;&#x64A;&#x629; &#x641;&#x64A; &#x627;&#x644;&#x62A;&#x639;&#x627;&#x628;&#x64A;&#x631; &#x627;&#x644;&#x646;&#x645;&#x637;&#x64A;&#x629; RegEx</title><link>https://academy.hsoub.com/programming/javascript/%D9%85%D8%B7%D8%A7%D8%A8%D9%82%D8%A9-%D8%B9%D8%AF%D8%A9-%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A7%D8%AA-%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-regex-r1420/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61d4a247c7142_------.png.6c990c5b55123e4f1d56927022a7778e.png" /></p>

<p>
	المجموعة الملتقطة capturing group هي الجزء الذي يضم محارف بين قوسين <code>(...)</code> في أي تعبيير نمطي RegEx، ولها تأثيران اثنان:
</p>

<ol>
<li>
		تسمح بالحصول على جزء من التطابق مثل عنصر مستقل ضمن مصفوفة النتائج.
	</li>
	<li>
		يُطبق المحدد الكمي quantifier على مجموعة الالتقاط كلها إذا وضع بعد القوسين مباشرةً.
	</li>
</ol>
<h2>
	أمثلة عن مطابقة عدة مجموعات
</h2>

<p>
	لنتعرف كيفية عمل الأقواس من خلال الأمثلة.
</p>

<h3>
	مثال gogogo
</h3>

<p>
	يفيد النمط <code>+go</code> دون أقواس في إيجاد المحرف <code>g</code> يليه المحرف <code>o</code> مكررًا مرةً أو أكثر، مثل <code>goooo</code> أو <code>goooooo</code>، وإذا وضعنا محارف النمط السابق بين قوسين <code>+(go)</code>، فسيعني ذلك <code>go</code> أو <code>gogo</code> أو <code>gogogo</code> وهكذا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_9" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">'Gogogo now!'</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/(go)+/</span><span class="pln">ig</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// "Gogogo"</span></pre>

<h3>
	مثال نطاق موقع ويب
</h3>

<p>
	نحتاج إلى تعبير نمطي للبحث عن نطاق موقع ويب، مثل النطاقات التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_11" style="">
<span class="pln">mail</span><span class="pun">.</span><span class="pln">com
users</span><span class="pun">.</span><span class="pln">mail</span><span class="pun">.</span><span class="pln">com
smith</span><span class="pun">.</span><span class="pln">users</span><span class="pun">.</span><span class="pln">mail</span><span class="pun">.</span><span class="pln">com</span></pre>

<p>
	يتألف النطاق من كلمات متتالية تفصل بينها نقاط، ويقابل ذلك التعبير النمطي <code>+w+\.)+\w\)</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_14" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/(\w+\.)+\w+/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"site.com my.site.com"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// site.com,my.site.com</span></pre>

<p>
	سيعمل النمط السابق، لكنه سيواجه مشكلةً عندما تحتوي الكلمات على شرطة قصيرة <code>-</code>، مثل <code>my-site.com</code>، فلا يعتبر هذا المحرف من محارف الصنف <code>w\</code>، وسنحل هذه المشكلة باستبدال التعبير <code>w\</code> بالمجموعة <code>[-w\]</code>، في كل كلمة عدا الأخيرة، وسيصبح النمط بالشكل <code>+w-]+\.)+\w\])</code>.
</p>

<h3>
	مثال البريد الإلكتروني
</h3>

<p>
	يمكن توسيع المثال السابق لإنشاء تعبير نمطي لبريد إلكتروني اعتمادًا على النمط السابق، وما دام للبريد الإلكتروني الشكل <code>name@domain</code>، فيمكن أن تكون أي كلمة هي الاسم، حيث يُسمح ضمنها بالشرطة القصيرة أو النقاط، وبلغة التعبير النمطي ستكون <code>[.-w\]</code>، وسيكون التعبير النمطي للبريد الإلكتروني <code>/+[-w.-]+@([\w-]+\.)+[\w\]/</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_16" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/[-.\w]+@([\w-]+\.)+[\w-]+/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="str">"my@mail.com @ his@site.com.uk"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">));</span><span class="pln"> </span><span class="com">// my@mail.com, his@site.com.uk</span></pre>

<p>
	ليس هذا التعبير مثاليًا، لكنه سيعمل في معظم الأحيان، وسيساعدك في التخلص من الأخطاء الكتابية، وتأكد أن الطريقة الحقيقية الوحيدة للتحقق من بريد هو استلامه للرسالة التي أرسلتها!
</p>

<h2>
	المحتوى الموجود بين قوسين عند البحث عن تطابق
</h2>

<p>
	تُرقّم الأقواس من اليسار إلى اليمين، وسيتذكر المحرك المحتوى الذي يتطابق مع كل قوس، ويسمح بالحصول على هذه التطابقات ضمن النتيجة، ويبحث التابع <code>(str.match(regexp</code> عن التطابق الأول، ويعيد النتيجة في مصفوفة عندما لا تُستخدم الراية <code>g</code> ضمن التعبير <code>regexp</code>، حيث ستجد ضمن المصفوفة:
</p>

<ol>
<li>
		التطابق بشكله الكامل في الموقع <code>0</code>.
	</li>
	<li>
		محتوى القوس الأول في الموقع <code>1</code>.
	</li>
	<li>
		محتوى القوس الثاني في الموقع <code>2</code>.
	</li>
	<li>
		وهكذا…
	</li>
</ol>
<p style="text-align: center;">
	<img alt="regexp-nested-groups.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87993" data-unique="2zs4npf4u" src="https://academy.hsoub.com/uploads/monthly_2022_01/regexp-nested-groups.png.a1a1c23113afb6d7b8f8fe097e6854ec.png"></p>

<p>
	فلو أردنا مثلًا البحث عن وسوم HTML من النمط <code>&lt;?*.&gt;</code>، ثم معالجة النتائج، فمن المناسب أن نحصل على محتوى كل وسم ضمن متغير خاص به، وعندما نغلّف المحتوى الداخلي للوسم ضمن قوسين، بالشكل التالي <code>&lt;(?*.)&gt;</code>، فسنحصل على الوسم كاملًا، وليكن <code>&lt;h1&gt;</code>، وعلى محتوى هذا الوسم (أي النص <code>h1</code>) ضمن النتيجة:
</p>

<p style="text-align: center;">
	<img alt="regexp-nested-groups.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87993" data-unique="ni5dn35a7" src="https://academy.hsoub.com/uploads/monthly_2022_01/regexp-nested-groups.png.a1a1c23113afb6d7b8f8fe097e6854ec.png"></p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_18" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;h1&gt;Hello, world!&lt;/h1&gt;'</span><span class="pun">;</span><span class="pln">

let tag </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/&lt;(.*?)&gt;/</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> tag</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;h1&gt;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> tag</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// h1</span></pre>

<h2>
	المجموعات المتداخلة
</h2>

<p>
	يمكن أن تتداخل الأقواس، وعندها ستُرقَّم أيضًا من اليسار إلى اليمين، فعندما نبحث عن وسم ضمن الوسم <code>&lt;span class="my"&gt;</code> مثلًا، فلربما نريد الحصول على:
</p>

<ol>
<li>
		محتوى الوسم كاملًا <code>"span class="my</code>.
	</li>
	<li>
		اسم الوسم: <code>span</code>.
	</li>
	<li>
		سمات الوسم: <code>"class="my</code>.
	</li>
</ol>
<p>
	لنضف الأقواس إلى النمط <code>&lt;(([a-z]+)\s*([^&gt;]*))&gt;</code>، لاحظ كيف تُرقَّم الأقواس من اليسار إلى اليمين:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="87993" href="https://academy.hsoub.com/uploads/monthly_2022_01/regexp-nested-groups.png.a1a1c23113afb6d7b8f8fe097e6854ec.png" rel=""><img alt="regexp-nested-groups.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87993" data-unique="jg0g5l29e" src="https://academy.hsoub.com/uploads/monthly_2022_01/regexp-nested-groups.png.a1a1c23113afb6d7b8f8fe097e6854ec.png"></a>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_20" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;span class="my"&gt;'</span><span class="pun">;</span><span class="pln">

let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;(([a-z]+)\s*([^&gt;]*))&gt;/</span><span class="pun">;</span><span class="pln">

let result </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">);</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">result</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]);</span><span class="pln"> </span><span class="com">// &lt;span class="my"&gt;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">result</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]);</span><span class="pln"> </span><span class="com">// span class="my"</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">result</span><span class="pun">[</span><span class="lit">2</span><span class="pun">]);</span><span class="pln"> </span><span class="com">// span</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">result</span><span class="pun">[</span><span class="lit">3</span><span class="pun">]);</span><span class="pln"> </span><span class="com">// class="my"</span></pre>

<p>
	سنجد دائمًا التطابق الكامل في الموقع صفر من المصفوفة، ثم المجموعات مرقمةً من اليسار إلى اليمين بواسطة القوس المفتوح، حيث تُعاد المجموعة الأولى في الموقع الأول <code>[result[1</code>، تليها الثانية الناتجة عن القوس المفتوح الثاني <code>(+[a-z])</code> ضمن <code>[result[2</code>، ثم نتيجة التطابق مع النمط <code>(‎[^&gt;]*‎)</code> ضمن <code>[result[3</code>، وستكون نتيجة كل مجموعة بصيغة نص.
</p>

<h2>
	المجموعات الاختيارية
</h2>

<p>
	حتى لو كانت المجموعة اختياريةً وغير موجودة ضمن التطابق، كأن يكون لها المحدد الكمي <code>?(...)</code>، فسيبقى مكانها محجوزًا ضمن المصفوفة، وقيمته هي <code>undefined</code>، فلو تأملنا مثلًا التعبير <code>?(a(z)?(c</code>، فسنجد أنه يبحث عن <code>"a"</code> متبوعًا -اختياريًا- بالحرف <code>"z"</code>، ومتبوعًا -اختياريًا أيضًا- بالحرف <code>"c"</code>، لأن المحدد الكمي <code>?</code> يعني محرفًا أو لا شيء، فلو طبّقنا التعبير السابق على نص مكون من الحرف <code>a</code> فقط، فستكون النتيجة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_22" style="">
<span class="pln">let match </span><span class="pun">=</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/a(z)?(c)?/</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> match</span><span class="pun">.</span><span class="pln">length </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 3</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> match</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// a (whole match)</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> match</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// undefined</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> match</span><span class="pun">[</span><span class="lit">2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// undefined</span></pre>

<p>
	سيكون طول المصفوفة 3 علمًا أن كل المجموعات فارغة!
</p>

<p>
	لكن لو كان النص هو <code>ac</code>:
</p>

<pre class="ipsCode">
let match = 'ac'.match(/a(z)?(c)?/)

alert( match.length ); // 3
alert( match[0] ); // ac (whole match)
alert( match[1] ); // undefined, because there's nothing for (z)?
alert( match[2] ); // c
</pre>

<p>
	سيبقى طول المصفوفة 3، لكنك لن تجد تطابقًا يقابل المجموعة <code>(z)?</code>، وستكون النتيجة <code>["ac", undefined, "c"]</code>.
</p>

<h2>
	البحث عن كل التطابقات ضمن المجموعات: التابع matchAll
</h2>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		**التابع <code>matchAll</code> هو تابع جديد، وقد نحتاج إلى موائمة برمجية polyfill لتعويض نقص دعمه في بعض المتصفحات القديمة التي لا تدعمه، لكن يمكنك استخدام بعض <a href="https://github.com/ljharb/String.prototype.matchAll" rel="external nofollow">الموائمات الخاصة</a>.
	</p>
</blockquote>

<p>
	لن يعيد التابع <code>match</code> محتوى المجموعات إذا استخدم للبحث بوجود الراية <code>g</code>، والتي تعني إيجاد كل التطابقات، وسنحاول في المثال التالي إيجاد كل الوسوم في النص:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_24" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;h1&gt; &lt;h2&gt;'</span><span class="pun">;</span><span class="pln">

let tags </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/&lt;(.*?)&gt;/</span><span class="pln">g</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> tags </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;h1&gt;,&lt;h2&gt;</span></pre>

<p>
	لاحظ أن النتيجة هي مصفوفة تحتوي على التطابقات كاملةً لكن دون تفاصيل، أي دون محتوى كل تطابق، لكننا نحتاج عمليًا إلى ذلك المحتوى، وسيساعدنا البحث باستخدام التابع <code>(str.matchAll(regexp</code> على استخلاص ذلك المحتوى، فقد أضيف هذا التابع إلى <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a> بعد فترة طويلة من إضافة <code>match</code> مثل نسخة جديدة ومحسنة منه.
</p>

<p>
	يشابه <code>matchAll</code> التابع <code>match</code>، مع وجود ثلاثة اختلافات، وهي:
</p>

<ol>
<li>
		لا يعيد مصفوفةً، بل كائنًا قابلًا للتكرار iterable object.
	</li>
	<li>
		يعيد كل تطابق مثل مصفوفة تحتوي مجموعات عند استخدام الراية <code>g</code>.
	</li>
	<li>
		عندما لا يجد تطابقات فلن يعيد <code>null</code>، بل كائنًا فارغًا قابلًا للتكرار.
	</li>
</ol>
<p>
	إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_26" style="">
<span class="pln">let results </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;h1&gt; &lt;h2&gt;'</span><span class="pun">.</span><span class="pln">matchAll</span><span class="pun">(</span><span class="str">/&lt;(.*?)&gt;/</span><span class="pln">gi</span><span class="pun">);</span><span class="pln">

</span><span class="com">// النتائج ليست مصفوفة بل كائن قابل للتعداد</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">results</span><span class="pun">);</span><span class="pln"> </span><span class="com">// [object RegExp String Iterator]</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">results</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]);</span><span class="pln"> </span><span class="com">// undefined (*)</span><span class="pln">

results </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Array</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">results</span><span class="pun">);</span><span class="pln"> </span><span class="com">// تحويل النتيجة إلى مصفوفة عادية</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">results</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]);</span><span class="pln"> </span><span class="com">// &lt;h1&gt;,h1 (1st tag)</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">results</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]);</span><span class="pln"> </span><span class="com">// &lt;h2&gt;,h2 (2nd tag)</span></pre>

<p>
	إنّ الاختلاف الأول مهم جدًا كما يوضّحه السطر "(*)"، فلا يمكن الحصول على التطابق في الموقع <code>[results[0</code>، لأن الكائن لا يمثل مصفوفةً زائفةً pseudoarray، ويمكننا تحويلها إلى مصفوفة حقيقية باستخدام <code>Array.from</code>، وستجد العديد من التفاصيل عن المصفوفات الزائفة والكائنات القابلة للتكرار في المقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%83%D8%B1%D9%91%D9%8E%D8%B1%D8%A9-iterables-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r820/" rel="">Iterables</a>.
</p>

<p>
	لا حاجة لتحويل المصفوفة باستخدام <code>Array.from</code> إذا كنا سنشكل حلقةً من النتائج:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_30" style="">
<span class="pln">let results </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;h1&gt; &lt;h2&gt;'</span><span class="pun">.</span><span class="pln">matchAll</span><span class="pun">(</span><span class="str">/&lt;(.*?)&gt;/</span><span class="pln">gi</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">for</span><span class="pun">(</span><span class="pln">let result of results</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="pln">result</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// first alert: &lt;h1&gt;,h1</span><span class="pln">
  </span><span class="com">// second: &lt;h2&gt;,h2</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أو عند استخدام <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A5%D8%B3%D9%86%D8%A7%D8%AF-%D8%A8%D8%A7%D9%84%D8%AA%D9%81%D9%83%D9%8A%D9%83-destructuring-assignment-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r824/" rel="">التفكيك destructuring</a>:
</p>

<pre class="ipsCode">
let [tag1, tag2] = '&lt;h1&gt; &lt;h2&gt;'.matchAll(/&lt;(.*?)&gt;/gi);
</pre>

<p>
	يشابه تنسيق كل تطابق يعيده التابع <code>matchAll</code> التنسيق الذي يعيده <code>match</code> دون الراية <code>g</code>، وهذا التنسيق هو مصفوفة مع خصائص إضافية <code>index</code> التي تطابق الفهرس في النص، و<code>input</code> الذي يعني النص الأصلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_32" style="">
<span class="pln">let results </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;h1&gt; &lt;h2&gt;'</span><span class="pun">.</span><span class="pln">matchAll</span><span class="pun">(</span><span class="str">/&lt;(.*?)&gt;/</span><span class="pln">gi</span><span class="pun">);</span><span class="pln">

let </span><span class="pun">[</span><span class="pln">tag1</span><span class="pun">,</span><span class="pln"> tag2</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> results</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> tag1</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;h1&gt;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> tag1</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// h1</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> tag1</span><span class="pun">.</span><span class="pln">index </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 0</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> tag1</span><span class="pun">.</span><span class="pln">input </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;h1&gt; &lt;h2&gt;</span></pre>

<p>
	انتبه، لماذا ستكون نتيجة التابع كائنًا قابلًا للتكرار وليس مصفوفةً؟
</p>

<p>
	السبب بسيط وهو التحسين، فلن يُنفِّذ الاستدعاء عملية البحث، بل سيعيد كائنًا قابلًا للتكرار لا يحتوي على النتيجة مبدئيًا، ويُنفَّذ البحث في كل مرة نكرره (ضمن حلقة مثلًا)، وبالتالي سيجد العدد المطلوب من النتائج تمامًا، فإذا كان من المحتمل مثلًا وجود 100 تطابق، لكننا وجدنا في حلقة <code>for..of</code> خمسةً فقط، وقررنا أن هذا كاف وأوقفنا الحلقة، فلن يستهلك المحرك وقتًا إضافيًا في إيجاد التطابقات 95 الباقية.
</p>

<h2>
	المجموعات المسماة
</h2>

<p>
	يصعب تذكر المجموعات بأرقامها، على الرغم من بساطته في الأنماط البسيطة، لكن عند البحث عن أنماط أكثر تعقيدًا فلن يكون ترقيم الأقواس أمرًا مناسبًا، وسنجد أن خيار تسمية الأقواس هو الأفضل، ونسمي الأقواس بوضع الاسم بالشكل التالي <code>&lt;name&gt;?</code> مباشرةً بعد القوس، ولنبحث مثلًا عن تاريخ وفق التنسيق "يوم-شهر-سنة":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_34" style="">
<span class="pln">let dateRegexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/(?&lt;year&gt;[0-9]{4})-(?&lt;month&gt;[0-9]{2})-(?&lt;day&gt;[0-9]{2})/</span><span class="pun">;</span><span class="pln">
let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"2019-04-30"</span><span class="pun">;</span><span class="pln">

let groups </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">dateRegexp</span><span class="pun">).</span><span class="pln">groups</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">groups</span><span class="pun">.</span><span class="pln">year</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 2019</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">groups</span><span class="pun">.</span><span class="pln">month</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 04</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">groups</span><span class="pun">.</span><span class="pln">day</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 30</span></pre>

<p>
	حيث سنجد المجموعات عبر الخاصية <code>groups.</code> لكل تطابق، ويمكن إيجاد جميع التواريخ باستخدام الراية <code>g</code>، كما ينبغي استخدام التابع <code>matchAll</code> للحصول على التطابق كاملًا بالإضافة إلى المجموعات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_36" style="">
<span class="pln">let dateRegexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/(?&lt;year&gt;[0-9]{4})-(?&lt;month&gt;[0-9]{2})-(?&lt;day&gt;[0-9]{2})/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"2019-10-30 2020-01-01"</span><span class="pun">;</span><span class="pln">

let results </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">matchAll</span><span class="pun">(</span><span class="pln">dateRegexp</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">for</span><span class="pun">(</span><span class="pln">let result of results</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let </span><span class="pun">{</span><span class="pln">year</span><span class="pun">,</span><span class="pln"> month</span><span class="pun">,</span><span class="pln"> day</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">groups</span><span class="pun">;</span><span class="pln">

  alert</span><span class="pun">(`</span><span class="pln">$</span><span class="pun">{</span><span class="pln">day</span><span class="pun">}.</span><span class="pln">$</span><span class="pun">{</span><span class="pln">month</span><span class="pun">}.</span><span class="pln">$</span><span class="pun">{</span><span class="pln">year</span><span class="pun">}`);</span><span class="pln">
  </span><span class="com">// first alert: 30.10.2019</span><span class="pln">
  </span><span class="com">// second: 01.01.2020</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	مطابقة مجموعات ثم تنفيذ عملية استبدال
</h2>

<p>
	يسمح التابع <code>(str.replace(regexp, replacement</code> الذي يستبدل محتوى الأقواس ضمن النص <code>replacement</code>، بكل التطابقات <code>regexp</code> التي يجدها في النص <code>str</code>، وينفذ ذلك باستخدام الرمز <code>n$</code>، حيث <code>n</code> هو رقم المجموعة، وإليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_38" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"John Bull"</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/(\w+) (\w+)/</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">,</span><span class="pln"> </span><span class="str">'$2, $1'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// Bull, John</span></pre>

<p>
	ستتغير العملية باستخدام <code>&lt;name&gt;$</code> في الأقواس المسماة، ولتغيير تنسيق التاريخ مثلًا من "يوم -شهر-سنة" إلى "سنة.شهر.يوم":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_40" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/(?&lt;year&gt;[0-9]{4})-(?&lt;month&gt;[0-9]{2})-(?&lt;day&gt;[0-9]{2})/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"2019-10-30, 2020-01-01"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">,</span><span class="pln"> </span><span class="str">'$&lt;day&gt;.$&lt;month&gt;.$&lt;year&gt;'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">
</span><span class="com">// 30.10.2019, 01.01.2020</span></pre>

<h2>
	استثناء التقاط المجموعات باستخدام :?
</h2>

<p>
	نحتاج أحيانًا إلى الأقواس لتطبيق المحددات الكمية بطريقة صحيحة، لكننا لا نحتاج إلى محتواها ضمن النتائج، لذلك يمكن استثناء المجموعات باستخدام <code>:?</code> في البداية، فإذا أردنا مثلًا إيجاد النمط <code>+(go)</code>، لكننا لا نحتاج إلى محتوى الأقواس (<code>go</code>) في عنصر مصفوفة مستقل، فيمكننا صياغة النمط بالشكل `+(go?:)، وإليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_42" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Gogogo John!"</span><span class="pun">;</span><span class="pln">

</span><span class="com">// ?: exludes 'go' from capturing</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/(?:go)+ (\w+)/</span><span class="pln">i</span><span class="pun">;</span><span class="pln">

let result </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> result</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// Gogogo John (تطابق كامل)</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> result</span><span class="pun">[</span><span class="lit">1</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// John</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">length </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 2 (لا مزيد من العناصر ضمن المصفوفة)</span></pre>

<h2>
	الخلاصة
</h2>

<ol>
<li>
		تُجمِّع الأقواس أجزاءً من التعبير النمطي ليُطبَّق المحدد الكمي عليها مثل زمرة واحدة.
	</li>
	<li>
		تُرقَّم أقواس المجموعات من اليسار إلى اليمين، كما يمُكن أن تُسمّى اختياريًا باستخدام النمط <code>(...&lt;name&gt;?)</code>.
	</li>
	<li>
		يمكن الحصول على المحتوى الموجود داخل الأقواس -الذي يحقق التطابق- بصورة مستقلة ضمن النتيجة، حيث:
	</li>
</ol>
<ul>
<li>
		يعيد التابع <code>str.match</code> المجموعة الملتقطة عند استخدام الراية فقط.
	</li>
	<li>
		يعيد التابع <code>str.matchAll</code> المجموعات الملتقطة دومًا.
	</li>
</ul>
<ol>
<li>
		إذا لم تُسمَّ الأقواس فسنحصل على محتوياتها ضمن المصفوفة وفقًا لتسلسل ترقيمها، كما يمكن الحصول على محتويات الأقواس المسماة من خلال الخاصية <code>groups.</code>.
	</li>
	<li>
		يمكن استخدام محتويات الأقواس في النص البديل للتابع، إما عبر أرقامها من خلال <code>n$</code>، أو أسمائها من خلال <code>&lt;name&gt;$</code>.
	</li>
	<li>
		يمكن استثناء مجموعة من الترقيم باستخدام النمط <code>:?</code> قبلها، وذلك عندما نريد تطبيق مُحصٍ quantifier على كامل المجموعة، لكننا لا نريد أن تظهر محتويات المجموعة -الموجودة بين قوسين- في عنصر مستقل ضمن مصفوفة النتيجة، كما لا يمكن الإشارة إلى هذه الأقواس عند استخدام تابع الاستبدال.
	</li>
</ol>
<h2>
	مهام لإنجازها
</h2>

<h3>
	تحقق من عنوان MAC
</h3>

<p>
	يتكون عنوان MAC لواجهة اتصال مع الشبكات من 6 أرقام ست عشرية ذات خانتين تفصل بينها نقطتان ":"، مثل العنوان التالي <code>'01:32:54:67:89:AB'</code>، اكتب تعبيرًا نظاميًا يتحقق من أن النص هو عنوان MAC.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_44" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/your regexp/</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'01:32:54:67:89:AB'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// true</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'0132546789AB'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// false (no colons)</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'01:32:54:67:89'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// false (5 numbers, must be 6)</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'01:32:54:67:89:ZZ'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="com">// false (ZZ at the end)</span></pre>

<p>
	<strong>الحل</strong>: يُعطى التعبير النمطي الذي يبحث عن عدد ست عشري من خانتين بالشكل: <code>{2}[‎0-9a-f]</code> مفترضين استخدام الراية <code>i</code>. سنحتاج الآن إلى هذا النمط وخمسة أنماط أخرى مشابهة له، وبالتالي سيكون التعبير النمطي على الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_46" style="">
<span class="pun">[</span><span class="lit">0</span><span class="pun">-</span><span class="lit">9a</span><span class="pun">-</span><span class="pln">f</span><span class="pun">]{</span><span class="lit">2</span><span class="pun">}(:[</span><span class="lit">0</span><span class="pun">-</span><span class="lit">9a</span><span class="pun">-</span><span class="pln">f</span><span class="pun">]{</span><span class="lit">2</span><span class="pun">}){</span><span class="lit">5</span><span class="pun">}</span></pre>

<p>
	ولكي نجبر التعبير على التقاط كامل النص الموافق، لابد من وضع محرفي ارتكاز البداية والنهاية <code>$...^</code> إليك شيفرة الحل بشكلها الكامل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_49" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/^[0-9a-f]{2}(:[0-9a-f]{2}){5}$/</span><span class="pln">i</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'01:32:54:67:89:AB'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// true</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'0132546789AB'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// false (no colons)</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'01:32:54:67:89'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// false (5 numbers, need 6)</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="str">'01:32:54:67:89:ZZ'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="com">// false (ZZ in the end)</span></pre>

<h3>
	أوجد الألوان التي تنسّق بالشكل abc# أو abcdef
</h3>

<p>
	اكتب تعبيرًا نمطيًا يبحث عن الألوان المكتوبة وفق أحد التنسيقين <code>abc#</code> أو <code>abcdef#</code>، أي المحرف <code>#</code> يليه ثلاث أو ست أرقام ست عشرية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_51" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/your regexp/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"color: #3f3; background-color: #AA00ef; and: #abcd"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// #3f3 #AA00ef</span></pre>

<p>
	لاحظ أنه لا ينبغي الحصول على تطابقات تحوي أربع أو خمس أرقام ست عشرية، بل 3 أو 6 فقط.
</p>

<p>
	<strong>الحل</strong>:
</p>

<p>
	سيكون التعبير النمطي المناسب للبحث عن شيفرة لون ثلاثية الأرقام كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_53" style="">
<span class="str">/#[a-f0-9]{3}/</span><span class="pln">i</span></pre>

<p>
	يمكننا أيضًا إضافة ثلاث أرقام ست عشرية أخرى بالضبط فلن نحتاج أكثر أو أقل، لكون الشيفرة اللونية مزيج من ثلاث أو ستة أرقام.
</p>

<p>
	لنستخدم إذًا المكمم {1,2} بعد وضع الصيغة السابقة للتعبير النمطي بين قوسين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_55" style="">
<span class="str">/#([a-f0-9]{3}){1,2}/</span><span class="pln">i</span></pre>

<p>
	لاحظ الشيفرة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_57" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/#([a-f0-9]{3}){1,2}/</span><span class="pln">gi</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"color: #3f3; background-color: #AA00ef; and: #abcd"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// #3f3 #AA00ef #abc</span></pre>

<p>
	تواجهنا مشكلة صغيرة هنا، فسيجد التعبير النمطي المحارف abc# ضمن abcd#. يمكن وضع النمط <code>b\</code> في النهاية لحل المشكلة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_59" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/#([a-f0-9]{3}){1,2}\b/</span><span class="pln">gi</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"color: #3f3; background-color: #AA00ef; and: #abcd"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// #3f3 #AA00ef</span></pre>

<h3>
	أوجد كل الأرقام
</h3>

<p>
	اكتب تعبيرًا نمطيًا يبحث عن كل الأعداد العشرية، بما فيها الصحيحة والعشرية ذات الفاصلة العائمة أو السالبة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_61" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/your regexp/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"-1.5 0 2 -123.4."</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// -1.5, 0, 2, -123.4</span></pre>

<p>
	<strong>الحل</strong>:
</p>

<p>
	يُعطى نمط العدد العشري الموجب بوجود القسم العشري منه على الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_63" style="">
<span class="pln">\d</span><span class="pun">+(</span><span class="pln">\.\d</span><span class="pun">+)?</span></pre>

<p>
	لنضع الإشارة السالبة <code>-</code> لتكون اختيارية في بداية النمط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_65" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/-?\d+(\.\d+)?/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"-1.5 0 2 -123.4."</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">   </span><span class="com">// -1.5, 0, 2, -123.4</span></pre>

<h3>
	فسر العبارات الرياضية
</h3>

<p>
	تتكون العملية الحسابية من عددين بينهما إشارة عمليات، مثل:
</p>

<ul>
<li>
		<code>1 + 2</code>
	</li>
	<li>
		<code>1.2 * 3.4</code>
	</li>
	<li>
		<code>-3 / -6</code>
	</li>
	<li>
		<code>-2 - 2</code>
	</li>
</ul>
<p>
	قد تكون العملية <code>"+"</code>أو <code>"-"</code> أو <code>"*"</code> أو <code>"/"</code>، وقد توجد مساحات فارغة قبل أو بعد أو بين الأجزاء.
</p>

<p>
	أنشئ تابعًا <code>(parse(expr</code> يقبل العبارة معاملًا، ويعيد مصفوفةً من ثلاثة عناصر، وهي:
</p>

<ol>
<li>
		العدد الأول.
	</li>
	<li>
		العامل الرياضي (إشارة العملية).
	</li>
	<li>
		العدد الثاني.
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_67" style="">
<span class="pln">let </span><span class="pun">[</span><span class="pln">a</span><span class="pun">,</span><span class="pln"> op</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> parse</span><span class="pun">(</span><span class="str">"1.2 * 3.4"</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">a</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 1.2</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">op</span><span class="pun">);</span><span class="pln"> </span><span class="com">// *</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">b</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 3.4</span></pre>

<p>
	<strong>الحل</strong>: يُعطى التعبير النمطي لإيجاد عدد كما رأينا في المهمة السابقة كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_69" style="">
<span class="pun">-?</span><span class="pln">\d</span><span class="pun">+(</span><span class="pln">\.\d</span><span class="pun">+)?</span></pre>

<p>
	تُعطى العملية الرياضية وفق النمط التالي [/*+-] ولابد من وضع المحرف <code>-</code> في بداية الأقواس المربعة لأنها ستعنى مجالًا من المحارف إن وضعت في المنتصف ونحن نريد فقط المحرف <code>-</code> بحد ذاته. لايد أيضًا من تجاوز المحرف <code>/</code>في التعبير النمطي كالتالي <code>/.../</code> وهذا ما سنفعله لاحقًا.
</p>

<p>
	نحتاج إلى عدد ثم عملية ثم عدد آخر وقد تكون هناك مساحات فارغة اختيارية بينهم وبالتالي سيكون التعبير النمطي كاملًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_72" style="">
<span class="pun">-?</span><span class="pln">\d</span><span class="pun">+(</span><span class="pln">\.\d</span><span class="pun">+)?</span><span class="pln">\s</span><span class="pun">*[-+*/]</span><span class="pln">\s</span><span class="pun">*-?</span><span class="pln">\d</span><span class="pun">+(</span><span class="pln">\.\d</span><span class="pun">+)?</span></pre>

<p>
	يتكون التعبير النمطي من ثلاثة أقسام يفصل بينها <code>‎s*‎\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_74" style="">
<span class="str">/-?\d+(\.\d+)?/</span><span class="pln">   </span><span class="com">// العدد الأول</span><span class="pln">
</span><span class="pun">/[-+*</span><span class="str">/],/</span><span class="pln">   </span><span class="com">// رمز العملية</span><span class="pln">
</span><span class="pun">/-?</span><span class="pln">\d</span><span class="pun">+(</span><span class="pln">\.\d</span><span class="pun">+)?</span><span class="str">/   /</span><span class="pun">/</span><span class="pln"> </span><span class="pun">العدد</span><span class="pln"> </span><span class="pun">الثاني</span></pre>

<p>
	يمثل القسم الأول العدد الأول والقسم الثاني العملية الحسابية والثالث العدد الثاني، ولكي يظهر كل قسم كنتيجة مستقلة ضمن مصفوفة النتيجة، سنضع كل قسم ضمن قوسين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_76" style="">
<span class="str">/(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)/</span></pre>

<p>
	إليك الشيفرة:
</p>

<pre class="ipsCode">
let regexp = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;
alert( "1.2 + 12".match(regexp) );
</pre>

<p>
	تتضمن النتائج:
</p>

<ol>
<li>
		التطابق كاملًا : "result[0] == "1.2 + 12
	</li>
	<li>
		المجموعة الأولى <code>(‎-?\d+(\.\d+)?‎)</code> وتمثل العدد الأول مع أجزائه العشرية: "1.2"
	</li>
	<li>
		المجموعة الثانية <code>‎(\.\d+)?‎</code> وتمثل الجزء العشري الأول: "‎.2"
	</li>
	<li>
		المجموعة الثالثة <code>‎([-+*\/])‎</code> وتمثل العملية الحسابية: "+"
	</li>
	<li>
		المجموعة الرابعة <code>‎(-?\d+(\.\d+)?)‎</code> وتمثل العدد الثاني: "12"
	</li>
	<li>
		المجموعة الخامسة <code>‎(\.\d+)?‎</code> والتي تمثل الجزء العشري من العدد الثاني وهو في الواقع غير موجود undefined
	</li>
</ol>
<p>
	نحتاج في الواقع إلى العددين والعملية الحسابية دون الأجزاء العشرية لذلك سنجعل التعبير أكثر وضوحًا ليلائم ما نريده. سنزيل العنصر الأول من المصفوفة والذي يمثل التطابق الكامل بإجراء انزياح لمصفوفة النتيجة <code>array.shift</code>.
</p>

<p>
	يمكن التخلص من الأجزاء العشرية <code>(‎.\d+‎)</code> في المجموعة الثانية والرابعة (أي النقط 3 و 4) من المصفوفة بوضع المحرف <code>?</code> في بداية كل مجموعة.
</p>

<p>
	إليك الحل النهائي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2899_78" style="">
<span class="kwd">function</span><span class="pln"> parse</span><span class="pun">(</span><span class="pln">expr</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/</span><span class="pun">;</span><span class="pln">

  let result </span><span class="pun">=</span><span class="pln"> expr</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">result</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
  result</span><span class="pun">.</span><span class="pln">shift</span><span class="pun">();</span><span class="pln">

  </span><span class="kwd">return</span><span class="pln"> result</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> parse</span><span class="pun">(</span><span class="str">"-1.23 * 3.45"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">  </span><span class="com">// -1.23, *, 3.45</span></pre>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://javascript.info/regexp-groups" rel="external nofollow">Capturing Groups</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%85%D8%AD%D8%AF%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D9%83%D9%85%D9%8A%D8%A9-%D9%88%D8%A3%D9%86%D9%85%D8%A7%D8%B7-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D9%81%D9%8A-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-r1419/" rel="">المحددات الكمية وأنماط استخدامها في التعابير النمطية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/linux/%d9%85%d9%82%d8%af%d9%85%d8%a9-%d9%81%d9%8a-%d8%a7%d9%84%d8%aa%d8%b9%d8%a7%d8%a8%d9%8a%d8%b1-%d8%a7%d9%84%d9%86%d9%85%d8%b7%d9%8a%d8%a9-regular-expressions-r63/" rel="">مقدمة في التعابير النمطية Regular Expressions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/cpp/%D8%A7%D9%84%D8%AA%D8%B9%D8%A8%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-regular-expressions-%D9%81%D9%8A-cpp-r957/" rel="">التعبيرات النمطية Regular expressions في Cpp</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1374/" rel="">التعابير النمطية في البرمجة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1420</guid><pubDate>Wed, 05 Jan 2022 06:54:03 +0000</pubDate></item><item><title>&#x627;&#x644;&#x645;&#x62D;&#x62F;&#x62F;&#x627;&#x62A; &#x627;&#x644;&#x643;&#x645;&#x64A;&#x629; &#x648;&#x623;&#x646;&#x645;&#x627;&#x637; &#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645;&#x647;&#x627; &#x641;&#x64A; &#x627;&#x644;&#x62A;&#x639;&#x627;&#x628;&#x64A;&#x631; &#x627;&#x644;&#x646;&#x645;&#x637;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%85%D8%AD%D8%AF%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D9%83%D9%85%D9%8A%D8%A9-%D9%88%D8%A3%D9%86%D9%85%D8%A7%D8%B7-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85%D9%87%D8%A7-%D9%81%D9%8A-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-r1419/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2022_01/61d49822b9bfa_-------JavaScript--.png.cab4c8e6ef46999814154a83b4032165.png" /></p>

<p>
	لنفترض أنّ لدينا النص التالي <code>67-45-123-(903)7+</code>، وأننا نريد إيجاد كل الأعداد الموجودة ضمنه، سنحتاج هنا إلى أعداد كاملة، مثل <code>7 و903 و123 و45 و67</code>، لا إلى كل رقم بمفرده، حيث يتألف العدد من رقم واحد أو تتابعٍ لأرقام <code>d\</code>، ولتحديد عدد الأرقام التي نحتاجها سنستخدم محددًا كميًا أو مكممًا quantifier.
</p>

<h2>
	محدد الكمية {n}
</h2>

<p>
	إنّ أبسط المحددات الكمية هو رقم ضمن أقواس معقوصة <code>{n}</code>، حيث يضاف المكمم إلى المحرف أو صنف المحرف أو الأقواس المربعة وغيرها؛ ليحدد العدد الذي نحتاجه منها، وللمكممات بعض الأشكال المتقدمة التي سنستعرض أمثلةً عنها:
</p>

<ul>
<li>
		العدد الدقيق: مثل <code>{5}</code>، والذي يحدد العدد الدقيق من المحارف التي نحتاجها، فمثلًا يحدد <code>{5}d</code> خمسة أرقام تمامًا، كما لو كتبنا <code>d\d\d\d\d\</code>، ويعرض المثال التالي كيف نبحث عن خمسة أرقام متتالية:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_7" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"I'm 12345 years old"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d{5}/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">//  "12345"</span></pre>

<p>
	ويمكن إضافة محرف حد الكلمة <code>b\</code> لنستثني الأرقام الأطول.
</p>

<ul>
<li>
		المجال: مثل <code>{3,5}</code>، ويحدد الأعداد التي تضم من 3 إلى 5 أرقام، ولإيجاد ذلك توضع حدود المجال ضمن أقواس معقوصة:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_9" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"I'm not 12, but 1234 years old"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d{3,5}/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// "1234"`</span></pre>

<p>
	يمكننا تجاهل الحد الأعلى ليصبح التعبير على الشكل <code>{,d{3\</code>، وسيبحث عن أعداد مؤلفة من ثلاثة أرقام أو أكثر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_11" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"I'm not 12, but 345678 years old"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d{3,}/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="com">// "345678"`</span></pre>

<p>
	لنعد الآن إلى النص <code>67-45-123-(903)7+</code>، حيث يتألف العدد من رقم واحد أو من تتابعٍ لأرقام في صف، فالتعبير النمطي المناسب هو <code>{,d{1\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_13" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"+7(903)-123-45-67"</span><span class="pun">;</span><span class="pln">

let numbers </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d{1,}/</span><span class="pln">g</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">numbers</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 7,903,123,45,67</span></pre>

<h2>
	اختصارات المحددات الكمية
</h2>

<p>
	لمعظم المكممات اختصارات، وهي:
</p>

<ul>
<li>
		<code>+</code>: يعني "واحد أو أكثر" مرة، تمامًا مثل النمط <code>{,1}</code>، إذ يبحث النمط <code>+d\</code> عن الأعداد:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_15" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"+7(903)-123-45-67"</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d+/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 7,903,123,45,67`</span></pre>

<ul>
<li>
		<code>?</code>: يعني "صفر أو واحد" مرة، تمامًا مثل النمط <code>{0,1}</code>، أي يجعل الرمز اختياريًا، إذ يبحث النمط <code>ou?r</code> مثلًا عن المحرف <code>o</code> متبوعًا بمحرف <code>u</code> واحد أو غير متبوع به، ومن ثم المحرف <code>r</code>، لنبحث عن الكلمتين <code>color</code> أو <code>colour</code>:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_17" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Should I write color or colour?"</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/colou?r/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// color, colour</span></pre>

<ul>
<li>
		<code>*</code>: يعني "صفر أو أكثر"، تمامًا مثل النمط <code>{,0}</code>، أي يبحث عن عدم وجود محرف أو تكراره مرةً واحدةً أو أكثر، إذ يبحث النمط <code>*d0\</code> مثلًا عن رقم متبوع بأي تكرار للمحرف <code>0</code> (عدم وجوده أو تكراره مرةً أو عدة مرات):
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_19" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"100 10 1"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d0*/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 100, 10, 1`</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"100 10 1"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d0+/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 100, 10 //  لا تطابق مع 1 لأن النمط 0+ سيتطلب أكثر من صفر</span></pre>

<h2>
	أمثلة أكثر
</h2>

<p>
	تستخدم المكممات كثيرًا، وتُعدّ البنية الرئيسية للتعابير النمطية المعقدة، وسنطلع على بعض الأمثلة.
</p>

<h3>
	التعبير النمطي للكسور العشرية (عدد بفاصلة عشرية)
</h3>

<p>
	ويعطى بالشكل <code>+d+\.\d\</code>، إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_21" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"0 1 12.345 7890"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d+\.\d+/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 12.345</span></pre>

<h3>
	التعبير النمطي لفتح وسم HTML لا يحوي سمات مثل <span> أو </span>
</h3>

<ol>
<li>
		الطريقة الأبسط: <code>a-z]+&gt;/i]&gt;/</code>
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_23" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"&lt;body&gt; ... &lt;/body&gt;"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/&lt;[a-z]+&gt;/</span><span class="pln">gi</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;body&gt;</span></pre>

<p>
	يبحث التعبير عن المحرف <code>'&gt;'</code> يتبعه حرف لاتيني أو أكثر، ثم المحرف <code>'&lt;'</code>.
</p>

<ol start="2">
<li>
		الطريق المحسنة: <code>a-z][a-z0-9]*&gt;/i]&gt;/</code>: إذ يمكن أن يحتوي وسم HTML -مثلما تنص المعايير- على رقم في أي موقع من الاسم، عدا بدايته مثل <code>&lt;h1&gt;</code>.
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_26" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"&lt;h1&gt;Hi!&lt;/h1&gt;"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/&lt;[a-z][a-z0-9]*&gt;/</span><span class="pln">gi</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;h1&gt;</span></pre>

<h3>
	التعبير النمطي لفتح أو إغلاق وسم HTML لا يحوي سمات
</h3>

<p>
	استخدم التعبير التالي: <code>a-z][a-z0-9]\*&gt;/i]?/\&gt;/</code>، حيث وضعنا شرطةً مائلةً اختياريةً <code>?/</code> قرب بداية النمط، وكان لابد من تجاوزها بشرطة معكوسة، وإلا فستعتقد <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a> أنها نهاية النمط.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_28" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"&lt;h1&gt;Hi!&lt;/h1&gt;"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/&lt;\/?[a-z][a-z0-9]*&gt;/</span><span class="pln">gi</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;h1&gt;, &lt;/h1&gt;</span></pre>

<p>
	لاحظ أنه لكتابة تعابير نمطية أكثر دقةً لا بدّ من تعقيد التعبير أكثر، فيمكن أن ترى بأن القاعدة المشتركة بين تلك الأمثلة هي أنّ الدقة تأتي من تعقيد التعبير وطوله، ففي وسوم HTML كان يمكننا استخدام التعبير <code>&lt;+w\&gt;</code>، لكن التعبير الذي استخدمناه هو الأصلح؛ بما يتوافق مع تقييدات التسمية وقواعدها في <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a>.
</p>

<p>
	هل يمكن استخدام <code>&lt;+w\&gt;</code> أم يجب استخدام النمط <code>&lt;‎[a-z][a-z0-9]*‎&gt;</code>؟
</p>

<p>
	يُقبَل استخدام كلا النمطين في الواقع العملي، ويعتمد اختيارنا على قدرتنا على التعامل مع التطابقات الناتجة عن البحث، وصعوبة استخلاصها بطرق أخرى.
</p>

<h2>
	مكممات البحث الجشعة والكسولة
</h2>

<p>
	تبدو المكممات Quantifiers بسيطة الاستخدام للوهلة الأولى، لكنها قد تغدو مربكةً عند استخدامها، لذلك لا بدّ من فهم آلية البحث جيدًا إذا خططنا للبحث عن أشياء أكثر تعقيدًا من <code>/+d\/</code>، لنلق نظرةً على المهمة التالية مثلًا:
</p>

<p>
	لدينا نص ما ونريد استبدال الإشارتين <code>«...»</code> بإشارتي التنصيص <code>"..."</code>، لأنها مفضلة في تنسيقات الطباعة والكتابة في بلدان عدة، أي يجب أن يصبح النص <code>"مرحبًا أحمد"</code> مثلًا بالشكل <code>«مرحبًا أحمد»</code>، وسنجد أيضًا إشارتي التنصيص <code>„!Witam, świat”</code> في اللغة البولندية، أو الإشارتين <code>「你好，世界」</code> في اللغة الصينية، لكننا سنختار الإشارتين <code>«...»</code> فقط في مهمتنا.
</p>

<p>
	ستكون الخطوة الأولى تحديد النصوص الموضوعة ضمن إشارتي التنصيص، ثم استبدالهما، وقد يبدو التعبير النمطي التالي <code>g/"+."/</code>، والذي يعني (إشارة تنصيص ثم شيء ما مكرر ثم إشارة تنصيص أخرى) مناسبًا، لكنه ليس كذلك.
</p>

<p>
	انظر إلى المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_30" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/".+"/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'a "witch" and her "broom" is one'</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// "witch" and her "broom"</span></pre>

<p>
	لم يعمل النمط كما هو مطلوب! فبدلًا من إيجاد الكلمتين <code>"witch"</code> و<code>"broom"</code>، وجد العبارة التالية:<code>"witch" and her "broom"</code>.
</p>

<h2>
	البحث الجشع Greedy search
</h2>

<p>
	يتبع محرك التعابير النمطية <a href="https://academy.hsoub.com/programming/advanced/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D9%88%D8%A7%D8%B1%D8%B2%D9%85%D9%8A%D8%A7%D8%AA-r1282/" rel="">الخوارزمية</a> التالية في إيجاد التطابقات:
</p>

<ul>
<li>
		من أجل كل موقع في النص:
	</li>
	<li>
		يحاول إيجاد التطابق عند هذا الموقع.
	</li>
	<li>
		إذا لم يجد تطابقًا فسينتقل إلى الموقع التالي.
	</li>
</ul>
<p>
	لكن هذه الكلمات غير قادرة على وصف سبب فشل التعبير بدقة، لذلك سنفصل آلية البحث عن النمط <code>"+."</code>:
</p>

<p>
	الخطوة الأولى، يحاول المحرك إيجاد المحرف الأول من النمط <code>"</code>عند الموقع 0 من النص <code>a "witch" and her "broom" is one</code>، لكنه بدلًا من ذلك سيجد المحرف <code>a</code>، لذلك سيتابع إلى الموقع التالي ويحاول إيجاد أول محارف النمط وسيخفق ثانيةً، وأخيرًا سيجد تطابقًا في الموقع الثالث (الموقع 2).
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="87977" href="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step1_01.png.c3c5921ff56ba9d6a88393948f4d6ebf.png" rel=""><img alt="greedy_step1_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87977" data-unique="beibfyxx7" src="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step1_01.png.c3c5921ff56ba9d6a88393948f4d6ebf.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	الخطوة الثانية، يجد المحرك المحرف الأول ويتابع لإيجاد بقية محارف النمط، ويحاول أن يكتشف فيما لو تطابق بقية النص مع المحارف الباقية من النمط <code>"+.</code>، وسيكون المحرف التالي في النمط هو النقطة <code>.</code>، التي تشير إلى أي محرف عدا محرف السطر الجديد، لذلك ستجد التطابق مع الحرف <code>'w'</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="87978" href="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step2_02.png.b95ac072111678c65424bcabea2c1f1c.png" rel=""><img alt="greedy_step2_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87978" data-unique="s1bkajug1" src="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step2_02.png.b95ac072111678c65424bcabea2c1f1c.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	الخطوة الثالثة، سيتابع المحرك المطابقة مع النقطة نظرًا لوجود المكمم <code>+</code> بعدها، وسيطابق كل المحارف الأخرى ويضيفها إلى النتيجة حتى يصل إلى نهاية النص، لأن كل المحارف عدا محرف السطر الجديد ستمثل تطابقًا.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="87979" href="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step3_03.png.32b215be01b27ffbaab7bad0ff281cf9.png" rel=""><img alt="greedy_step3_03.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87979" data-unique="myh94o0q1" src="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step3_03.png.32b215be01b27ffbaab7bad0ff281cf9.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	الخطوة الرابعة، سينتهي المحرك من تكرار المحرف <code>.</code> ويحاول إيجاد المحرف التالي من النمط -وهي إشارة التنصيص <code>"</code>-، وهنا تظهر المشكلة فقد انتهى النص ولم يعد هناك المزيد من المحارف للبحث ضمنها، عندها سيفهم المحرك أنه ضم الكثير من المحارف <code>.+</code>، وسيبدأ بالتراجع، حيث يختصر نتيجة التطابق التي ولّدها المكمم بمقدار محرف إلى الخلف؛ مفترضًا أن المحارف ستنتهي قبل انتهاء النص بمحرف واحد، ثم يحاول إيجاد بقية محارف النمط انطلاقًا من هذا الموقع، وسينهي المحرك البحث إذا وجد المحرف، لكنه سيجد المحرف <code>'e'</code>، وبالتالي لن تنتهي العملية.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="87980" href="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step4_04.png.d8072af833ef8c6668bf5bc7f9c88c60.png" rel=""><img alt="greedy_step4_04.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87980" data-unique="5t0mdxfl2" src="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step4_04.png.d8072af833ef8c6668bf5bc7f9c88c60.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	الخطوة الخامسة، سيقلل المحرك عدد المحارف الناتجة عن تكرار النقطة بمحرف واحد أيضًا، إلى أن يجد التطابق مع آخر محارف النمط <code>'"'</code> حتى يجده.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="87981" href="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step5_05.png.d11b94653fa25f362e8bbee42ffbb3ad.png" rel=""><img alt="greedy_step5_05.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87981" data-unique="je1zrqh8b" src="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step5_05.png.d11b94653fa25f362e8bbee42ffbb3ad.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	الخطوة السادسة، سيكتمل البحث عند إيجاد التطابق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="87982" href="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step6_06.png.28cdfa7be9f26244360cb4c2b35edcc6.png" rel=""><img alt="greedy_step6_06.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87982" data-unique="mf1bx2le7" src="https://academy.hsoub.com/uploads/monthly_2022_01/greedy_step6_06.png.28cdfa7be9f26244360cb4c2b35edcc6.png" style="width: 400px; height: auto;"></a>
</p>

<p>
	الخطوة السابعة، وهكذا ستكون النتيجة هي <code>"witch" and her "broom"</code>.
</p>

<p>
	الخطوة الثامنة، مع وجود الراية <code>g</code> سيتابع البحث عن النمط ابتداءً من الموقع الذي انتهى فيه التطابق الأول، ولن يحصل على نتيجة طبعًا، فلن يجد إشارة تنصيص أخرى، طبعًا لم نتوقع ذلك، لكن الأمور تسير هكذا!
</p>

<p>
	<strong>يكرَّر المكمم في النمط الموسَّع -افتراضيًا- أكبر عدد ممكن من المرات</strong>، حيث يضيف المحرك القدر الأكبر من المحارف التي تتطابق مع <code>+.</code>، ثم يختصر النتيجة محرفًا تلو الآخر.
</p>

<h2>
	النمط الكسول Lazy mode
</h2>

<p>
	النمط الكسول أو المحدود هو نمط معاكس للنمط الموَّسع أو الجشع، ويعني أقل تكرار للمحارف، ويمكن أن نختار هذا النمط بوضع إشارة الاستفهام <code>?</code> بعد المكمم حيث يصبح بالشكل <code>?*</code> أو <code>?+</code> أو حتى <code>??</code> لــ<code>?</code>، ولنوضح الأمر علينا معرفة أن إشارة الاستفهام <code>?</code> هي مكمم بحد ذاتها (صفر أو واحد)، وعندما تُضاف بعد مكمم آخر فسيصبح لها معنىً آخر، وهو تحويل نمط البحث إلى النمط المحدود الكسول، وسيعمل النمط <code>g/"?+."/</code> في مهمتنا السابقة بالشكل الصحيح:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_39" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/".+?"/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'a "witch" and her "broom" is one'</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// "witch", "broom"</span></pre>

<p>
	ولفهم التغيّر بوضوح سنتعقب عملية البحث خطوةً خطوةَ:
</p>

<p>
	الخطوة الأولى، تبقى الخطوة الأولى نفسها، وهي إيجاد المحرف <code>'"'</code> في الموقع الثالث من النص.
</p>

<p style="text-align: center;">
	<img alt="lazy_step1_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87983" data-unique="xy1l2ga5z" src="https://academy.hsoub.com/uploads/monthly_2022_01/lazy_step1_01.png.aa0e450cea2fe7f4572052d8a97dcdc6.png" style="width: 400px; height: auto;"></p>

<p>
	الخطوة الثانية، وسيجد المحرك في الخطوة الثانية ما يطابق المحرف <code>'.'</code>.
</p>

<p style="text-align: center;">
	<img alt="lazy_step2_08.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87984" data-unique="tnkvzb0pp" src="https://academy.hsoub.com/uploads/monthly_2022_01/lazy_step2_08.png.774d5f3cb75dc2bac7fc3a1f3b67974d.png" style="width: 400px; height: auto;"></p>

<p>
	الخطوة الثالثة، سيظهر الاختلاف الآن، حيث لا يحاول المحرك في النمط المحدود إيجاد تطابق مع ما تمثله النقطة مرةً أخرى، بل يتوقف ويحاول إيجاد تطابقات مع بقية محارف النمط (أي <code>'"'</code>) مباشرةً، وسيتوقف البحث عن النمط إذا وجد تطابقًا، لكنه سيجد الحرف <code>i</code> بدلًا منه.
</p>

<p style="text-align: center;">
	<img alt="lazy_step3_09.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87985" data-unique="v9wzoewy1" src="https://academy.hsoub.com/uploads/monthly_2022_01/lazy_step3_09.png.69bde19943b3c5c2e19954abb9577567.png" style="width: 400px; height: auto;"></p>

<p>
	الخطوة الرابعة، سيزيد بعد ذلك محرك التعبير النمطي عدد التكرارات لمحرف النقطة مرةً واحدةً، ثم يحاول مطابقة بقية النمط، لكنه سيفشل ثانيةً، وسيزيد عدد مرات التكرار مرةً أخرى وأخرى…
</p>

<p style="text-align: center;">
	<img alt="lazy_step4_10.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87986" data-unique="pfr5bsw1l" src="https://academy.hsoub.com/uploads/monthly_2022_01/lazy_step4_10.png.73d837626e8d2efcbb42c072aca43321.png" style="width: 400px; height: auto;"></p>

<p>
	الخطوة الخامسة، سيتوقف البحث عندما يجد التطابق مع المحرف <code>'"'</code>.
</p>

<p style="text-align: center;">
	<img alt="lazy_step5_11.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87987" data-unique="z63hehopb" src="https://academy.hsoub.com/uploads/monthly_2022_01/lazy_step5_11.png.4e856a814eae46ee50e0d2b07e25f78b.png" style="width: 400px; height: auto;"></p>

<p>
	الخطوة السادسة، يبدأ البحث الجديد، نظرًا لوجود الراية <code>g</code>، من الموقع الذي انتهى عنده البحث السابق، وسيجد النتيجة الثانية.
</p>

<p style="text-align: center;">
	<img alt="lazy_step6_12.png" class="ipsImage ipsImage_thumbnailed" data-fileid="87988" data-unique="kl0eq9rb7" src="https://academy.hsoub.com/uploads/monthly_2022_01/lazy_step6_12.png.c84bd31678bcb43926af8fb7f53e68b0.png" style="width: 400px; height: auto;"></p>

<p>
	يعمل النمط الكسول بشكل مشابه للمكممات <code>?*</code> و<code>??</code>، حيث يزيد محرك التعبير النمطي عدد التكرارات إذا لم يجد تطابقًا مع بقية محارف النمط في الموقع المُعطى.
</p>

<p>
	<strong>يُفعّل النمط الكسول للمكممات إذا تبعها المحرف <code>?</code></strong>، وإلا فستبقى في النمط الموّسع الجشع، إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_41" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"123 456"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d+ \d+?/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 123 4</span></pre>

<ol>
<li>
		يحاول النمط <code>+d\</code> إيجاد أكبر عدد ممكن من الأعداد (نمط موسّع)، لذلك سيجد <code>123</code> ويتوقف، لأن المحرف التالي سيكون الفراغ <code>' '</code>.
	</li>
	<li>
		سيجد المحرك المحرف التالي في النمط وهو الفراغ <code>' '</code>.
	</li>
	<li>
		يحاول إيجاد الأعداد التالية وفقًا للنمط <code>?+d\</code> حيث يكون المكمم محدودًا، وسيجد العدد <code>4</code> ثم يتحقق من وجود بقية محارف النمط بعده، لكن محارف النمط قد انتهت.
	</li>
</ol>
<p>
	لا يكرر النمط الكسول أي شيء دون حاجة إلى ذلك، لذا سينتهي البحث بالنتيجة <code>4 123</code>.
</p>

<p>
	لاحظ، تحسن محركات <a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1374/" rel="">التعابير النمطية</a> الحديثة خوارزميات البحث الداخلية لتعمل بسرعة أكبر، لذلك قد تعمل بطريقة مختلفة قليلًا عما وصفناه سابقًا، ولن نحتاج إلى معرفة تفاصيل الخوارزمية المحسَّنة عند كتابة التعابير النمطية وفهمها، لأنها تعمل داخليًا، وبما أن التعابير المعقدة صعبة التحسين، فسيجري البحث وفق الخطوات التي وصفناها سابقًا تمامًا.
</p>

<h2>
	مقاربة بديلة
</h2>

<p>
	نجد عادةً عند استخدام التعابير النمطية عدة طرق لتنفيذ الشيء نفسه، إذ يمكن في حالتنا السابقة إيجاد إشارات التنصيص دون الحاجة إلى البحث المحدود، بل من خلال النمط <code>"+[^"]"</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_43" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/"[^"]+"/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'a "witch" and her "broom" is one'</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// "witch", "broom"</span></pre>

<p>
	سينجح هذا النمط لأنه يبحث عن إشارة تنصيص يتبعها تكرار لمحرف أو أكثر لا يمثل إشارة تنصيص، ثم يبحث عن إشارة تنصيص أخرى لإغلاق النص، وتجدر الملاحظة أن هذا المنطق في البحث لا يشكل بديلًا عن البحث المحدود، فهنالك حالات نحتاجه فيها.
</p>

<p>
	<strong>مثال عن إخفاق المكمم الكسول ونجاح الأسلوب البديل</strong>
</p>

<p>
	لنحاول مثلًا إيجاد رابط من الشكل <code>&lt;a href="..." class="doc"‎&gt;</code> بحيث يصلح لأي عنوان <code>href</code>، فما هو التعبير النمطي الذي سنستخدمه؟
</p>

<p>
	قد تكون الفكرة الأولى هي: <code>a href=".*" class="doc"&gt;/g&gt;/</code>، وسنتحقق منها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_45" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'...&lt;a href="link" class="doc"&gt;...'</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;a href=".*" class="doc"&gt;/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

</span><span class="com">// !عملت</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;a href="link" class="doc"&gt;</span></pre>

<p>
	ستعمل هذه الفكرة، لكن دعونا نتحقق منها عند وجود عدة روابط في النص:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_47" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'...&lt;a href="link1" class="doc"&gt;... &lt;a href="link2" class="doc"&gt;...'</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;a href=".*" class="doc"&gt;/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

</span><span class="com">// حصلنا على رابطين في نتيجة واحدة</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;a href="link1" class="doc"&gt;... &lt;a href="link2" class="doc"&gt;</span></pre>

<p>
	النتيجة خاطئة، لأنّ المكمم <code>*.</code> ضمّ الكثير من المحارف، وسيكون التطابق مشابهًا للوصف التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_49" style="">
<span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"....................................."</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"link1"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;...</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"link2"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;</span></pre>

<p>
	لنُعدِّل النمط بجعل المكمم كسولًا ( <code>?*.</code>):
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_51" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'...&lt;a href="link1" class="doc"&gt;... &lt;a href="link2" class="doc"&gt;...'</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;a href=".*?" class="doc"&gt;/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

</span><span class="com">// Works!</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;a href="link1" class="doc"&gt;, &lt;a href="link2" class="doc"&gt;</span></pre>

<p>
	سينجح الأمر وسيجد الرابطين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_53" style="">
<span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"....."</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;</span><span class="pln">    </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"....."</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"link1"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;...</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"link2"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;</span></pre>

<p>
	لنختبر ذلك مع نص آخر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_55" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'...&lt;a href="link1" class="wrong"&gt;... &lt;p style="" class="doc"&gt;...'</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;a href=".*?" class="doc"&gt;/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

</span><span class="com">// أخفق أيضًا</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;a href="link1" class="wrong"&gt;... &lt;p style="" class="doc"&gt;</span></pre>

<p>
	لقد أخفق البحث مجددًا، فقد طابق البحث الكثير من محارف النص بما فيها <code>&lt;p&gt;</code>، فما السبب؟
</p>

<p>
	إليك ما حدث:
</p>

<ol>
<li>
		يجد محرك التعبير النمطي المحارف <code>&lt;"=a href&gt;</code> بدايةً.
	</li>
	<li>
		ثم يبحث عن المكمم المحدود <code>?*.</code>، الذي يأخذ محرفًا ويتحقق من وجود تطابق مع بقية أحرف النمط <code>"class="doc "</code>، فلا يجد شيئًا.
	</li>
	<li>
		ثم يضيف محرفًا إلى نتيجة النمط <code>?*.</code> ويستمر بالإضافة إلى أن يصل إلى <code>"class="doc "</code>، لكن المشكلة أن ما وجده سيكون خارج حدود الرابط <code>&lt;...a&gt;</code> وضمن وسم مختلف <code>&lt;P&gt;</code>.
	</li>
</ol>
<p>
	إليك وصفًا لما حدث:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_57" style="">
<span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"..................................."</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"link1"</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"wrong"</span><span class="pun">&gt;...</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">p style</span><span class="pun">=</span><span class="str">""</span><span class="pln"> </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"doc"</span><span class="pun">&gt;</span></pre>

<p>
	لاحظ إخفاق كل من البحث الجشع والكسول في المهمة، لكن سيُنفذ النمط <code>"*[^"]"=href</code> المطلوب في حالتنا، لأنه سيبحث عن المحارف ضمن السمة <code>href</code> إلى أن يجد أقرب إشارة تنصيص، وهذا ما نريده.
</p>

<p>
	إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_59" style="">
<span class="pln">let str1 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'...&lt;a href="link1" class="wrong"&gt;... &lt;p style="" class="doc"&gt;...'</span><span class="pun">;</span><span class="pln">
let str2 </span><span class="pun">=</span><span class="pln"> </span><span class="str">'...&lt;a href="link1" class="doc"&gt;... &lt;a href="link2" class="doc"&gt;...'</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;a href="[^"]*" class="doc"&gt;/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

</span><span class="com">// Works!</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str1</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null, لا تطابق وهذا صحيح</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str2</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// &lt;a href="link1" class="doc"&gt;, &lt;a href="link2" class="doc"&gt;</span></pre>

<h2>
	الخلاصة
</h2>

<p>
	للمكممات Quantifiers نمطان للعمل:
</p>

<ul>
<li>
		الواسع الجشع Greedy: وهو الخيار الافتراضي الذي يحاول فيه محرك التعبير النمطي تكرار المحرف المُحصى أكبر عدد ممكن من المرات، فالنمط <code>+d\</code> مثلًا سيستهلك كل الأرقام الممكنة، وعندما لا يعود قادرًا على إضافة المزيد من المحارف سيتابع مطابقة بقية أحرف النمط، فإذا لم يجد تطابقًا فسيقلل عدد مرات التكرار مرةً واحدةً -يتراجع- ويحاول مجددًا.
	</li>
	<li>
		المحدود الكسول Lazy: ويُمكَّن بإضافة إشارة الاستفهام بعد المكمم، حيث يحاول المحرك إيجاد تطابق مع بقية محارف النمط قبل تكرار البحث عن المحرف المُحصى.
	</li>
</ul>
<p>
	ويمكن استخدام بحث موّسع ومعدّل بدقة وفق النمط <code>"[^"]+"</code> بديلًا عن الأسلوبين.
</p>

<h2>
	مهام لإنجازها
</h2>

<h3>
	إيجاد ثلاث نقاط متتابعة أو أكثر
</h3>

<p>
	اكتب نمطًا للبحث عن ثلاث نقاط متتابعة أو أكثر
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_61" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/your regexp/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello!... How goes?....."</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// ..., .....</span></pre>

<p>
	<strong>الحل</strong>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_63" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/\.{3,}/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello!... How goes?....."</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// ..., .....</span></pre>

<p>
	تذكر أن النقطة <code>.</code> محرف خاص ولابد من تجاوزه وحشر النمط <code>.\</code>.
</p>

<h3>
	تحديد الألوان في HTML
</h3>

<p>
	أنشئ تعبيرًا نمطيًا للبحث عن ألوان HTML المكتوبة على الشكل <code>ABCDEF#</code>، ابحث أولًا عن المحرف <code>#</code> تليه 6 محارف لأرقام ستة عشرية، إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_65" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/...your regexp.../</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2 #12345678"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln">  </span><span class="com">// #121212,#AA00ef</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		ملاحظة: لن نبحث في هذه المهمة عن تنسيقات لونية مثل <code>‎#123</code> أو <code>(rgb(1,2,3</code>.
	</p>
</blockquote>

<p>
	<strong>الحل</strong>:
</p>

<p>
	علينا أن نبحث عن المحرف <code>#</code> متبوعًا بستة محارف ست عشرية. يمكن وصف المحرف الست عشري وفق النمط <code>[0‎-9a-fA-F]</code>، أو وفق النمط <code>[‎0-9a-f]</code> عند استخدام الراية <code>i</code>. يمكننا عندئذ البحث عن ستة محارف ست عشرية باستخدام المكمم <code>{6}</code>. بالنتيجة سيكون التعبير النمطي المناسب من الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_67" style="">
<span class="str">/#[a-f0-9]{6}/</span><span class="pln">gi</span></pre>

<p>
	إليك شيفرة الحل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_69" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/#[a-f0-9]{6}/</span><span class="pln">gi</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2"</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">  </span><span class="com">// #121212,#AA00ef</span></pre>

<p>
	المشكلة في هذا الحل أن التعبير يطابق سلسلة طويلة أكثر حتى لو كانت أكثر من 6 محارف مثل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_71" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"#12345678"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln"> </span><span class="str">/#[a-f0-9]{6}/</span><span class="pln">gi </span><span class="pun">)</span><span class="pln"> </span><span class="pun">)</span><span class="pln"> </span><span class="com">// #123456</span></pre>

<p>
	يمكن حل هذه المشكلة بإضافة <code>‎\b</code> لنهاية التعبير:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_73" style="">
<span class="com">// لون</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"#123456"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln"> </span><span class="str">/#[a-f0-9]{6}\b/</span><span class="pln">gi </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// #123456</span><span class="pln">

</span><span class="com">// ليس لون</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"#12345678"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln"> </span><span class="str">/#[a-f0-9]{6}\b/</span><span class="pln">gi </span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null</span></pre>

<h3>
	إيجاد تطابق مع النمط /?+d+? d/
</h3>

<p>
	ما نتيجة التطابق في المثال الآتي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_75" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"123 456"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d+? \d+?/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// ?</span></pre>

<p>
	<strong>الحل</strong>: النتيجة هي <code>4 123</code>.
</p>

<p>
	يحاول النمط الكسول <code>‎d+?‎\</code> أقل عدد ممكن من الأرقام لكنه سيصل إلى محرف المسافة الفارغة وبالتالي سيأخذ فقط <code>123</code>، ثم يأخذ النمط الثاني <code>‎d+?‎\</code> رقمًا واحدًا فقط ويكتفي.
</p>

<h3>
	إيجاد تعليقات HTML
</h3>

<p>
	أوجد كل التعليقات في النص التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_77" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/your regexp/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`...</span><span class="pln"> </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">My</span><span class="pln"> </span><span class="pun">--</span><span class="pln"> comment
 test </span><span class="pun">--&gt;</span><span class="pln"> </span><span class="pun">..</span><span class="pln">  </span><span class="pun">&lt;!----&gt;</span><span class="pln"> </span><span class="pun">..</span><span class="pln">
</span><span class="pun">`;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// '&lt;!-- My -- comment \n test --&gt;', '&lt;!----&gt;'</span></pre>

<p>
	<strong>الحل</strong>:
</p>

<p>
	علينا إيجاد نمط بداية التعليق <code>--!&gt;</code> ثم نأخذ المحارف اللاحقة له حتى نصل إلى نمط نهاية التعليق <code>&lt;--</code>
</p>

<p>
	إذًا سيكون النمط من الشكل &lt;--?*.--!&gt; ، حيث يجبر المكمم الكسول المحرف <code>.</code> على التوقف قبل النمط <code>&lt;--</code>. نحتاج أيضًا إلى الراية <code>s</code> مع النقطة لكي تستمر في ضم المحارف حتى لو انتقلنا إلى سطر جديد وإلا لن نتمكن من التقاط التعليقات الممتدة على أكثر من سطر.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_79" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;!--.*?--&gt;/</span><span class="pln">gs</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`...</span><span class="pln"> </span><span class="pun">&lt;!--</span><span class="pln"> </span><span class="typ">My</span><span class="pln"> </span><span class="pun">--</span><span class="pln"> comment
 test </span><span class="pun">--&gt;</span><span class="pln"> </span><span class="pun">..</span><span class="pln">  </span><span class="pun">&lt;!----&gt;</span><span class="pln"> 
</span><span class="pun">..`;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// '&lt;!-- My -- comment \n test --&gt;', '&lt;!----&gt;'</span></pre>

<h3>
	إيجاد وسوم HTML
</h3>

<p>
	أنشئ تعبيرًا نمطيًا لإيجاد كل وسوم HTML (للبداية والنهاية) مع سماتها.
</p>

<p>
	إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_81" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/your regexp/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;&gt; &lt;a href="/"&gt; &lt;input type="radio" checked&gt; &lt;b&gt;'</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// '&lt;a href="/"&gt;', '&lt;input type="radio" checked&gt;', '&lt;b&gt;'</span></pre>

<p>
	نفترض هنا عدم وجود المحرفين <code>&lt;</code> و<code>&gt;</code> ضمن سمات الوسم، مما يسهل الأمر قليلًا.
</p>

<p>
	<strong>الحل</strong>
</p>

<p>
	سيكون التعبير النمطي الصحيح من الشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_83" style="">
<span class="pun">&lt;[^&lt;&gt;]+&gt;</span></pre>

<p>
	إليك شيفرة الحل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7630_85" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/&lt;[^&lt;&gt;]+&gt;/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;&gt; &lt;a href="/"&gt; &lt;input type="radio" checked&gt; &lt;b&gt;'</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// '&lt;a href="/"&gt;', '&lt;input type="radio" checked&gt;', '&lt;b&gt;'</span></pre>

<p>
	ترجمة -وبتصرف- للفصلين <a href="https://javascript.info/regexp-quantifiers" rel="external nofollow">َQuntifiers +,*,? And {n}</a> و<a href="https://javascript.info/regexp-greedy-and-lazy" rel="external nofollow">َGreedy and Lazy Quantifiers</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Moden JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AC%D8%A7%D9%84%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-r1400/" rel="">المجموعات والمجالات في التعابير النمطية</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1399/" rel="">أساسيات البحث باستخدام التعابير النمطية في جافاسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AC%D8%A7%D9%84%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-r1400/" rel="">المجموعات والمجالات في التعابير النمطية</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1419</guid><pubDate>Sat, 01 Jan 2022 16:00:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x62C;&#x627;&#x632; &#x645;&#x634;&#x631;&#x648;&#x639; &#x645;&#x62D;&#x631;&#x631; &#x631;&#x633;&#x648;&#x645; &#x646;&#x642;&#x637;&#x64A;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A5%D9%86%D8%AC%D8%A7%D8%B2-%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D9%85%D8%AD%D8%B1%D8%B1-%D8%B1%D8%B3%D9%88%D9%85-%D9%86%D9%82%D8%B7%D9%8A%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1401/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_12/61bada0077d05_-01.jpg.72b946c3fd022d22c407f95dc5dfd681.jpg" /></p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		إنني أنظر إلى هذه الألوان التي أمامي، ثم أنظر إلى اللوحة البيضاء، ثم أحاول وضع الألوان عليها كما يضع الشاعر كلماته في قصيدته تمامًا.
	</p>

	<p>
		ـــ يوان ميرو Joan Miro.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85692" href="https://academy.hsoub.com/uploads/monthly_2021_12/chapter_picture_19.jpg.aec632d61a311b93d518547e0ff96111.jpg" rel=""><img alt="chapter_picture_19.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="85692" data-unique="ptmkwzhoh" src="https://academy.hsoub.com/uploads/monthly_2021_12/chapter_picture_19.jpg.aec632d61a311b93d518547e0ff96111.jpg"></a>
</p>

<p>
	سنبني في هذا المقال تطبيق ويب بناءً على ما درسناه في المقالات السابقة وسيكون هذا التطبيق للرسم بأسلوب البكسلات، حيث يمكنك أخذ رؤية مكبرة من الصورة المنفردة وتغيير أو تعديل كل بكسل فيها، كما يمكنك فتح الصورة بالبرنامج وتعديلها بالفأرة أو أيّ أداة تأشير أخرى ثم حفظها، حيث سيبدو البرنامج هكذا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85695" href="https://academy.hsoub.com/uploads/monthly_2021_12/pixel_editor.png.bba1b7ea1d8b36f2227fde76ae7b208f.png" rel=""><img alt="pixel_editor.png" class="ipsImage ipsImage_thumbnailed" data-fileid="85695" data-unique="lg24y94om" src="https://academy.hsoub.com/uploads/monthly_2021_12/pixel_editor.png.bba1b7ea1d8b36f2227fde76ae7b208f.png" style="width: 500px; height: auto;"></a>
</p>

<h2>
	المكونات
</h2>

<p>
	تُظهر واجهة البرنامج عنصر <a href="https://wiki.hsoub.com/HTML/canvas" rel="external"><code>&lt;canvas&gt;</code></a> كبيرًا في الأعلى مع عدد من <a href="https://wiki.hsoub.com/HTML/form" rel="external">حقول الاستمارات</a> form fields أسفله، ويرسم المستخدِم على الصورة عبر اختيار أداة من حقل <a href="https://wiki.hsoub.com/HTML/select" rel="external"><code>&lt;select&gt;</code></a> ثم ينقر عليه أو يدهن أو يسحب المؤشر في لوحة الرسم، كما هناك أدوات لرسم بكسلات منفردة أو مستطيلات ولملء مساحة ما باللون ولالتقاط لون ما من الصورة.
</p>

<p>
	سنبني هيكل المحرِّر على أساس عدد من المكونات والكائنات التي تكون مسؤولة عن جزء من تمثيل <a href="https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%86%D8%AF-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1322/" rel="">كائن المستند DOM</a> وقد تحتوي مكونات أخرى داخلها، كما تتكون حالة التطبيق من الصورة الحالية والأداة المختارة واللون المختار كذلك، حيث سنضبط هذه المتغيرات كي تكون الحالة داخل قيمة واحدة، كما تبني مكونات الواجهة مظهرها دائمًا على الحالة الراهنة.
</p>

<p>
	دعنا ننظر في بديل ذلك كي نرى أهميته من خلال توزيع أجزاء من الحالة على الواجهة، وهذا سهل على البرنامج حتى نقطة ما، حيث نستطيع وضع حقل اللون ونقرأ قيمته عندما نريد معرفة اللون الحالي، لكن نضيف هنا <a href="https://academy.hsoub.com/design/general/%D8%A3%D9%81%D8%B6%D9%84-12-%D8%A3%D8%AF%D8%A7%D8%A9-%D8%A7%D9%86%D8%AA%D9%82%D8%A7%D8%A1-%D9%84%D9%84%D8%A3%D9%84%D9%88%D8%A7%D9%86-%D9%84%D9%85%D8%B5%D9%85%D9%85%D9%8A-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r515/" rel="">منتقي الألوان color picker</a> الذي يُعَدّ الأداة التي تسمح لك بالنقر على الصورة لاختيار اللون من بكسل ما، ولكي يظل حقل اللون مُظهرًا اللون الصحيح يجب على هذه الأداة معرفة أنه موجود وتحدِّثه كلما اختارت لونًا جديدًا، فإذا أضفت مكانًا آخرًا يجعل اللون مرئيًا بحيث يستطيع مؤشر الفأرة إظهاره مثلًا، فعليك تحديث شيفرة تغيير اللون لديك كي تبقى متزامنة.
</p>

<p>
	لكن في الواقع يخلق هذا مشكلةً بحيث يحتاج كل جزء من الواجهة إلى معرفة جميع الأجزاء الأخرى، وهذا ليس عمليًا إذ سيجعل البرنامج أقل مرونة في التعديل عليه، لكن لا يمثِّل هذا مشكلةً بالنسبة لبرنامج صغير مثل برنامجنا؛ أما في المشاريع الكبيرة فسيكون هذا كارثة حقيقية، ولكي نتجنب هذا الكابوس وإن كان من حيث المبدأ في مثالنا فسنكون حازمين بشأن تدفق البيانات، فهناك حالة تُرسم الواجهة وفقًا لها، وقد يستجيب مكوِّن الواجهة لإجراءات المستخدِم عبر تحديث الحالة، بحيث تحصل المكونات عندئذ على فرصة لمزامنة أنفسها مع هذه الحالة الجديدة.
</p>

<p>
	ضُبِط كل مكون من الناحية العملية ليُخطر عناصره الفرعية بإشعار كلما أُعطي حالةً جديدةً بالقدر الذي تحتاج إليه، لكن يُعَدّ ضبط ذلك أمرًا متعبًا، كما تفضِّل المتصفحات كثيرًا المكتبات البرمجية التي تسهل ذلك، لكن نستطيع إنشاء برنامج صغير مثل هذا بدون هذه <a href="https://academy.hsoub.com/devops/networking/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D9%86%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%AD%D8%AA%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8%D9%8A%D8%A9-r552/" rel="">البنية التحتية</a>، كما تمثَّل التحديثات على الحالة على أساس كائنات سنطلق عليها إجراءات، وقد تنشئ المكونات مثل هذه الإجراءات وترسلها بسرعة إلى دالة مركزية لإدارة الحالة، حيث تحسب هذه الدالة الحالة التالية ثم تحدِّث مكونات الواجهة أنفسها إليها.
</p>

<p>
	نأخذ بهذا مهمة تشغيل واجهة المستخدِم ونضيف إليها بعض الهيكلة، كما تحتفظ الأجزاء المتعلقة بـ <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-dom-r644/" rel="">DOM</a> بما يشبه العمود الفقري رغم أنها ملأى بالآثار الجانبية، إذ يُعَدّ هذا العمود دورة تحديث الحالة، كما تحدِّد الحالة كيف سيبدو DOM، ولا توجد طريقة تستطيع أحداث DOM تغيير الحالة بها إلا عبر إرسال الإجراءات إلى الحالة، كما توجد هناك صور عدة لهذا المنظور ولكل منها منافعه ومساوئه، لكن الفكرة الرئيسية لها واحدة وهي أنّ تغيرات الحالة يجب عليها المرور على قناة واحدة معرَّفة جيدًا بدلًا من كونها في كل مكان.
</p>

<p>
	ستكون مكوناتنا أصنافًا مطابقةً للواجهة، ويُعطى بانيها حالةً قد تكون حالة البرنامج كله أو قيمةً أصغر إذا لم يحتج الوصول إلى كل شيء، حيث يستخدِم هذا في بناء خاصية <code>dom</code>، ويُعَدّ هذا عنصر DOM الذي سيمثِّل المكوِّن، كما ستأخذ أغلب المنشئات قيمًا أخرى أيضًا لا تتغير مع الوقت مثل الدالة التي تستطيع استخدامها لإرسال إجراء ما، ويملك كل مكوِّن تابعًا <code>syncState</code> يُستخدَم لمزامنته مع قيمة الحالة الجديدة، حيث يأخذ التابع وسيطًا واحدًا وهو الحالة التي تكون من نوع الوسيط الأول نفسه لبانيها.
</p>

<h2>
	الحالة
</h2>

<p>
	ستكون حالة التطبيق كائنًا له الخاصيات <code>picture</code> و<code>tool</code> و<code>color</code>، كما ستكون الصورة نفسها كائنًا يخزِّن العرض والارتفاع ومحتوى البكسل للصورة، في حين تُخزَّن البكسلات في مصفوفة ثنائية عبر طريقة صنف المصفوفة matrix نفسها من مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AD%D9%8A%D8%A7%D8%A9-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%A9-%D9%84%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1243/" rel="">الحياة السرية للكائنات في جافاسكريبت</a> صفًا صفًا من الأعلى حتى الأسفل.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_12" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">Picture</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> pixels</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">height </span><span class="pun">=</span><span class="pln"> height</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pixels </span><span class="pun">=</span><span class="pln"> pixels</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">static</span><span class="pln"> empty</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let pixels </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Array</span><span class="pun">(</span><span class="pln">width </span><span class="pun">*</span><span class="pln"> height</span><span class="pun">).</span><span class="pln">fill</span><span class="pun">(</span><span class="pln">color</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Picture</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> pixels</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  pixel</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pixels</span><span class="pun">[</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> y </span><span class="pun">*</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">width</span><span class="pun">];</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  draw</span><span class="pun">(</span><span class="pln">pixels</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let copy </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pixels</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">();</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let </span><span class="pun">{</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">}</span><span class="pln"> of pixels</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      copy</span><span class="pun">[</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> y </span><span class="pun">*</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">width</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> color</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Picture</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">height</span><span class="pun">,</span><span class="pln"> copy</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نريد التمكّن من معاملة الصورة على أنها قيمة غير قابلة للتغير immutable لأسباب سنعود إليها لاحقًا في هذا المقال، لكن قد نحتاج أحيانًا إلى تحديث مجموعة بكسلات في الوقت نفسه أيضًا، ولكي نفعل ذلك فإن الصنف له تابع <code>draw</code> يتوقع مصفوفةً من البكسلات المحدَّثة، إذ تكون كائنات لها خاصيات <code>x</code> و<code>y</code> و<code>color</code>، كما ينشئ صورةً جديدةً مغيّرًا بها هذه البكسلات، ويستخدِم ذلك التابع <code>slice</code> دون وسائط لنسخ مصفوفة البسكلات كلها، بحيث تكون البداية الافتراضية لـ slice هي 0 والنهاية الافتراضية هي طول المصفوفة.
</p>

<p>
	يستخدِم التابع <code>empty</code> جزأين من وظائف المصفوفة لم نرهما من قبل، فيمكن استدعاء باني <code><a href="https://wiki.hsoub.com/JavaScript/Array" rel="external">Array</a></code> بعدد لإنشاء مصفوفة فارغة بطول محدَّد، ثم يمكن استخدام التابع <code>fill</code> بعدها لملء هذه المصفوفة بقيمة معطاة، وتُستخدَم هذه الأجزاء لإنشاء مصفوفة تحمل كل البكسلات فيه اللون نفسه.
</p>

<p>
	تُخزن الألوان على أساس سلاسل نصية تحتوي على رموز <a href="https://academy.hsoub.com/programming/css/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%A3%D9%84%D9%88%D8%A7%D9%86-%D9%81%D9%8A-css-r255/" rel="">ألوان CSS</a> العادية، وهي التي تبدأ بعلامة الشباك <code>#</code> متبوعة بستة أرقام ست-عشرية، بحيث يكون اثنان فيها للمكون الأحمر واثنان للأخضر واثنان للأزرق، وقد يكون هذا مبهمًا نوعًا ما، لكنها الصيغة التي تستخدمِها <a href="https://wiki.hsoub.com/HTML" rel="external">HTML</a> في حقول ألوانها، حيث يمكن استخدامها في خاصية <code>fillStyle</code> لسياق لوحة رسم، وهي كافية لنا في هذا البرنامج، كما يُكتب اللون الأسود أصفارًا على الصورة <code>‎#000000</code>، ويبدو اللون الوردي الزاهي هكذا <code>‎#ff00ff</code> بحيث تكون مكونات اللونين الأحمر والأزرق لها القيمة العظمى عند 255، فتُكتب <code>ff</code> بالنظام الست-عشري الذي يستخدِم a حتى f لتمثيل الأعداد من 10 حتى 15.
</p>

<p>
	سنسمح للواجهة بإرسال الإجراءات على أساس كائنات لها خاصيات تتجاوز خاصيات الحالة السابقة، ويرسل حقل اللون كائنًا حين يغيره المستخدِم مثل <code>{color: field.value}</code> تحسِب منه دالة التحديث حالةً جديدةً.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_14" style="">
<span class="kwd">function</span><span class="pln"> updateState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُعَدّ استخدام <a href="https://wiki.hsoub.com/JavaScript/Object/assign" rel="external"><code>Object.assign</code></a> لإضافة خصائص <code>state</code> إلى كائن فارغ أولًا ثم تجاوز بعضها بخصائص من <code>action</code>، استخدامًا شائعًا في شيفرات جافاسكربت التي تستخدِم كائنات غير قابلة للتغير على صعوبته في التعامل معه، والأسهل من ذلك استخدام عامِلًا ثلاثي النقاط لتضمين جميع الخصائص من كائن آخر في تعبير الكائن، وذلك لا زال بعد في مراحل اعتماده الأخيرة، وإذا تم فسوف تستطيع كتابة <code>‎{...state, ...action}‎</code> بدلًا من ذلك، لكن هذا لم بثبت عمله بعد في جميع المتصفحات.
</p>

<h2>
	بناء DOM
</h2>

<p>
	أحد الأمور التي تفعلها مكونات الواجهة هي إنشاء هيكل DOM، كما سنقدم نسخة موسعة قليلًا من دالة <code>elt</code> لأننا لا نريد استخدام توابع DOM في ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_19" style="">
<span class="kwd">function</span><span class="pln"> elt</span><span class="pun">(</span><span class="pln">type</span><span class="pun">,</span><span class="pln"> props</span><span class="pun">,</span><span class="pln"> </span><span class="pun">...</span><span class="pln">children</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let dom </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="pln">type</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">props</span><span class="pun">)</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">(</span><span class="pln">dom</span><span class="pun">,</span><span class="pln"> props</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let child of children</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">typeof</span><span class="pln"> child </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"string"</span><span class="pun">)</span><span class="pln"> dom</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">child</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln"> dom</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">createTextNode</span><span class="pun">(</span><span class="pln">child</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> dom</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الفرق الأساسي بين هذه النسخة والتي استخدمناها في مقال <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D9%84%D8%B9%D8%A8%D8%A9-%D9%85%D9%86%D8%B5%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1355/" rel="">مشروع لعبة منصة باستخدام جافاسكربت</a> أنها تسند خاصيات إلى <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D8%B9%D9%82%D8%AF-dom-%D8%A8%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D8%A7%D9%84%D8%B5%D9%86%D9%81-%D9%88%D8%A7%D9%84%D9%88%D8%B3%D9%85-%D9%88%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%8A%D8%A7%D8%AA-r1107/" rel="">عقد DOM</a> وليس سمات، ويعني هذا أننا لا نستطيع استخدامها لضبط سمات عشوائية، لكن نستطيع استخدامها لضبط خاصيات قيمها ليست سلاسل نصية مثل <code>onclick</code> والتي يمكن تعيينها إلى دالة لتسجيل معالج حدث نقرة، وهذا يسمح بالنمط التالي من تسجيل معالِجات الأحداث:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_21" style="">
<span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
    document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">elt</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onclick</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="str">"The button"</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span></pre>

<h2>
	اللوحة Canvas
</h2>

<p>
	يُعَدّ جزء الواجهة الذي يعرض الصورة على أساس شبكة من الصناديق المربعة المكوّن الأول الذي سنعرِّفه، وهذا الجزء مسؤول عن أمرين فقط هما عرض showing الصورة وتوصيل أحداث المؤشر التي على هذه الصورة إلى بقية التطبيق، وبالتالي يمكننا تعريفه على أساس مكوِّن لا يطلع إلا على الصورة الحالية وليس له شأن بحالة التطبيق كاملًا، كما لا يستطيع إرسال إجراءات مباشرةً لأنه لا يعرف كيف يعمل التطبيق، وأنما يستدعي <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%82%D8%AF%D9%85%D8%A9-%D8%A5%D9%84%D9%89-%D8%B1%D8%AF%D9%88%D8%AF-%D8%A7%D9%84%D9%86%D8%AF%D8%A7%D8%A1-callbacks-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r914/" rel="">دالة رد نداء callback function</a> توفرها الشيفرة التي أنشأته حين يستجيب لأحداث المؤشر بحيث تعالِج أجزاء التطبيق على حدة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_23" style="">
<span class="kwd">const</span><span class="pln"> scale </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">class</span><span class="pln"> </span><span class="typ">PictureCanvas</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> pointerDown</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onmousedown</span><span class="pun">:</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">mouse</span><span class="pun">(</span><span class="pln">event</span><span class="pun">,</span><span class="pln"> pointerDown</span><span class="pun">),</span><span class="pln">
      ontouchstart</span><span class="pun">:</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">touch</span><span class="pun">(</span><span class="pln">event</span><span class="pun">,</span><span class="pln"> pointerDown</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">picture</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  syncState</span><span class="pun">(</span><span class="pln">picture</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture </span><span class="pun">==</span><span class="pln"> picture</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture </span><span class="pun">=</span><span class="pln"> picture</span><span class="pun">;</span><span class="pln">
    drawPicture</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنرسم كل بكسل على أساس مربع بعداه 10*10 كما هو مُحدَّد في ثابت <code>scale</code>، ثم يحتفظ المكوِّن بصورته الحالية ولا يعيد الرسم إلا حين تُعطى <code>syncState</code> صورةً جديدةً، كما تضبط دالة الرسم الفعلية حجم اللوحة وفقًا لمقياس الصورة وحجمها، ثم تملؤها بسلسلة من المربعات يمثِّل كل منها بكسلًا واحدًا.
</p>

<pre class="ipsCode">
function drawPicture(picture, canvas, scale) {
  canvas.width = picture.width * scale;
  canvas.height = picture.height * scale;
  let cx = canvas.getContext("2d");

  for (let y = 0; y &lt; picture.height; y++) {
    for (let x = 0; x &lt; picture.width; x++) {
      cx.fillStyle = picture.pixel(x, y);
      cx.fillRect(x * scale, y * scale, scale, scale);
    }
  }
}
</pre>

<p>
	إذا ضغطنا زر الفأرة الأيسر أثناء وجود الفأرة فوق لوحة الصورة فيستدعي المكون رد نداء <code>pointerDown</code> ليعطيه موضع البكسل الذي تم النقر عليه في إحداثيات الصورة، حيث سيُستخدم هذا لتنفيذ تفاعل الفأرة مع الصورة، وقد يعيد رد النداء دالة رد نداء أخرى لتُنبَّه بإشعار حين يتحرك المؤشر إلى بكسل آخر أثناء الضغط على الزر.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_25" style="">
<span class="typ">PictureCanvas</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">mouse </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">downEvent</span><span class="pun">,</span><span class="pln"> onDown</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">downEvent</span><span class="pun">.</span><span class="pln">button </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  let pos </span><span class="pun">=</span><span class="pln"> pointerPosition</span><span class="pun">(</span><span class="pln">downEvent</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
  let onMove </span><span class="pun">=</span><span class="pln"> onDown</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">onMove</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  let move </span><span class="pun">=</span><span class="pln"> moveEvent </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">moveEvent</span><span class="pun">.</span><span class="pln">buttons </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">removeEventListener</span><span class="pun">(</span><span class="str">"mousemove"</span><span class="pun">,</span><span class="pln"> move</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let newPos </span><span class="pun">=</span><span class="pln"> pointerPosition</span><span class="pun">(</span><span class="pln">moveEvent</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">==</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&amp;&amp;</span><span class="pln"> newPos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">==</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
      pos </span><span class="pun">=</span><span class="pln"> newPos</span><span class="pun">;</span><span class="pln">
      onMove</span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"mousemove"</span><span class="pun">,</span><span class="pln"> move</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> pointerPosition</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> domNode</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let rect </span><span class="pun">=</span><span class="pln"> domNode</span><span class="pun">.</span><span class="pln">getBoundingClientRect</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">{</span><span class="pln">x</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">((</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">clientX </span><span class="pun">-</span><span class="pln"> rect</span><span class="pun">.</span><span class="pln">left</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> scale</span><span class="pun">),</span><span class="pln">
          y</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">((</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">clientY </span><span class="pun">-</span><span class="pln"> rect</span><span class="pun">.</span><span class="pln">top</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> scale</span><span class="pun">)};</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	بما أننا نعرف حجم البكسلات ونستطيع استخدام <code>getBoundingClientRect</code> في إيجاد موضع اللوحة على الشاشة، فمن الممكن الذهاب من إحداثيات حدث الفأرة <code>clientX</code> و<code>clientY</code> إلى إحداثيات الصورة، وتُقرَّب هذه دومًا كي تشير إلى بكسل بعينه؛ أما بالنسبة لأحداث اللمس فيتوجب علينا فعل شيء قريب من ذلك لكن باستخدام أحداث مختلفة والتأكد أننا نستدعي <code>preventDefault</code> على حدث <code>"touchstart"</code> لمنع التمرير العمودي أو الأفقي panning.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_27" style="">
<span class="typ">PictureCanvas</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">touch </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">startEvent</span><span class="pun">,</span><span class="pln">
                                         onDown</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let pos </span><span class="pun">=</span><span class="pln"> pointerPosition</span><span class="pun">(</span><span class="pln">startEvent</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
  let onMove </span><span class="pun">=</span><span class="pln"> onDown</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">);</span><span class="pln">
  startEvent</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">onMove</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  let move </span><span class="pun">=</span><span class="pln"> moveEvent </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let newPos </span><span class="pun">=</span><span class="pln"> pointerPosition</span><span class="pun">(</span><span class="pln">moveEvent</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln">
                                 </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">==</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&amp;&amp;</span><span class="pln"> newPos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">==</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    pos </span><span class="pun">=</span><span class="pln"> newPos</span><span class="pun">;</span><span class="pln">
    onMove</span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
  let end </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">removeEventListener</span><span class="pun">(</span><span class="str">"touchmove"</span><span class="pun">,</span><span class="pln"> move</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">removeEventListener</span><span class="pun">(</span><span class="str">"touchend"</span><span class="pun">,</span><span class="pln"> end</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"touchmove"</span><span class="pun">,</span><span class="pln"> move</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"touchend"</span><span class="pun">,</span><span class="pln"> end</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	لا تكون أحداث <code>clientX</code> و<code>clientY</code> متاحةً مباشرةً لأحداث اللمس على كائن الحدث، لكن نستطيع استخدام إحداثيات كائن اللمس الأول في خاصية <code>touches</code>.
</p>

<h2>
	التطبيق
</h2>

<p>
	سننفذ المكون الأساسي على أساس صدَفة حول لوحة الصورة كي نبني التطبيق جزءًا جزءًا مع مجموعة من الأدوات والمتحكمات التي نمررها لبانيه، كما ستكون المتحكمات عناصر الواجهة التي ستظهر تحت الصورة، وستكون متاحةً في صورة مصفوفة من بواني المكونات.
</p>

<p>
	تفعل الأدوات مهامًا مثل رسم البكسلات أو ملء مساحة ما، ويعرض التطبيق مجموعةً من الأدوات المتاحة مثل حقل <code>&lt;select&gt;</code>، كما تحدِّد الأداة المختارة حاليًا ما يحدث عندما يتفاعل المستخدِم مع الصورة بأداة تأشير مثل الفأرة، وتوفَّر مجموعة من الأدوات المتاحة على أساس كائن ينظِّم الأسماء التي تظهر في الحقل المنسدل للدوال التي تنفِّذ الأدوات، كما تحصل مثل هذه الدوال على موضع الصورة وحالة التطبيق الحالية ودالة <code>dispatch</code> في هيئة وسائط، وقد تعيد دالة معالِج حركة move handler تُستدعى مع موضع جديد وحالة حالية عندما يتحرك المؤشر إلى بكسل جديد.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_29" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">PixelEditor</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> config</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let </span><span class="pun">{</span><span class="pln">tools</span><span class="pun">,</span><span class="pln"> controls</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> config</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PictureCanvas</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> pos </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let tool </span><span class="pun">=</span><span class="pln"> tools</span><span class="pun">[</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state</span><span class="pun">.</span><span class="pln">tool</span><span class="pun">];</span><span class="pln">
      let onMove </span><span class="pun">=</span><span class="pln"> tool</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">onMove</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> pos </span><span class="pun">=&gt;</span><span class="pln"> onMove</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">controls </span><span class="pun">=</span><span class="pln"> controls</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">
      </span><span class="typ">Control</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Control</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> config</span><span class="pun">));</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{},</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">,</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"br"</span><span class="pun">),</span><span class="pln">
                   </span><span class="pun">...</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">controls</span><span class="pun">.</span><span class="pln">reduce</span><span class="pun">(</span><span class="pln">
                     </span><span class="pun">(</span><span class="pln">a</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">concat</span><span class="pun">(</span><span class="str">" "</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">),</span><span class="pln"> </span><span class="pun">[]));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let ctrl of </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">controls</span><span class="pun">)</span><span class="pln"> ctrl</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يستدعي معالج المؤشر المعطى لـ <code>PictureCanvas</code> الأداة المختارة حاليًا باستخدام الوسائط المناسبة، وإذا أعاد معالج حركة فسيكيّفه ليستقبل الحالة، وتُنشأ جميع المتحكمات وتُخزَّن في <code>this.controls</code> كي يمكن تحديثها حين تتغير حالة التطبيق، ويدخل استدعاء <code>reduce</code> مسافات بين عناصر متحكمات <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%B4%D8%AC%D8%B1%D8%A9-dom-%D9%84%D8%AA%D8%B9%D8%AF%D9%8A%D9%84%D9%87%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1104/" rel="">DOM</a> كي لا تبدو هذه العناصر مكثَّفة بجانب بعضها، كما تُعَدّ قائمة اختيار الأدوات أول متحكم، وتنشئ عنصر <code>&lt;select&gt;</code> مع خيار لكل أداة وتضبط معالِج حدث <code>"change"</code> الذي يحدِّث حالة التطبيق حين يختار المستخدِم أداةً مختلفةً.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_31" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">ToolSelect</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">tools</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">select </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"select"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onchange</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> dispatch</span><span class="pun">({</span><span class="pln">tool</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">select</span><span class="pun">.</span><span class="pln">value</span><span class="pun">})</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="pun">...</span><span class="typ">Object</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">(</span><span class="pln">tools</span><span class="pun">).</span><span class="pln">map</span><span class="pun">(</span><span class="pln">name </span><span class="pun">=&gt;</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"option"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      selected</span><span class="pun">:</span><span class="pln"> name </span><span class="pun">==</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">tool
    </span><span class="pun">},</span><span class="pln"> name</span><span class="pun">)));</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"label"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> </span><span class="str">"? Tool: "</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">select</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">select</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">tool</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	حين نغلِّف نص العنوان label text والحقل داخل عنصر <a href="https://wiki.hsoub.com/HTML/label" rel="external"><code>&lt;label&gt;</code></a> فإننا نخبر المتصفح أن العنوان ينتمي إلى هذا الحقل كي تستطيع النقر مثلًا على العنوان لتنشيط الحقل، كذلك نحتاج إلى إمكانية تغيير اللون، لذا سنضيف متحكمًا لهذا وهو عنصر <a href="https://wiki.hsoub.com/HTML/input" rel="external"><code>&lt;input&gt;</code></a> من HTML مع سمة <code>type</code> لـ <code>color</code>، بحيث تعطينا حقل استمارة مخصص لاختيار الألوان، وقيمةً مثل هذا الحقل تكون دائمًا رمز لون <a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">CSS</a> بصيغة <code>‎"#RRGGBB"‎</code> أي الأحمر ثم الأخضر ثم الأزرق بمعنى رقمين لكل لون، وسيعرض المتصفح واجهة مختار الألوان color picker عندما يتفاعل المستخدِم معها، كما ينشئ هذا المتحكم مثل ذلك الحقل ويربطه ليكون متزامنًا مع خاصية <code>color</code> الخاصة بحالة التطبيق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_33" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">ColorSelect</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">dispatch</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">input </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"color"</span><span class="pun">,</span><span class="pln">
      value</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">color</span><span class="pun">,</span><span class="pln">
      onchange</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> dispatch</span><span class="pun">({</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">input</span><span class="pun">.</span><span class="pln">value</span><span class="pun">})</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"label"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> </span><span class="str">"? Color: "</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">input</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">input</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">color</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	أدوات الرسم
</h2>

<p>
	نحتاج قبل رسم أي شيء إلى تنفيذ الأدوات التي ستتحكم في وظائف الفأرة وأحداث اللمس على اللوحة، وأبسط أداة هي أداة الرسم التي تغير أيّ بكسل تنقر عليه أو تلمسه بإصبعك إلى اللون الحالي، وترسل إجراءً يحدِّث الصورة إلى إصدار يُعطى فيه البكسل المشار إليه اللون المختار الحالي.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_35" style="">
<span class="kwd">function</span><span class="pln"> draw</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> drawPixel</span><span class="pun">({</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">},</span><span class="pln"> state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let drawn </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">color</span><span class="pun">};</span><span class="pln">
    dispatch</span><span class="pun">({</span><span class="pln">picture</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">draw</span><span class="pun">([</span><span class="pln">drawn</span><span class="pun">])});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  drawPixel</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> drawPixel</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تستدعي الدالة فورًا دالة <code>drawPixel</code> ثم تعيدها كي تُستدعى مرةً أخرى من أجل البكسلات التي ستُلمَس لاحقًا حين يسحب المستخدِم إصبعه أو يمرره على الصورة، ولكي نرسم أشكالًا أكبر فمن المفيد إنشاء مستطيلات بسرعة، كما ترسم أداة <code>rectangle</code> مستطيلًا بين النقطة التي تبدأ السحب منها حتى النقطة التي تترك فيها المؤشر أو ترفع إصبعك.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_37" style="">
<span class="kwd">function</span><span class="pln"> rectangle</span><span class="pun">(</span><span class="pln">start</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> drawRectangle</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let xStart </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="pln">start</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">x</span><span class="pun">);</span><span class="pln">
    let yStart </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="pln">start</span><span class="pun">.</span><span class="pln">y</span><span class="pun">,</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span><span class="pln">
    let xEnd </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(</span><span class="pln">start</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">x</span><span class="pun">);</span><span class="pln">
    let yEnd </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(</span><span class="pln">start</span><span class="pun">.</span><span class="pln">y</span><span class="pun">,</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span><span class="pln">
    let drawn </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let y </span><span class="pun">=</span><span class="pln"> yStart</span><span class="pun">;</span><span class="pln"> y </span><span class="pun">&lt;=</span><span class="pln"> yEnd</span><span class="pun">;</span><span class="pln"> y</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let x </span><span class="pun">=</span><span class="pln"> xStart</span><span class="pun">;</span><span class="pln"> x </span><span class="pun">&lt;=</span><span class="pln"> xEnd</span><span class="pun">;</span><span class="pln"> x</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        drawn</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">color</span><span class="pun">});</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    dispatch</span><span class="pun">({</span><span class="pln">picture</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">draw</span><span class="pun">(</span><span class="pln">drawn</span><span class="pun">)});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  drawRectangle</span><span class="pun">(</span><span class="pln">start</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> drawRectangle</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	هناك تفصيل مهم في هذا التنفيذ، وهو أنك حين تسحب المؤشر سيعاد رسم المستطيل على الصورة من الحالة الأصلية، وهكذا تستطيع جعل المستطيل أكبر أو أصغر مرةً أخرى أثناء إنشائه دون مستطيل وسيط يتبقى في الصورة النهائية، وهذا أحد الأسباب التي تجعل كائنات الصورة غير القابلة للتغيّر مفيدةً، كما سننظر في سبب آخر لاحقًا، وسيكون تنفيذ مهمة ملء اللون أكثر تفصيلًا، إذ هي أداة تملأ البكسل الذي تحت المؤشر والبكسلات المجاورة له التي لها اللون نفسه، وإنما نعني بالمجاورة له تلك البكسلات المجاورة رأسيًا أو عموديًا مباشرةً وليس البكسلات المجاورة قطريًا له، كما توضِّح الصورة التالية مجموعة بكسلات تُلوَّن باستخدام أداة الملء على البكسل الذي يحمل النقطة الحمراء.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85693" href="https://academy.hsoub.com/uploads/monthly_2021_12/flood-grid.png.36dada32e2bbf66814234c7e8edf4ca0.png" rel=""><img alt="flood-grid.png" class="ipsImage ipsImage_thumbnailed" data-fileid="85693" data-unique="9rwo8yl1o" src="https://academy.hsoub.com/uploads/monthly_2021_12/flood-grid.png.36dada32e2bbf66814234c7e8edf4ca0.png"></a>
</p>

<p>
	من المثير أن الطريقة التي نفعل بها ذلك تشبه شيفرة الاستكشاف التي تعرضنا لها في مقال <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%8A-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D8%B1%D8%AC%D9%84-%D8%A2%D9%84%D9%8A-%D8%B1%D9%88%D8%A8%D9%88%D8%AA-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1244/" rel="">مشروع تطبيقي لبناء رجل آلي (روبوت) عبر جافاسكريبت</a>، حيث بحثت تلك الشيفرة في مخطط لإيجاد طريق ما للروبوت، وتبحث هذه الشيفرة في شبكة لإيجاد كل البكسلات المرتبطة ببعضها بعضًا، لكن مشكلة تتبع مجموعة الفروع الممكنة مشابهةً هنا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_39" style="">
<span class="kwd">const</span><span class="pln"> around </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="pln">dx</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> dy</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">},</span><span class="pln"> </span><span class="pun">{</span><span class="pln">dx</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> dy</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">},</span><span class="pln">
                </span><span class="pun">{</span><span class="pln">dx</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> dy</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">},</span><span class="pln"> </span><span class="pun">{</span><span class="pln">dx</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> dy</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">}];</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> fill</span><span class="pun">({</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">},</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let targetColor </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">pixel</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">);</span><span class="pln">
  let drawn </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[{</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">color</span><span class="pun">}];</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let done </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> done </span><span class="pun">&lt;</span><span class="pln"> drawn</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> done</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let </span><span class="pun">{</span><span class="pln">dx</span><span class="pun">,</span><span class="pln"> dy</span><span class="pun">}</span><span class="pln"> of around</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let x </span><span class="pun">=</span><span class="pln"> drawn</span><span class="pun">[</span><span class="pln">done</span><span class="pun">].</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> dx</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> drawn</span><span class="pun">[</span><span class="pln">done</span><span class="pun">].</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> dy</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">width </span><span class="pun">&amp;&amp;</span><span class="pln">
          y </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> y </span><span class="pun">&lt;</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">height </span><span class="pun">&amp;&amp;</span><span class="pln">
          state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">pixel</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> targetColor </span><span class="pun">&amp;&amp;</span><span class="pln">
          </span><span class="pun">!</span><span class="pln">drawn</span><span class="pun">.</span><span class="pln">some</span><span class="pun">(</span><span class="pln">p </span><span class="pun">=&gt;</span><span class="pln"> p</span><span class="pun">.</span><span class="pln">x </span><span class="pun">==</span><span class="pln"> x </span><span class="pun">&amp;&amp;</span><span class="pln"> p</span><span class="pun">.</span><span class="pln">y </span><span class="pun">==</span><span class="pln"> y</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        drawn</span><span class="pun">.</span><span class="pln">push</span><span class="pun">({</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">color</span><span class="pun">});</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  dispatch</span><span class="pun">({</span><span class="pln">picture</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">draw</span><span class="pun">(</span><span class="pln">drawn</span><span class="pun">)});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تتصرف مصفوفة البكسلات المرسومة على أساس قائمة العمل للدالة، فيجب علينا من أجل كل بكسل نصل إليه رؤية إذا كان أيّ بكسل مجاور له يحمل اللون نفسه ولم يُدهن مسبقًا، وتتأخر حلقة العد التكرارية خلف طول مصفوفة <code>drawn</code> بسبب إضافة البكسلات الجديدة، كما سيحتاج أيّ بكسل يسبقها إلى استكشافه، وحين تلحق بالطول فستكون كل البكسلات قد استُكشِفت وقد أتمت الدالة عملها؛ أما الأداة النهائية فهي مختار الألوان color picker الذي يسمح لك بالإشارة إلى لون في الصورة لاستخدامه على أساس لون الرسم الحالي.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_41" style="">
<span class="kwd">function</span><span class="pln"> pick</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  dispatch</span><span class="pun">({</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">pixel</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">)});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نستطيع الآن اختبار التطبيق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_43" style="">
<span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let state </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    tool</span><span class="pun">:</span><span class="pln"> </span><span class="str">"draw"</span><span class="pun">,</span><span class="pln">
    color</span><span class="pun">:</span><span class="pln"> </span><span class="str">"#000000"</span><span class="pun">,</span><span class="pln">
    picture</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Picture</span><span class="pun">.</span><span class="pln">empty</span><span class="pun">(</span><span class="lit">60</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="str">"#f0f0f0"</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
  let app </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PixelEditor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    tools</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">draw</span><span class="pun">,</span><span class="pln"> fill</span><span class="pun">,</span><span class="pln"> rectangle</span><span class="pun">,</span><span class="pln"> pick</span><span class="pun">},</span><span class="pln">
    controls</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="typ">ToolSelect</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ColorSelect</span><span class="pun">],</span><span class="pln">
    dispatch</span><span class="pun">(</span><span class="pln">action</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      state </span><span class="pun">=</span><span class="pln"> updateState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">);</span><span class="pln">
      app</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">).</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">app</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	الحفظ والتحميل
</h2>

<p>
	لا شك أننا حين نرسم الصورة الخاصة بنا سنود حفظها لاحقًا، كما يجب إضافة زر لتحميل الصورة الحالية على أساس ملف صورة، حيث يوفر المتحكم التالي هذا الزر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_45" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">SaveButton</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onclick</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">save</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="str">"? Save"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  save</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let canvas </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">);</span><span class="pln">
    drawPicture</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> canvas</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">
    let link </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"a"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      href</span><span class="pun">:</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">toDataURL</span><span class="pun">(),</span><span class="pln">
      download</span><span class="pun">:</span><span class="pln"> </span><span class="str">"pixelart.png"</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">link</span><span class="pun">);</span><span class="pln">
    link</span><span class="pun">.</span><span class="pln">click</span><span class="pun">();</span><span class="pln">
    link</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يتتبّع المكون الصورة الحالية ليستطيع الوصول إليها عند الحفظ، كما يستخدِم عنصر <code>&lt;canvas&gt;</code> لإنشاء ملف الصورة والذي يرسم الصورة على مقياس بكسل واحد لكل بكسل، في حين ينشئ التابع <code>toDataURL</code> الذي على عنصر اللوحة رابطًا يبدأ بـ <code>‎data:‎</code> على عكس الروابط التي تبدأ ببروتوكولات <code>http:‎</code> و<code>https:‎</code> العادية، تحتوي هذه الروابط على المصدر كاملًا في الرابط، كما تكون طويلةً جدًا لهذا، لكنه يسمح لنا بإنشاء روابط عاملة إلى صور عشوائية من داخل المتصفح.
</p>

<p>
	ننشئ عنصر رابط للوصول إلى المتصفح وتحميل الصورة يشير إلى هذا الرابط وله سمة <code>download</code>، وعندما يُنقَر على مثل هذه الروابط فستجعل المتصفح يعرض صندوقًا حواريًا لحفظ الملف، كما نضيف ذلك الرابط إلى المستند ونحاكي النقر عليه ثم نحذفه مرةً أخرى، وهكذا تستطيع فعل الكثير والكثير باستخدام التقنيات المتاحة في المتصفح لكن قد تبدو بعض هذه التقنيات غريبةً، بل إذا أردنا أن نكون قادرين على تحميل ملفات صورة موجودة إلى تطبيقنا، فسنحتاج إلى تعريف مكوِّن لزر، أي كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_47" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">LoadButton</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">_</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">dispatch</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onclick</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> startLoad</span><span class="pun">(</span><span class="pln">dispatch</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="str">"? Load"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  syncState</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> startLoad</span><span class="pun">(</span><span class="pln">dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let input </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    type</span><span class="pun">:</span><span class="pln"> </span><span class="str">"file"</span><span class="pun">,</span><span class="pln">
    onchange</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> finishLoad</span><span class="pun">(</span><span class="pln">input</span><span class="pun">.</span><span class="pln">files</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">input</span><span class="pun">);</span><span class="pln">
  input</span><span class="pun">.</span><span class="pln">click</span><span class="pun">();</span><span class="pln">
  input</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا أردنا الوصول إلى ملف في حاسوب المستخدِم، فسيكون على المستخدِم اختيار الملف من حقل إدخال الملف، لكنّا لا نريد أن يبدو زر التحميل مثل حقل إدخال ملف، لذا سننشئ إدخال الملف عندما يُنقر على الزر ونتظاهر حينها أنّ إدخال الملف ذاك قد نُقر عليه، فإذا اختار المستخدِم ملفًا، فسنستطيع استخدام <code>FileReader</code> للوصول إلى محتوياته في صورة رابط بيانات كما ذكرنا قبل قليل، ويمكن استخدام هذا الرابط لإنشاء عنصر <code>&lt;img&gt;</code>، لكن بسبب أننا لا نستطيع الوصول مباشرةً إلى البكسلات في مثل هذه الصورة فلا نستطيع إنشاء كائن <code>Picture</code> منها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_49" style="">
<span class="kwd">function</span><span class="pln"> finishLoad</span><span class="pun">(</span><span class="pln">file</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">file </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  let reader </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileReader</span><span class="pun">();</span><span class="pln">
  reader</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"load"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let image </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onload</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> dispatch</span><span class="pun">({</span><span class="pln">
        picture</span><span class="pun">:</span><span class="pln"> pictureFromImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln">
      </span><span class="pun">}),</span><span class="pln">
      src</span><span class="pun">:</span><span class="pln"> reader</span><span class="pun">.</span><span class="pln">result
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  reader</span><span class="pun">.</span><span class="pln">readAsDataURL</span><span class="pun">(</span><span class="pln">file</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يجب علينا رسم الصورة أولًا في عنصر <code>&lt;canvas&gt;</code> كي نصل إلى البكسلات، كما يملك سياق اللوحة canvas التابع <code>getImageData</code> الذي يسمح للسكربت قراءة بكسلاتها، لذا بمجرد أن تكون الصورة على اللوحة يمكننا الوصول إليها وبناء كائن <code>Picture</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_51" style="">
<span class="kwd">function</span><span class="pln"> pictureFromImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let width </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">width</span><span class="pun">);</span><span class="pln">
  let height </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">.</span><span class="pln">height</span><span class="pun">);</span><span class="pln">
  let canvas </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">});</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">image</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  let pixels </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">
  let </span><span class="pun">{</span><span class="pln">data</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> cx</span><span class="pun">.</span><span class="pln">getImageData</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> hex</span><span class="pun">(</span><span class="pln">n</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> n</span><span class="pun">.</span><span class="pln">toString</span><span class="pun">(</span><span class="lit">16</span><span class="pun">).</span><span class="pln">padStart</span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="str">"0"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let </span><span class="pun">[</span><span class="pln">r</span><span class="pun">,</span><span class="pln"> g</span><span class="pun">,</span><span class="pln"> b</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="pln">i</span><span class="pun">,</span><span class="pln"> i </span><span class="pun">+</span><span class="pln"> </span><span class="lit">3</span><span class="pun">);</span><span class="pln">
    pixels</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="str">"#"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> hex</span><span class="pun">(</span><span class="pln">r</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> hex</span><span class="pun">(</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> hex</span><span class="pun">(</span><span class="pln">b</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Picture</span><span class="pun">(</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln"> pixels</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	سنحدّ من حجم الصور إلى أن تكون 100 * 100 بكسل، بما أن أي شيء أكبر من هذا سيكون أكبر من أن يُعرض على الشاشة وقد يبطئ الواجهة، كما تكون خاصية <code>data</code> الخاصة بالكائن الذي يعيده <code>getImageData</code> مصفوفةً من مكونات الألوان، إذ تحتوي على أربع قيم لكل بكسل في المستطيل الذي تحدده الوسائط، حيث تمثل مكونات البكسل اللونية من الأحمر والأخضر والأزرق والشفافية alpha، كما تكون هذه المكونات أرقامًا تتراوح بين الصفر و255، ويعني الصفر في خانة الألفا أنه شفاف تمامًا و255 أنه مصمت، لكن سنتجاهل هذا في مثالنا إذ لا يهمنا كثيرًا.
</p>

<p>
	يتوافق كل رقمين ست-عشريين لكل مكوِّن مستخدَم في ترميزنا للألوان توافقًا دقيقًا للمجال الذي يتراوح بين الصفر و255، حيث يستطيع هذان الرقمان التعبير عن ‎16<sup>2</sup>‎ = 256 عددًا، كما يمكن إعطاء القاعدة إلى التابع <code>toString</code> الخاص بالأعداد على أساس وسيط كي ينتج <code>n.toString(16)‎</code> تمثيلًا من سلسلة نصية في النظام الست عشري، ويجب التأكد من أنّ كل عدد يأخذ رقمين فقط، لذلك فإن الدالة المساعِدة <code>hex</code> تستدعي <code>padStart</code> لإضافة صفر بادئ عند الحاجة، ونستطيع الآن التحميل والحفظ ولم يبق إلا ميزة إضافية واحدة.
</p>

<h2>
	سجل التغييرات Undo History
</h2>

<p>
	ستكون نصف عملية التعديل على الصور بارتكاب أخطاء صغيرة بين الحين والآخر ثم تصحيحها، لذا من المهم لنا وجود سجل للخطوات التي ينفذها المستخدِم كي يستطيع العودة إليها وتصحيح ما يريده، حيث سنحتاج لهذا تخزين النسخ السابقة من الصورة، وهو أمر يسير بما أنها قيمة غير قابلة للتغيّر لكنها تحتاج حقلًا إضافيًا داخل حالة التطبيق.
</p>

<p>
	سنضيف مصفوفة <code>done</code> للحفاظ على النسخ السابقة من الصورة، كما سيتطلب الحفاظ على هذه الخاصية دالةً معقدةً لتحديث الحالة بحيث تضيف الصورة إلى المصفوفة، لكن لا نريد تخزين كل تغيير يحدث، وإنما التغييرات التي تحدث كل فترة زمنية محدَّدة، لذا سنحتاج إلى خاصية ثانية هي <code>doneAt</code> تتتبّع آخر وقت حفظنا فيه صورة في السجل.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_53" style="">
<span class="kwd">function</span><span class="pln"> historyUpdateState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">undo </span><span class="pun">==</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">done</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      picture</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">done</span><span class="pun">[</span><span class="lit">0</span><span class="pun">],</span><span class="pln">
      done</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">done</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">1</span><span class="pun">),</span><span class="pln">
      doneAt</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">action</span><span class="pun">.</span><span class="pln">picture </span><span class="pun">&amp;&amp;</span><span class="pln">
             state</span><span class="pun">.</span><span class="pln">doneAt </span><span class="pun">&lt;</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      done</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[</span><span class="pln">state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> </span><span class="pun">...</span><span class="pln">state</span><span class="pun">.</span><span class="pln">done</span><span class="pun">],</span><span class="pln">
      doneAt</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا كان الإجراء هو إجراء تراجع undo، فستأخذ الدالة آخر صورة من السجل وتجعلها هي الصورة الحالية، حيث تضبط <code>doneِAt</code> على صفر كي نضمن أن التغيير التالي يخزِّن الصورة في السجل مما يسمح لك بالعودة إليها في وقت آخر إذا أردت؛ أما إن كان الإجراء غير ذلك ويحتوي على صورة جديدة وكان آخر وقت تخزين صورة أكثر من ثانية واحدة -أي أكثر من 1000 ميلي ثانية-، فستُحدَّث خصائص <code>done</code> و<code>doneAt</code> لتخزين الصورة السابقة، كما لا يفعل مكوّن زر التراجع الكثير، إذ يرسِل إجراءات التراجع عند النقر عليه ويعطِّل نفسه إذا لم يكن ثمة شيء يُتراجع عنه.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_55" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">UndoButton</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">dispatch</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      onclick</span><span class="pun">:</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> dispatch</span><span class="pun">({</span><span class="pln">undo</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">}),</span><span class="pln">
      disabled</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">done</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="str">"⮪ Undo"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">disabled </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">done</span><span class="pun">.</span><span class="pln">length </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	لنرسم
</h2>

<p>
	نحتاج أولًا إلى إنشاء حالة كي نستطيع استخدام التطبيق مع مجموعة من الأدوات والمتحكمات ودالة إرسال، ثم نمرر إليها باني <code>PixelEditor</code> لإنشاء المكوِّن الأساسي، وبما أننا نحتاج إلى إنشاء عدة محررات في التدريبات، فسنعرِّف بعض الرابطات bindings أولًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_57" style="">
<span class="kwd">const</span><span class="pln"> startState </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  tool</span><span class="pun">:</span><span class="pln"> </span><span class="str">"draw"</span><span class="pun">,</span><span class="pln">
  color</span><span class="pun">:</span><span class="pln"> </span><span class="str">"#000000"</span><span class="pun">,</span><span class="pln">
  picture</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Picture</span><span class="pun">.</span><span class="pln">empty</span><span class="pun">(</span><span class="lit">60</span><span class="pun">,</span><span class="pln"> </span><span class="lit">30</span><span class="pun">,</span><span class="pln"> </span><span class="str">"#f0f0f0"</span><span class="pun">),</span><span class="pln">
  done</span><span class="pun">:</span><span class="pln"> </span><span class="pun">[],</span><span class="pln">
  doneAt</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> baseTools </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">draw</span><span class="pun">,</span><span class="pln"> fill</span><span class="pun">,</span><span class="pln"> rectangle</span><span class="pun">,</span><span class="pln"> pick</span><span class="pun">};</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> baseControls </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="typ">ToolSelect</span><span class="pun">,</span><span class="pln"> </span><span class="typ">ColorSelect</span><span class="pun">,</span><span class="pln"> </span><span class="typ">SaveButton</span><span class="pun">,</span><span class="pln"> </span><span class="typ">LoadButton</span><span class="pun">,</span><span class="pln"> </span><span class="typ">UndoButton</span><span class="pln">
</span><span class="pun">];</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> startPixelEditor</span><span class="pun">({</span><span class="pln">state </span><span class="pun">=</span><span class="pln"> startState</span><span class="pun">,</span><span class="pln">
                           tools </span><span class="pun">=</span><span class="pln"> baseTools</span><span class="pun">,</span><span class="pln">
                           controls </span><span class="pun">=</span><span class="pln"> baseControls</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let app </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PixelEditor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    tools</span><span class="pun">,</span><span class="pln">
    controls</span><span class="pun">,</span><span class="pln">
    dispatch</span><span class="pun">(</span><span class="pln">action</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      state </span><span class="pun">=</span><span class="pln"> historyUpdateState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">);</span><span class="pln">
      app</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> app</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تستطيع استخدام <code>=</code> بعد اسم الرابطة حين نفكك كائنًا أو مصفوفةً كي تعطي الرابطة قيمةً افتراضيةً تُستخدَم عندما تكون الخاصية مفقودةً أو تحمل قيمة غير معرفة <code>undefined</code>، كما تستفيد دالة <code>StartPixelEditor</code> من هذا في قبول كائن له عدد من الخصائص الاختيارية على أساس وسيط، فإذا لم توفر خاصية <code>tools</code>، فستكون مقيدةً إلى <code>baseTools</code>، وتوضِّح الشيفرة التالية كيفية الحصول على محرر حقيقي على الشاشة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_59" style="">
<span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">startPixelEditor</span><span class="pun">({}));</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تستطيع الآن الرسم فيه إذا شئت.
</p>

<h2>
	سبب صعوبة البرنامج
</h2>

<p>
	لا شك أنّ التقنيات المتعلقة بالمتصفحات رائعة وتمكننا من فعل الكثير بالواجهات المرئية من بنائها وتعديلها بل وتنقيحها من الأخطاء البرمجية كذلك، كما ستضمن أنّ البرنامج الذي تكتبه من أجل المتصفح سيعمل على كل حاسوب وهاتف يتصل بالإنترنت، لكن تُعَدّ تقنيات المتصفحات هذه بحارًا واسعةً، إذ عليك تعلّم الكثير الكثير من الطرق والأدوات لتستطيع الاستفادة منها، كما أنّ النماذج البرمجية الافتراضية المتاحة لها كثيرة المشاكل إلى حد أن أغلب المبرمجين يفضلون التعامل مع طبقات مجرَّدة عليها عوضًا عن التعامل المباشر معها، وعلى الرغم من اتجاه الوضع نحو الأفضل، إلا أن هذا يكون في صورة إضافة مزيد من العناصر لحل المشاكل وأوجه القصور الموجودة مما يخلق المزيد من التعقيد.
</p>

<p>
	لا يمكن استبدال الميزة المستخدَمة من قِبل ملايين المواقع بقرار واحد بسهولة، بل حتى لو أمكن ذلك فمن الصعب الاتفاق على بديلها، ونحن مقيدون بأدواتنا والعوامل الاجتماعية والاقتصادية والتاريخية التي أثرت في إنشائها، والأمر الذي قد يفيدنا في هذا هو أننا قد نصبح أفضل في الإنتاجية إذا عرفنا كيف تعمل هذه التقنيات ولماذا هي على الوجه التي عليه بدلًا من الثورة عليها وتجنبها كليًا.
</p>

<p>
	قد تكون التجريدات الجديدة مفيدةً حقًا، فقد كان نموذج المكونات وأسلوب تدفق البيانات اللذان استخدمناهما في هذا المقال مثالًا على ذلك، وهناك الكثير من المكتبات التي تجعل برمجة واجهة المستخدِم أفضل وأسهل، سيما <a href="https://wiki.hsoub.com/React" rel="external">React</a> و <a href="https://academy.hsoub.com/programming/javascript/angular/%D9%85%D8%A7-%D9%87%D9%8A-angular%D8%9F-r1379/" rel="">Angular</a> الشائعتا الاستخدام وقت كتابة هذه السلسلة بنسختها الأصلية، لكن هذا مجال كبير بحد ذاته ننصحك بإنفاق بعض الوقت في تصفِّحه كي تعرف كيف تعمل هذه المكتبات والفوائد التي تجنيها منها.
</p>

<h2>
	تدريبات
</h2>

<p>
	لا زال هناك مساحة نطور فيها برنامجنا، فلمَ لا نضيف بعض المزايا الجديدة في صورة تدريبات؟
</p>

<h3>
	رابطات لوحة المفاتيح
</h3>

<p>
	أضف اختصارات للوحة المفاتيح إلى التطبيق، بحيث إذا ضغطنا على الحرف الأول من اسم أداة فستُختار الأداة، وctrl+z يفعِّل إجراء التراجع.
</p>

<p>
	افعل ذلك عبر تعديل مكوِّن <code>PixelEditor</code> وأضف خاصية <code>tabIndex</code> التي تساوي 0 إلى العنصر المغلِّف <code>&lt;div&gt;</code> كي يستطيع استقبال التركيز من لوحة المفاتيح.
</p>

<p>
	لاحظ أن الخاصية الموافقة لسمة <code>tabindex</code> تُسمى <code>tabIndex</code> حيث يكون حرف <code>I</code> فيها على الصورة الكبيرة، كما تتوقع دالة <code>elt</code> أسماء خصائص، ثم سجِّل معالجات أحداث المفاتيح مباشرةً على ذلك العنصر، حيث سيعني هذا أنه عليك ضغط أو لمس أو نقر التطبيق قبل أن تستطيع التفاعل معه بلوحة المفاتيح.
</p>

<p>
	تذكَّر أنّ أحداث لوحة المفاتيح لها الخاصيتان <code>ctrlKey</code> و<code>metaKey</code> -المخصص لزر command في ماك-، حيث تستطيع استخدامهما لتعرف هل هذان الزران مضغوط عليهما أم لا.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_61" style="">
<span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// الأصلي PixelEditor صنف.</span><span class="pln">
  </span><span class="com">// وسع المنشئ. </span><span class="pln">
  </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">PixelEditor</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    constructor</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> config</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let </span><span class="pun">{</span><span class="pln">tools</span><span class="pun">,</span><span class="pln"> controls</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> config</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">

      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PictureCanvas</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> pos </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        let tool </span><span class="pun">=</span><span class="pln"> tools</span><span class="pun">[</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state</span><span class="pun">.</span><span class="pln">tool</span><span class="pun">];</span><span class="pln">
        let onMove </span><span class="pun">=</span><span class="pln"> tool</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">onMove</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> pos </span><span class="pun">=&gt;</span><span class="pln"> onMove</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">controls </span><span class="pun">=</span><span class="pln"> controls</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">
        </span><span class="typ">Control</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Control</span><span class="pun">(</span><span class="pln">state</span><span class="pun">,</span><span class="pln"> config</span><span class="pun">));</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{},</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">,</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"br"</span><span class="pun">),</span><span class="pln">
                     </span><span class="pun">...</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">controls</span><span class="pun">.</span><span class="pln">reduce</span><span class="pun">(</span><span class="pln">
                       </span><span class="pun">(</span><span class="pln">a</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">concat</span><span class="pun">(</span><span class="str">" "</span><span class="pun">,</span><span class="pln"> c</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">),</span><span class="pln"> </span><span class="pun">[]));</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">state </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let ctrl of </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">controls</span><span class="pun">)</span><span class="pln"> ctrl</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">startPixelEditor</span><span class="pun">({}));</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<ul>
<li>
		ستكون خاصية <code>key</code> لأحداث مفاتيح الأحرف هي الحرف نفسه في حالته الصغرى إذا لم يكن زر shift مضغوطًا، لكن لا تهمنا أحداث المفاتيح التي فيها زر shift.
	</li>
	<li>
		يستطيع معالج <code>"keydown"</code> فحص كائن الحدث الخاص به ليرى إذا كان يطابق اختصارًا من الاختصارات، كما تستطيع الحصول على قائمة من الأحرف الأولى من كائن <code>tools</code> كي لا تضطر إلى كتابتها.
	</li>
	<li>
		إذا طابق حدث مفتاح اختصارًا ما، استدع <code>preventDefault</code> عليه وأرسل الإجراء المناسب.
	</li>
</ul>
<h3>
	الرسم بكفاءة
</h3>

<p>
	سيكون أغلب العمل الذي يفعله التطبيق أثناء الرسم داخل <code>drawPicture</code>، ورغم أنّ إنشاء حالة جديدة وتحديث بقية DOM لن يكلفنا كثيرًا، إلا أنّ إعادة رسم جميع البكسلات في اللوحة يمثِّل مهمةً ثقيلةً، لذا جِد طريقةً لتسريع تابع <code>syncState</code> الخاص بـ <code>PictureCanvas</code> عبر إعادة الرسم في حالة تغير البكسلات فقط.
</p>

<p>
	تذكَّر أنّ <code>drawPicture</code> تُستخدَم أيضًا بواسطة زر الحفظ، فتأكد إذا غيرتها من أنك لا تخرِّب الوظيفة القديمة، أو أنشئ نسخةً جديدةً باسم مختلف، ولاحظ أنّ تغيير حجم عنصر <code>&lt;canvas&gt;</code> عبر ضبط خاصيتَي <code>width</code> و<code>height</code> له يتسبب في مسحه ليصير شفافًا مرةً أخرى.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_63" style="">
<span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// غيّر هذا التابع</span><span class="pln">
  </span><span class="typ">PictureCanvas</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">syncState </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">picture</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture </span><span class="pun">==</span><span class="pln"> picture</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture </span><span class="pun">=</span><span class="pln"> picture</span><span class="pun">;</span><span class="pln">
    drawPicture</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">

  </span><span class="com">// ربما تود تغيير هذا أو استخدامه كذلك.</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> drawPicture</span><span class="pun">(</span><span class="pln">picture</span><span class="pun">,</span><span class="pln"> canvas</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    canvas</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> picture</span><span class="pun">.</span><span class="pln">width </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
    canvas</span><span class="pun">.</span><span class="pln">height </span><span class="pun">=</span><span class="pln"> picture</span><span class="pun">.</span><span class="pln">height </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
    let cx </span><span class="pun">=</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let y </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> y </span><span class="pun">&lt;</span><span class="pln"> picture</span><span class="pun">.</span><span class="pln">height</span><span class="pun">;</span><span class="pln"> y</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> picture</span><span class="pun">.</span><span class="pln">width</span><span class="pun">;</span><span class="pln"> x</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        cx</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> picture</span><span class="pun">.</span><span class="pln">pixel</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">);</span><span class="pln">
        cx</span><span class="pun">.</span><span class="pln">fillRect</span><span class="pun">(</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln"> y </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">startPixelEditor</span><span class="pun">({}));</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	يمثِّل هذا التدريب مثالًا ممتازًا لرؤية كيف تسرِّع هياكل البيانات غير القابلة للتغير من الشيفرة، كما نستطيع الموازنة بين الصورة القديمة والجديدة بما أن لدينا كليهما وإعادة الرسم في حالة تغير لون البكسلات فقط، مما يوفر 99% من مهمة الرسم في الغالب.
</p>

<p>
	اكتب دالة <code>updatePicture</code> جديدةً أو اجعل دالة <code>drawPicture</code> تأخذ وسيطًا إضافيًا قد يكون غير معرَّف أو قد يكون الصورة السابقة، وتتحقق الدالة لكل بكسل مما إذا كانت الصورة السابقة قد مرت على هذا الموضع باللون نفسه أم لا، كما تتخطى البكسل الذي تكون تلك حالته.
</p>

<p>
	يجب عليك تجنب <code>width</code> و<code>height</code> حين يكون للصورتين الجديدة والقديمة نفس الحجم لأن اللوحة تُمسح حين يتغير حجمها، فإذا اختلفتا -وتلك ستكون حالتنا إذا حُمِّلت صورة جديدة- فيمكنك ضبط الرابطة التي تحمل الصورة القديمة على null بعد تغيير حجم اللوحة، إذ يجب ألا تتخطى أيّ بكسل بعد تغيير حجم اللوحة.
</p>

<h3>
	الدوائر
</h3>

<p>
	عرِّف أداةً اسمها <code>circle</code> ترسم دائرةً مصمتةً حين تسحب بالمؤشر، حيث يكون مركز الدائرة عند نقطة بداية السحب، ويُحدَّد نصف قطره بالمسافة المسحوبة.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_65" style="">
<span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> circle</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ضع شيفرتك هنا</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  let dom </span><span class="pun">=</span><span class="pln"> startPixelEditor</span><span class="pun">({</span><span class="pln">
    tools</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> baseTools</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">circle</span><span class="pun">})</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">).</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	تستطيع النظر في أداة <code>rectangle</code> لتستقي منها إرشادًا لهذا التدريب، حيث ستحتاج إلى إبقاء الرسم على صورة البدء بدلًا من الصورة الحالية عندما يتحرك المؤشر، ولكي تعرف أيّ البكسلات يجب تلوينها، استخدام نظرية فيثاغورس بحساب المسافة بين الموضع الحالي للمؤشر والموضع الابتدائي من خلال أخذ الجذر التربيعي <code>Math.sqrt</code> لمجموع تربيع <code>Math.pow(x, 2)‎</code> لفرق في إحداثيات x وتربيع الفرق في إحداثيات y.
</p>

<p>
	كرر بعد ذلك على تربيع البكسلات حول نقطة البداية التي تكون جوانبها ضعف نصف القطر على الأقل، ولوّن تلك التي تكون داخل نصف قطر الدائرة باستخدام معادلة فيثاغورس مرةً أخرى لتعرف بُعدها عن المركز، وتأكد من أنك لا تلون البكسلات التي تكون خارج حدود الصورة.
</p>

<h3>
	الخطوط المستقيمة
</h3>

<p>
	يُعَدّ هذا التدريب متقدمًا أكثر مما قبله وسيحتاج إلى تصميم حل لمشكلة ليست بالهينة، لذا تأكد من امتلاكك وقت وصبر قبل أن تبدأ العمل عليه، ولا يمنعنك الفشل في المحاولات أن تعيد الكرة.
</p>

<p>
	عندما تختار أداة <code>draw</code> في أغلب المتصفحات وتسحب المؤشر بسرعة ستجد أن ما حصلت عليه خطًا من النقاط التي تفصل بينها مسافات فارغة، وذلك لأن حدثَي <code>"mousemove"</code> أو <code>"touchmove"</code> لا ينطلقان بسرعة تغطي كل بكسل تمر عليه، لذا نريد منك تطوير أداة <code>draw</code> لتجعلها ترسم خطًا كاملًا، وهذا يعني أنه عليك جعل دالة معالج الحركة تتذكر الموضع السابق وتصله بالموضع الحالي، ولكي تفعل ذلك عليك كتابة دالة رسم خط عامة بما أنّ البكسلات التي تمر عليها قد لا تكون متجاورةً بما يصلح لخط مستقيم.
</p>

<p>
	يُعَدّ الخط المستقيم بين بكسلين سلسلةً من البكسلات المتصلة في سلسلة واحدة بأقرب هيئة تمثل خطًا مستقيمًا من البداية إلى النهاية، ويُنظر إلى البكسلات المتجاورة قطريًا على أنها متصلة، لذا فإن الخط المائل يجب أن يبدو مثل الصورة التي على اليسار وليس الصورة اليمنى.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85694" href="https://academy.hsoub.com/uploads/monthly_2021_12/line-grid.png.0e93e7cbfc5513e93c87472321e63930.png" rel=""><img alt="line-grid.png" class="ipsImage ipsImage_thumbnailed" data-fileid="85694" data-unique="tagtddbqb" src="https://academy.hsoub.com/uploads/monthly_2021_12/line-grid.png.0e93e7cbfc5513e93c87472321e63930.png"></a>
</p>

<p>
	أخيرًا، إذا كانت لدينا شيفرةً ترسم خطًا بين نقطتين عشوائيتين فربما تريد استخدامها كي تعرِّف أداة سطر <code>line</code> ترسم خطًا مستقيمًا بين بداية السحب ونهايته.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_68" style="">
<span class="pun">&lt;</span><span class="pln">div</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// هذه أداة الرسم القديمة، أعد كتابتها.</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> draw</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">function</span><span class="pln"> drawPixel</span><span class="pun">({</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">},</span><span class="pln"> state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let drawn </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">color</span><span class="pun">};</span><span class="pln">
      dispatch</span><span class="pun">({</span><span class="pln">picture</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">picture</span><span class="pun">.</span><span class="pln">draw</span><span class="pun">([</span><span class="pln">drawn</span><span class="pun">])});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    drawPixel</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> drawPixel</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> line</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> dispatch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ضع شيفرتك هنا.</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  let dom </span><span class="pun">=</span><span class="pln"> startPixelEditor</span><span class="pun">({</span><span class="pln">
    tools</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">draw</span><span class="pun">,</span><span class="pln"> line</span><span class="pun">,</span><span class="pln"> fill</span><span class="pun">,</span><span class="pln"> rectangle</span><span class="pun">,</span><span class="pln"> pick</span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">).</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	تتكون مشكلة رسم خط من البكسلات من أربع مشكلات تختلف اختلافًا طفيفًا فيما بينها، إذ يُعَدّ رسم خط أفقي من اليسار إلى اليمين سهلًا إذ تكرر على إحداثيات x وتلون البكسل في كل خطوة، فإذا كان الخط يميل قليلًا أقل من 45 درجة أو <code>‎¼π</code> راديان، فتستطيع وضع إحداثيات y مع الميل، لكن لا زلت في حاجة إلى بكسل لكل موضع x، ويُحدِّد الميل موضع y لكل بكسل من هذه البكسلات.
</p>

<p>
	لكن ستحتاج إلى تغيير الطريقة التي تعامل بها الإحداثيات بمجرد تجاوز الميل درجة 45، حيث ستحتاج الآن إلى بكسل واحد لكل موضع y بما أن الخط يتقدم إلى الأعلى أكثر من سيره إلى اليسار، وعندما تتجاوز 135 درجة فعليك العودة إلى التكرار على إحداثيات x لكن من اليمين إلى اليسار.
</p>

<p>
	لست بحاجة إلى كتابة أربع حلقات تكرارية، وبما أنّ رسم خط من A إلى B هو نفسه رسم خط من B إلى A، فيمكنك نبديل مواضع البداية والنهاية للخطوط التي تبدأ من اليمين إلى اليسار وتعامِلها على أنها من اليسار إلى اليمين، لذا تحتاج إلى حلقتين تكراريتين مختلفتين، وأول شيء يجب أن تفعله دالة رسم الخطوط هو معرفة هل الفرق بين إحداثيات x أكبر من الفرق بين إحداثيات y أم لا، فإذا كان فإنّ هذا خط مائل للأفقية، وإلا فإنه يميل لأن يكون رأسيًا.
</p>

<p>
	تأكد من أن توازن بين القيم المطلقة لفروق x وy والتي تحصل عليها بواسطة <code>Math.abs</code>، وبمجرد معرفتك أيّ محور ستكرر عليه، تستطيع تفقد نقطة البدء لترى إذا كان لها إحداثي أعلى على هذا المحور من نقطة النهاية أم لا وتبدلهما إذا دعت الحاجة، وتكون الطريقة المختصرة هنا لتبديل قيم رابطتين في جافاسكربت تستخدِم مهمة تفكيك كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_175_70" style="">
<span class="pun">[</span><span class="pln">start</span><span class="pun">,</span><span class="pln"> end</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">end</span><span class="pun">,</span><span class="pln"> start</span><span class="pun">];</span></pre>

<p>
	ثم تحسب ميل الخط الذي يُحدِّد المقدار الذي يتغير به الإحداثي على المحور الآخر لكل خطوة تأخذها على المحور الأساسي، وتستطيع تشغيل حلقة تكرارية هنا على المحور الأساسي أثناء تتبع الموضع الموافق على المحور الآخر، كما يمكنك رسم بكسلات على كل تكرار، لكن تأكد من أن تقرِّب إحداثيات المحور غير الأساسي بما أنها ستكون أعدادًا كسرية على الأرجح ولا يتجاوب معها تابع <code>draw</code> جيدًا.
</p>

<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/19_paint.html" rel="external nofollow">للفصل التاسع عشر من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/http-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1367/" rel="">HTTP والاستمارات في جافاسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-%D9%81%D9%8A-%D8%A8%D9%8A%D8%A6%D8%A9-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D9%88%D8%A7%D9%84%D9%85%D9%88%D8%A7%D8%B5%D9%81%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AA%D8%B9%D9%84%D9%82%D8%A9-%D8%A8%D9%87%D8%A7-r1103/" rel="">جافاسكربت في بيئة المتصفح والمواصفات المتعلقة بها</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%d8%a7%d9%84%d8%aa%d8%b9%d8%a7%d9%85%d9%84-%d9%85%d8%b9-%d8%b9%d9%86%d8%b5%d8%b1-canvas-%d8%a8%d8%a7%d8%b3%d8%aa%d8%ae%d8%af%d8%a7%d9%85-%d8%ac%d8%a7%d9%81%d8%a7%d8%b3%d9%83%d8%b1%d8%a8%d8%aa-%d8%b1%d8%b3%d9%85-%d8%a7%d9%84%d8%a3%d8%b4%d9%83%d8%a7%d9%84-r209/" rel="">التعامل مع عنصر Canvas باستخدام جافاسكربت (رسم الأشكال)</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%86%D8%AD%D9%86%D9%89-%D8%A8%D9%8A%D8%B2%D9%8A%D9%87-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87-%D9%81%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-%D9%88%D8%B5%D9%86%D8%A7%D8%B9%D8%A9-%D8%A7%D9%84%D8%AD%D8%B1%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1339/" rel="">منحنى بيزيه وأهميته في الرسوميات وصناعة الحركات في جافاسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D8%AD%D8%B1%D9%83%D8%A7%D8%AA-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1351/" rel="">إنشاء الحركات عبر جافاسكربت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1401</guid><pubDate>Thu, 16 Dec 2021 06:27:45 +0000</pubDate></item><item><title>&#x627;&#x644;&#x645;&#x62C;&#x645;&#x648;&#x639;&#x627;&#x62A; &#x648;&#x627;&#x644;&#x645;&#x62C;&#x627;&#x644;&#x627;&#x62A; &#x641;&#x64A; &#x627;&#x644;&#x62A;&#x639;&#x627;&#x628;&#x64A;&#x631; &#x627;&#x644;&#x646;&#x645;&#x637;&#x64A;&#x629;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%85%D8%AC%D9%85%D9%88%D8%B9%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AC%D8%A7%D9%84%D8%A7%D8%AA-%D9%81%D9%8A-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-r1400/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_12/61cf7f3947b7c_-----Javascript--.png.d356babbf3bfce32c467e56d95bb8e29.png" /></p>

<p>
	وجود عدة محارف أو أصناف محارف ضمن قوسين مربعين <code>[…]</code> يعني البحث عن أي محرف بينها. سيكمل هذا المقال الغوص في التعابير النمطية بعد أن تطرقنا إلى مقدمة شاملة عنها في المقال السابق بعنوان <a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1399/" rel="">أساسيات التعابير النمطية في جافاسكربت</a>.
</p>

<h2>
	المجموعات
</h2>

<p>
	يعني النمط <code>[eao]</code> البحث عن أيٍّ من المحارف الثلاثة <code>'a'</code> أو <code>'e'</code> أو <code>'o'</code>، ويُدعى هذا النمط بالمجموعة، ويمكن استخدام المجموعات ضمن <a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1374/" rel="">التعابير النمطية</a> ومع محارف نظامية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_7" style="">
<span class="com">//"op" ثم  [t أو m] جد </span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Mop top"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/[tm]op/</span><span class="pln">gi</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// "Mop", "top"</span></pre>

<p>
	لاحظ أنه وعلى الرغم من وجود عدة محارف ضمن المجموعة، فإنها سترتبط بمحرف واحد تمامًا عند إعادة النتيجة، فمثلًا لن يعيد البحث التالي أي نتيجة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_9" style="">
<span class="com">//"la" ثم  [o أو i] ثم  "V" جد </span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Voila"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/V[oi]la/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null, no matches</span></pre>

<p>
	سيبحث النمط عن:
</p>

<ul>
<li>
		<code>V</code> ثم أحد الحرفين <code>[oi]</code>.
	</li>
	<li>
		ثم <code>la</code>.
	</li>
	<li>
		لذا ستكون النتيجة إما <code>Vola</code> أو <code>Vila</code>، والنصان غير موجودين.
	</li>
</ul>
<h2>
	المجالات
</h2>

<p>
	يمكن أن تضم الأقواس المربعة مجالات من المحارف، مثل المجال <code>[a-z]</code>، الذي يحدد المحارف بين <code>a</code> و<code>z</code>، أو المجال <code>[0-5]</code> الذي يحدد الأرقام بين <code>0</code> و<code>5</code>، وسنبحث في المثال التالي عن <code>"x"</code> متبوعًا برقمين أو حرفين من <code>A</code> إلى <code>F</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_11" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Exception 0xAF"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/x[0-9A-F][0-9A-F]/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// xAF</span></pre>

<p>
	ستجد مجالين ضمن الأقواس المربعة <code>[A-F0-9]</code>، وسنبحث عن عدد من 0 إلى 9 أو حرف من A إلى F، ويمكن البحث عن أحرف بالحالة الصغيرة بإضافة المجال <code>a-f</code>، بحيث يصبح النمط <code>[A-F0-9a-f]</code> أو باستخدام الراية <code>i</code>، كما يمكن استخدام أصناف المحارف داخل الأقواس المربعة، فلو أردنا مثلًا البحث عن محرف كلمة أو شرطة قصيرة، فستكون المجموعة المناسبة هي <code>[-w\]</code>، ويمكن الجمع أيضًا بين أكثر من صنف محارف، مثل المجموعة <code>[s\d\]</code>، التي تعني البحث عن محرف فراغ أو رقم
</p>

<p>
	انتبه إلى أن أصناف المحارف هي اختصارات لمجموعات محددة من المحارف، فمثلًا:
</p>

<ul>
<li>
		<code>d\</code>: هي نفسها المجموعة <code>[9-0]</code>.
	</li>
	<li>
		<code>w\</code>: هي نفسها المجموعة <code>[a-zA-Z0-9]</code>.
	</li>
	<li>
		<code>s\</code>: هي نفسها المجموعة <code>[t\n\v\f\r\ ]</code>، بالإضافة إلى بعض محارف فراغ Unicode الخاصة.
	</li>
</ul>
<h3>
	مثال: محرف الكلمة w\ للغات متعددة
</h3>

<p>
	يمكن لصنف المحارف <code>w\</code>، كونه اختصارًا للنمط <code>[a-zA-Z0-9]</code>، أن يجد المحارف العربية أو الصينية وغيرها، لذلك يمكننا كتابة أنماط من التعابير لإيجاد محارف في أي لغة باستخدام خصائص الترميز Unicode مثل التعبير النمطي التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_13" style="">
<span class="pun">[{</span><span class="pln">p</span><span class="pun">{</span><span class="typ">Alpha</span><span class="pun">}</span><span class="pln">\p</span><span class="pun">{</span><span class="pln">M</span><span class="pun">}</span><span class="pln">\p</span><span class="pun">{</span><span class="typ">Nd</span><span class="pun">}</span><span class="pln">\p</span><span class="pun">{</span><span class="typ">Pc</span><span class="pun">}</span><span class="pln">\p</span><span class="pun">{</span><span class="typ">Join_C</span><span class="pln">\]</span></pre>

<p>
	ولنفسر الآن هذا النمط، حيث ننشئ فيه مجموعةً خاصةً بنا تتضمن محارف لها خصائص Unicode التالية:
</p>

<ul>
<li>
		<code>Alpha</code>: للأحرف.
	</li>
	<li>
		<code>M</code>: للعلامات فوق الأحرف.
	</li>
	<li>
		<code>Nd</code>: للأعداد بالنظام العشري.
	</li>
	<li>
		<code>Pc</code>: للشرطة السفلية (_) والمحارف المشابهة.
	</li>
	<li>
		<code>Join_C</code>: للمحارف الخاصة <code>200c</code> و<code>200d</code> التي تستخدم في لغات غير لاتينية، مثل العربية.
	</li>
</ul>
<p>
	إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_15" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/</span><span class="pln">gu</span><span class="pun">;</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Hi</span><span class="pln"> </span><span class="pun">你好</span><span class="pln"> </span><span class="lit">12</span><span class="pun">`;</span><span class="pln">

</span><span class="com">// جد كل الأحرف والأرقام:</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// H,i,你,好,1,2</span></pre>

<p>
	يمكن بالطبع تعديل هذه النمط بإضافة خصائص جديدة أو إزالتها، وسنفصّل في هذا الخصائص لاحقًا.
</p>

<p>
	انتبه إلى أن خصائص يونيكود Unicode أي <code>p{...}‎</code> غير مدعومة في متصفح إنترنت إكسبلورر IE، فلا يدعم متصفح IE خصائص Unicode، فإذا احتجنا إليها فيمكننا استخدام المكتبة <a href="http://xregexp.com/" rel="external nofollow">XRegExp</a>، ومن الممكن أيضًا استخدام مجالات لأحرف اللغة المطلوبة، مثل المجال [ا-ي] في اللغة العربية.
</p>

<h2>
	مجالات الاستثناء
</h2>

<p>
	ستجد أيضًا مجالات لاستثناء محارف معينة تبدو بالشكل <code>[…^]</code>، فهي معلّمة بالمحرف <code>^</code> في البداية، وتطابق أي محرف عدا المحارف المعطاة، وإليك بعض الأمثلة:
</p>

<ul>
<li>
		<code>[aeyo^]</code>: أي محرف عدا المحارف <code>'a'</code> أو <code>'e'</code> أو <code>'y'</code> أو <code>'o'</code>.
	</li>
	<li>
		<code>[0-9^]</code>: أي محرف عدا الأرقام، لها وظيفة الصنف <code>D\</code>.
	</li>
	<li>
		<code>[s\^]</code>: أي محرف لا يمثل محرف فراغ.
	</li>
</ul>
<p>
	يبحث المثال التالي عن أي محارف عدا الأحرف أو الأرقام أو الفراغات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_17" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"alice15@gmail.com"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/[^\d\sA-Z]/</span><span class="pln">gi</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// @ and .</span></pre>

<h2>
	التجاوز عند استخدام […]
</h2>

<p>
	رأينا سابقًا أننا نتجاوز (نهرِّب) الوظيفة الخاصة لمحرف باستخدام الشرطة المعكوسة <code>.\</code>، وإذا أردنا البحث عن الشرطة المعكوسة بذاتها نكررها مرتين <code>\\</code>، ويمكن استخدام معظم المحارف الخاصة ضمن الأقواس المربعة دون الحاجة إلى تجاوزها:
</p>

<ul>
<li>
		لاتحتاج الرموز <code>. + ( )</code> إلى تجاوز.
	</li>
	<li>
		لا يحدث تجاوز للشرطة <code>-</code> في بداية ونهاية النص، أي عندما لا تحدد مجالًا.
	</li>
	<li>
		يحدث تجاوز للعلامة <code>^</code> في بداية النص فقط، عندما تعني الاستثناء.
	</li>
	<li>
		يحدث تجاوز دائم لقوس الإغلاق المربع <code>[</code>، إن أردنا البحث عنه بحد ذاته.
	</li>
</ul>
<p>
	وبعبارة أخرى يُسمح باستخدام المحارف الخاصة جميعها دون الحاجة للتجاوز؛ إلا عندما تعني شيئًا محددًا ضمن الأقواس المربعة، فالنقطة ضمن الأقواس المربعة تعني نقطة، فمثلًا سيبحث النمط <code>[.,]</code> مثلًا عن نقطة أو فاصلة.
</p>

<p>
	يبحث النمط <code>[-().^+]</code> في المثال التالي عن أي محرف من المحارف <code>+().^-</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_20" style="">
<span class="com">// لا حاجة للتجاوز</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/[-().^+]/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"1 + 2 - 3"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// تطابق +, -</span></pre>

<p>
	لكن إذا قررت تجاوز هذه المحارف للاحتياط، فلا مشكلة في ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_22" style="">
<span class="com">// تجاوز كل شيء</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/[\-\(\)\.\^\+]/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"1 + 2 - 3"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// سينجح أيضًا +, -</span></pre>

<h2>
	المجالات واستعمال الراية "u"
</h2>

<p>
	إذا وجد محرف بصيغة زوج بديل surrogate pair، وهو تفسير بعض الخصائص للمحرف ذي البايتات الأربع، فلا بدّ من استخدام الراية <code>u</code> للتعامل مع هذه المحارف.
</p>

<p>
	لنبحث مثلًا عن النمط <code>[‎??‎]</code> ضمن النص "?":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_24" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">'?'</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/[??]/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">//  [?] سيظهر محرف غريب مثل</span><span class="pln">
</span><span class="com">// جرى البحث بطريقة غير صحيحة وأعاد نصف المحرف فقط</span></pre>

<p>
	ستكون النتيجة خاطئةً، لأن التعبير النمطي لا يعلم بوجود زوج بديل افتراضيًا، إذ يعتقد محرك التعبير النمطي أن النمط <code>[‎??‎]</code> مكون من أربعة محارف، وليس محرفين:
</p>

<ol>
<li>
		الأول هو النصف اليساري من ?.
	</li>
	<li>
		الثاني هو النصف اليميني من ?.
	</li>
	<li>
		,الثالث هو النصف اليساري من ?.
	</li>
	<li>
		الرابع هو النصف اليميني من ?.
	</li>
</ol>
<p>
	ويمكننا الاطلاع على ترميزها بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_26" style="">
<span class="kwd">for</span><span class="pun">(</span><span class="pln">let i</span><span class="pun">=</span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">&lt;</span><span class="str">'??'</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">'??'</span><span class="pun">.</span><span class="pln">charCodeAt</span><span class="pun">(</span><span class="pln">i</span><span class="pun">));</span><span class="pln"> </span><span class="com">// 55349, 56499, 55349, 56500</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	إذًا سيجد المثال السابق النصف الأيسر فقط من ? ويعرضه، لكن سنحل المشكلة عند إضافة الراية <code>u</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_28" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">'?'</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/[??]/</span><span class="pln">u</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// ?</span></pre>

<p>
	ونستخدم الحل نفسه عندما نبحث عن مجال يحوي محارف بأزواج بديلة، مثل <code>[‎?-?‎]</code>، وسينتج خطأً إذا لم نستخدم الراية <code>u</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_30" style="">
<span class="str">'?'</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/[?-?]/</span><span class="pun">);</span><span class="pln"> </span><span class="com">// خطأ: تعبير نظامي غير صحيح</span></pre>

<p>
	والسبب أنّ الزوج البديل سيعامل مثل محرفين دون الراية <code>u</code>، وهكذا سيُفسَّر النمط <code>[‎?-?‎]</code> على الشكل التالي:<br>
	 
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8738_7" style="">
<span class="pun">[&lt;</span><span class="lit">56500</span><span class="pun">&gt;&lt;</span><span class="lit">55349</span><span class="pun">&gt;-&lt;</span><span class="lit">56499</span><span class="pun">&gt;&lt;</span><span class="lit">55349</span><span class="pun">&gt;]</span><span class="pln">
</span></pre>

<p>
	حيث يُبدَّل كل زوج بترميزه، وهكذا سيكون المجال <code>56499-55349</code> خاطئًا، لأن الرمز النصي <code>56499</code> لبداية المجال أكبر من الرمز النصي <code>55349</code> لنهايته، وهذا هو سبب الخطأ، وسيعمل النمط بشكل صحيح بوجود الراية <code>u</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7219_32" style="">
<span class="com">//  ? إلى ? البحث عن محارف من</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">'?'</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/[?-?]/</span><span class="pln">u</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// ?</span></pre>

<p>
	ترجمة -وبتصرف- للفصل <a href="https://javascript.info/regexp-character-sets-and-ranges" rel="external nofollow">Sets and Ranges</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Moden JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/html/%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B4%D8%AC%D8%B1%D8%A9-dom-%D8%A7%D9%84%D8%AE%D9%81%D9%8A%D8%A9-r1353/" rel="">التعابير النمطية (regexp/PCRE) في PHP</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/linux/%d9%85%d9%82%d8%af%d9%85%d8%a9-%d9%81%d9%8a-%d8%a7%d9%84%d8%aa%d8%b9%d8%a7%d8%a8%d9%8a%d8%b1-%d8%a7%d9%84%d9%86%d9%85%d8%b7%d9%8a%d8%a9-regular-expressions-r63/" rel="">مقدمة في التعابير النمطية Regular Expressions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1341/" rel="">كيفية التعامل مع النصوص في البرمجة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1400</guid><pubDate>Sat, 25 Dec 2021 16:02:00 +0000</pubDate></item><item><title>&#x623;&#x633;&#x627;&#x633;&#x64A;&#x627;&#x62A; &#x627;&#x644;&#x628;&#x62D;&#x62B; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x627;&#x644;&#x62A;&#x639;&#x627;&#x628;&#x64A;&#x631; &#x627;&#x644;&#x646;&#x645;&#x637;&#x64A;&#x629; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%AD%D8%AB-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1399/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_12/61cf7d9c57c6c_------.png.215db37e374ebf1b26312f38b562a08e.png" /></p>

<p>
	<a href="https://academy.hsoub.com/programming/general/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1374/" rel="">التعابير النمطية Regular expressions</a> هي أنماط تزودنا بأسلوب فعّال للبحث عن النصوص التي تطابق نمطًا محددًا واستبدالها في <a href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a>، يتيح الكائن <a href="https://wiki.hsoub.com/JavaScript/RegExp" rel="external">RegExp</a> استخدام هذه التعابير، وتُدمج مع التوابع التي تتعامل مع النصوص.
</p>

<h2>
	مقدمة إلى التعابير النمطية
</h2>

<p>
	تتألف التعابير النمطية من نمط pattern، ورايات flags اختيارية، ولإنشاء كائن تعبير نمطي يمكن استخدام صيغتين، صيغة طويلة وصيغة قصيرة، أما الصيغة الطويلة فتكتب بالشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_9" style="">
<span class="pln">regexp </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RegExp</span><span class="pun">(</span><span class="str">"pattern"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"flags"</span><span class="pun">);</span></pre>

<p>
	وأما الصيغة القصيرة فتكتب باستخدام المحرف <code>"/"</code> بالشكل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_11" style="">
<span class="pln">regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/pattern/</span><span class="pun">;</span><span class="pln"> </span><span class="com">// لا رايات</span><span class="pln">
regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/pattern/</span><span class="pln">gmi</span><span class="pun">;</span><span class="pln"> </span><span class="com">//  g و m و i باستخدام الرايات</span></pre>

<p>
	تخبر الخطوط المائلة <a href="https://academy.hsoub.com/programming/javascript/%d9%85%d8%a7-%d9%87%d9%8a-%d8%ac%d8%a7%d9%81%d8%a7-%d8%b3%d9%83%d8%b1%d9%8a%d8%a8%d8%aa-%d8%9f-r524/" rel="">JavaScript</a> بأننا ننشئ تعبيرًا نمطيًا، فهي تلعب دورًا مماثلًا لعلامة الاقتباس في النصوص، وفي كلتا الصيغتين سيصبح <a href="https://wiki.hsoub.com/JavaScript/RegExp" rel="external">الكائن regexp</a> نسخةً عن الصنف المدمج <code>RegExp</code>.
</p>

<p>
	لا يُسمح بإدخال متغيرات تمثل الأنماط ضمن صيغة الخطوط المائلة <code>/.../</code> باستخدام القوالب <code>{...}$</code> الديناميكية مثلًا كما نفعل في النصوص، فهي ثابتة بشكل كامل، وهذا هو الاختلاف الرئيسي بين الصيغتين، إذ نستخدم الخطوط المائلة عندما نريد استخدام تعابير نمطية محددة ومعروفة بالنسبة لنا عند كتابة الشيفرة، وهذه هي الحالة الأكثر شيوعًا، بينما نستخدم الصيغة <code>new RegExp</code> عادةً عندما نحتاج إلى إنشاء تعابير نمطية أثناء التنفيذ انطلاقًا من نص أُنشئ ديناميكيًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_15" style="">
<span class="pln">let tag </span><span class="pun">=</span><span class="pln"> prompt</span><span class="pun">(</span><span class="str">"What tag do you want to find?"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"h2"</span><span class="pun">);</span><span class="pln">

let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RegExp</span><span class="pun">(`&lt;</span><span class="pln">$</span><span class="pun">{</span><span class="pln">tag</span><span class="pun">}&gt;`);</span><span class="pln"> </span><span class="com">// "h2" إن كان الجواب /&lt;h2&gt;/ نفس نتيجة</span></pre>

<h3>
	الرايات
</h3>

<p>
	للتعابير النمطية رايات تؤثر على نتيجة البحث، وتستخدم JavaScript ستةً منها، وهي:
</p>

<ul>
<li>
		<code>i</code>: سيكون البحث غير حساس لحالة الأحرف مع هذه الراية، أي لن يكون هناك فرق بين "a" و"A".
	</li>
	<li>
		<code>g</code>: سيعيد البحث جميع النتائج المتطابقة مع هذه الراية، وإلا فسيعيد النتيجة الأولى.
	</li>
	<li>
		<code>m</code>: سيعمل البحث في نمط الأسطر المتعددة، وسنغطي ذلك لاحقًا.
	</li>
	<li>
		<code>s</code>: ستمكّن نمط "dotall" الذي يسمح بأن نعدَّ النقطة <code>.</code> هي محرف نهاية السطر <code>n\</code>، وسنغطيه لاحقًا.
	</li>
	<li>
		<code>u</code>: سيمكّن الدعم الكامل لترميز Unicode، وبالتالي المعالجة الصحيحة لمحتويات الأقواس المعقوصة {..}، وسنغطيه لاحقًا.
	</li>
	<li>
		<code>y</code>: سيمكّن النمط اللاصق "Sticky"، حيث يبحث في المكان المحدد تمامًا من النص، وسنغطيه لاحقًا.
	</li>
</ul>
<h3>
	البحث باستخدام التابع str.match
</h3>

<p>
	تتكامل التعابير النمطية مع توابع النصوص، إذ يبحث التابع <code>(str.match(regexp</code> عن كل ما يطابق التعبير <code>regexp</code> في النص <code>str</code>، ويعمل وفق ثلاثة أنماط:
</p>

<p>
	النمط الأول، التعبير النمطي مع الراية <code>g</code>، ويعيد مصفوفةً تضم النتائج المطابقة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_19" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"We will, we will rock you"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/we/</span><span class="pln">gi</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// سيعيد مصفوفة من الحالتين المتطابقتين للبحث</span></pre>

<p>
	وانتبه إلى أنّ البحث سيعيد النتيجتين "we" و"We" لأن راية حالة الأحرف <code>i</code> مفعّلة.
</p>

<p>
	النمط الثاني، إذا لم نفعّل الراية <code>g</code>، فسيعيد البحث النتيجة الأولى فقط ضمن مصفوفة، مع وضع التطابق في الدليل <code>0</code>، بالإضافة إلى تفاصيل أخرى في الخصائص:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_21" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"We will, we will rock you"</span><span class="pun">;</span><span class="pln">

let result </span><span class="pun">=</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/we/</span><span class="pln">i</span><span class="pun">);</span><span class="pln"> </span><span class="com">//  g دون الراية</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> result</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]</span><span class="pln"> </span><span class="pun">);</span><span class="pln">     </span><span class="com">// We (التطابق الأول)</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">length </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 1</span><span class="pln">

</span><span class="com">// Details:</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">index </span><span class="pun">);</span><span class="pln">  </span><span class="com">// 0 (موقع التطابق)</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">input </span><span class="pun">);</span><span class="pln">  </span><span class="com">// We will, we will rock you (النص الأصلي)</span></pre>

<p>
	قد تحتوي المصفوفة على عناصر أخرى بالإضافة إلى العنصر ذي الدليل <code>0</code>؛ إذا كان جزء من التعبير النمطي ضمن قوسين مغلقين وسنغطي ذلك في المقالات القادمة.
</p>

<p>
	النمط الثالث، إذا لم توجد أي تطابقات، فسيعيد التابع القيمة <code>null</code>، بوجود الراية <code>g</code> أو عدم وجودها، لذا انتبه أننا لن نحصل في هذه الحالة على مصفوفة فارغة، بل على القيمة <code>null</code>، وسيؤدي نسيان هذه الحقيقة إلى مشاكل عدة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_23" style="">
<span class="pln">let matches </span><span class="pun">=</span><span class="pln"> </span><span class="str">"JavaScript"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/HTML/</span><span class="pun">);</span><span class="pln"> </span><span class="com">// = null</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">matches</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">//length خطأ لا يمكن قراءة الخاصية </span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">"Error in the line above"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا أردنا أن نحصل دائمًا على <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-arrays-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r818/" rel="">مصفوفة</a>، فيمكننا أن ننفذ الأمر بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_25" style="">
<span class="pln">let matches </span><span class="pun">=</span><span class="pln"> </span><span class="str">"JavaScript"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/HTML/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">matches</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">"No matches"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// ستعمل الآن</span><span class="pln">
</span><span class="pun">}</span></pre>

<h3>
	الاستبدال باستخدام التابع str.replace
</h3>

<p>
	يستبدل التابع <code>(str.replace(regexp, replacement</code> التطابقات الموافقة للكائن <code>regexp</code> في النص <code>str</code> بقيمة <code>replacement</code>، وستستبدل كل التطابقات عند استخدام الراية <code>g</code>، وإلا فسيستبدل أول تطابق فقط، وإليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_29" style="">
<span class="com">// g دون الراية</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"We will, we will"</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="str">/we/</span><span class="pln">i</span><span class="pun">,</span><span class="pln"> </span><span class="str">"I"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// I will, we will</span><span class="pln">

</span><span class="com">// مع الراية</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"We will, we will"</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="str">/we/</span><span class="pln">ig</span><span class="pun">,</span><span class="pln"> </span><span class="str">"I"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// I will, I will</span></pre>

<p>
	يمكن استخدام محارف خاصة ضمن الوسيط الثاني <code>replacement</code> للبحث عن أجزاء من معيار التطابق:
</p>
<style type="text/css">
table {
    width: 100%;
}

thead {
    vertical-align: middle;
    text-align: center;
} 

td, th {
    border: 1px solid #dddddd;
    text-align: right;
    padding: 8px;
    text-align: inherit;

}
tr:nth-child(even) {
    background-color: #dddddd;
}</style>
<table>
<thead><tr>
<th style="text-align:right">
				الرمز
			</th>
			<th style="text-align:right">
				ما يفعله ضمن النص replacment
			</th>
		</tr></thead>
<tbody>
<tr>
<td style="text-align:right">
				<code>&amp;$</code>
			</td>
			<td style="text-align:right">
				يمثل نص التطابق
			</td>
		</tr>
<tr>
<td style="text-align:right">
				<code>`$</code>
			</td>
			<td style="text-align:right">
				يمثل النص الواقع قبل نص التطابق.
			</td>
		</tr>
<tr>
<td style="text-align:right">
				<code>'$</code>
			</td>
			<td style="text-align:right">
				يمثل النص الواقع بعد نص التطابق.
			</td>
		</tr>
<tr>
<td style="text-align:right">
				<code>n$</code>
			</td>
			<td style="text-align:right">
				يمثل التطابق ذا الرقم n من مجموعة التطابق (الموجود ضمن قوسي تجميع"()" ) .
			</td>
		</tr>
<tr>
<td style="text-align:right">
				<code>‎$&lt;name&gt;‎</code>
			</td>
			<td style="text-align:right">
				يمثل التطابق ذا الاسم name من مجموعة التطابق (الموجود ضمن قوسي تجميع"()" ).
			</td>
		</tr>
<tr>
<td style="text-align:right">
				<code>$$</code>
			</td>
			<td style="text-align:right">
				يمثل المحرف <code>$</code>
			</td>
		</tr>
</tbody>
</table>
<p>
	مثال باستخدام <code>&amp;$</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_31" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"I love HTML"</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="str">/HTML/</span><span class="pun">,</span><span class="pln"> </span><span class="str">"$&amp; and JavaScript"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// I love HTML and JavaScript</span></pre>

<h3>
	الاختبار باستخدام التابع regexp.test
</h3>

<p>
	يبحث التابع <code>(regexp.test(str</code> عن تطابق واحد على الأقل -إن وُجد- ويعيد القيمة <code>true</code>، وإن لم يجد تطابقًا فسيعيد القيمة <code>false</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_33" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"I love JavaScript"</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/LOVE/</span><span class="pln">i</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="pln">str</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// true</span></pre>

<p>
	سندرس لاحقًا في هذا القسم <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-regular-expressions-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1278/" rel="">تعابير نمطيةً</a> أكثر، وسنتعرف على توابع أخرى لاحقًا.
</p>

<h2>
	أصناف المحارف
</h2>

<p>
	لنتأمل مهمةً تتطلب تحويل رقم هاتف، مثل <code>"67-45-123-(903)7+"</code>، إلى رقم صرف، أي <code>79031234567</code>، لتنفيذ ذلك يمكننا تتبع وحذف أي محرف لا يمثل رقمًا، وستساعدنا أصناف المحارف في ذلك، حيث تمثل إشارات خاصةً تطابق أي رمز ضمن مجموعة معينة، وسنتعرف بدايةً على الصنف "digit"، الذي يُعبَّر عنه بالشكل <code>d\</code>، ويرتبط بأي رقم مفرد، فمثلًا لنجد الرقم الأول في رقم الهاتف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_35" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"+7(903)-123-45-67"</span><span class="pun">;</span><span class="pln">

let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/\d/</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 7</span></pre>

<p>
	سيبحث التابع عن كل الأرقام التي سيجدها في النص بإضافة الراية <code>g</code>، وسيعيدها ضمن مصفوفة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_37" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"+7(903)-123-45-67"</span><span class="pun">;</span><span class="pln">

let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/\d/</span><span class="pln">g</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">//  7,9,0,3,1,2,3,4,5,6,7</span><span class="pln">

</span><span class="com">// لنشكل عددًا من أرقام المصفوفة:</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">).</span><span class="pln">join</span><span class="pun">(</span><span class="str">''</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 79031234567</span></pre>

<p>
	سنستعرض الآن بعض الأصناف الأكثر استعمالًا، وهي:
</p>

<ul>
<li>
		<code>d\</code>: من كلمة digit رقم، ويعيد أي رقم بين 0 و9.
	</li>
	<li>
		<code>‎\s</code>: من كلمة space فراغ، وتضم محارف الفراغ، مثل: مسافة الجدولة <code>t\</code>، والسطر الجديد <code>n\</code>، وغيرها من الرموز النادرة الاستخدام مثل <code>r\</code> و<code>v\</code> و<code>f\</code>.
	</li>
	<li>
		<code>w\</code>: من كلمة word كلمة، وتعيد محارف قد تشكل كلمةً، مثل الأحرف اللاتينية أو الأرقام أو الشرطة السفلية <code>_</code>، ولا تنتمي الأحرف غير اللاتينية، مثل العربية، إلى هذا الصنف، إذ تشير الرموز <code>d\s\w\</code> مثلًا إلى رقم متبوع بمحرف فراغ، ويليه محرف كلمة مثل <code>1 a</code>.
	</li>
</ul>
<p>
	يمكن أن يضم الكائن محارف تعابير نمطية بالإضافة إلى الأصناف، فمثلًا سيبحث التعبير <code>CSS\d</code> عن الكلمة <code><a href="https://academy.hsoub.com/programming/css/%d8%aa%d8%b9%d8%b1%d9%91%d9%81-%d8%b9%d9%84%d9%89-%d8%a3%d8%b3%d8%a7%d8%b3%d9%8a%d8%a7%d8%aa-css-r70/" rel="">CSS</a></code> متبوعةً برقم:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_40" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Is there CSS4?"</span><span class="pun">;</span><span class="pln">
let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/CSS\d/</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// CSS4</span></pre>

<p>
	كما يمكن استخدام أصناف محارف عدة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_42" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"I love HTML5!"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\s\w\w\w\w\d/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// ' HTML5'</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85672" href="https://academy.hsoub.com/uploads/monthly_2021_12/love-html5-classes.png.bfe664817c553a97be22654f43fcb86f.png" rel=""><img alt="love-html5-classes.png" class="ipsImage ipsImage_thumbnailed" data-fileid="85672" data-unique="v7mjvvgxs" src="https://academy.hsoub.com/uploads/monthly_2021_12/love-html5-classes.png.bfe664817c553a97be22654f43fcb86f.png"></a>
</p>

<h3>
	الأصناف المعكوسة
</h3>

<p>
	لكل صنف من أصناف المحارف صنف معكوس inverse class، يرمز له بالحرف نفسه لكن بحالة حرف كبير uppercase، ويعني ذلك الحصول على أي محرف عدا المحرف الذي يعيده الصنف عادةً، أي:
</p>

<ul>
<li>
		<code>D\</code>: لا يعيد رقمًا، وإنما يعيد أي محرف عدا المحارف التي يعيدها <code>d\</code>، مثل الأحرف اللاتينية.
	</li>
	<li>
		<code>S\</code>: لا يعيد محرف فراغ، وإنما يعيد أي محرف عدا المحارف التي يعيدها <code>s\</code>، مثل الأحرف اللاتينية.
	</li>
	<li>
		<code>W\</code>: لا يعيد محرف كلمة، وإنما يعيد أي محرف عدا المحارف التي يعيدها <code>w\</code>، مثل الأحرف غير اللاتينية أو الفراغات.
	</li>
</ul>
<p>
	لاحظ كيف سنستخدَم هذه الأصناف في تحويل رقم الهاتف في المثال السابق إلى رقم صرف وبطريقة أقصر، وذلك بإزالة المحارف التي لا تمثل أرقامًا <code>D\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_44" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"+7(903)-123-45-67"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="str">/\D/</span><span class="pln">g</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 79031234567</span></pre>

<h3>
	يمثل المحرف نقطة أي محرف
</h3>

<p>
	يمثل المحرف <code>.</code> صنفًا مميزًا من المحارف، ويطابق أي محرف عدا محرف السطر الجديد، إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_46" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Z"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/./</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// Z</span></pre>

<p>
	كما يمكن إدراجه وسط التعبير regexp:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_48" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/CS.4/</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"CSS4"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// CSS4</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"CS-4"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// CS-4</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"CS 4"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// CS 4 (الفراغ هو محرف أيضًا)</span></pre>

<p>
	لاحظ أن النقطة تطابق أي محرف، لكنها لن تبحث عن محرف غير موجود، فلا بدّ من وجود محرف حتى يعيده هذا الصنف:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_50" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"CS4"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/CS.4/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null, لن يعيد الصنف شيئًا لعدم وجود محرف</span></pre>

<p>
	لاحظ أن محرف النقطة يمثل أي محرف بالفعل بوجود الراية "s"، فلا تعيد النقطة عادةً محرف السطر الجديد، إذ يعيد التعبير <code>A.B</code> الحرفين <code>A</code> و<code>B</code> وأي محارف بينهما عدا محرف السطر الجديد <code>n\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_52" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"A\nB"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/A.B/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null (لا تطابقات)</span></pre>

<p>
	لكننا في عدة حالات نريد للنقطة أن تعني أي محارف فعلًا، بما في ذلك السطر الجديد، لذلك سنستخدم الراية <code>s</code> وسنحصل على المطلوب:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_55" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"A\nB"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/A.B/</span><span class="pln">s</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// A\nB (match!)</span></pre>

<p>
	انتبه إلى أن المتصفح IE لا يدعم الراية <code>s</code>، لكن يمكن استخدام التعبير النمطي <code>[‎\s\S]</code> الذي يعيد أي محرف في أي مكان، وسنغطي ذلك لاحقًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_57" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"A\nB"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/A[\s\S]B/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// A\nB (match!)</span></pre>

<p>
	ويعني التعبير <code>[‎\s\S]</code> البحث عن (محرف فراغ أو محرف لا يمثل فراغًا)، وهذا يمثل عمليًا أي محارف، ويمكن استخدام أي أزواج من الأصناف وعكسها، مثل <code>[‎\d\D]</code>، كما يمكن استخدام التعبير <code>[^]</code> الذي يعني البحث عن أي محرف عدا "لا شيء"، ويمكن استخدام الحيلة ذاتها إن أردنا أن يؤدي المحرف "نقطة" <code>.</code> كلا الوظيفتين، أي مطابقة أي محارف عدا السطر الجديد <code>.</code> أو مع السطر الجديد <code>[‎\s\S]</code>.
</p>

<p>
	انتبه أيضًا إلى الفراغات، إذ لا نكترث عادةً لوجود فراغات زائدة، وسنعتبر النص <code>1-5</code> مماثلًا للنص <code>1 - 5</code>، لكن قد يخفق التعبير النمطي <code>regexp</code> إن لم يأخذ هذه الفراغات في الحسبان.
</p>

<p>
	لنحاول مثلًا أن نجد أرقامًا مفصولةً بشرطة صغيرة (-):
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_59" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"1 - 5"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d-\d/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null, لا تطابق</span></pre>

<p>
	سنصلح ذلك بإضافة فراغات إلى التعبير النمطي <code>d - \d\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_61" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"1 - 5"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d - \d/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 1 - 5, سنجد الآن تطابقًا</span><span class="pln">
</span><span class="com">//  \s أو يمكن استخدام الصنف:</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"1 - 5"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d\s-\s\d/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 1 - 5, سيعمل أيًا</span></pre>

<p>
	فالفراغ هو محرف، ولا يمكننا إضافة أو إزالة الفراغات من تعبير نمطي، ثم نتوقع أن يعمل كما هو مطلوب، فكل المحارف التي ندخلها في التعبير النمطي ستُؤخذ في الحسبان عند البحث، بما فيها الفراغات.
</p>

<h2>
	تعامل التعابير النمطية مع ترميز يونيكود Unicode
</h2>

<p>
	تستخدم JavaScript ترميز يونيكود Unicode لتمثيل النصوص، حيث تُرمّز معظم المحارف باستخدام 2 بايت، وهذا ما يسمح بترميز 65536 محرف كحد أقصى، ولا يعتبر العدد السابق كافيًا لترميز كل المحارف الممكنة، لذلك ستجد أن بعض المحارف الخاصة رمِّزت باستخدام 4 بايت، مثل المحرف <code>?</code> -التمثيل الرياضي للمحرف X- أو <code>?</code> -الابتسامة- وبعض المحارف الهيروغليفية وغيرها، وإليك قيم Unicode لبعض المحارف الخاصة:
</p>

<table>
<thead><tr>
<th style="text-align:center">
				المحرف
			</th>
			<th style="text-align:center">
				ترميز Unicode
			</th>
			<th style="text-align:center">
				عدد البايتات المستخدمة
			</th>
		</tr></thead>
<tbody>
<tr>
<td style="text-align:center">
				a
			</td>
			<td style="text-align:center">
				<code>0x0061</code>
			</td>
			<td style="text-align:center">
				2
			</td>
		</tr>
<tr>
<td style="text-align:center">
				≈
			</td>
			<td style="text-align:center">
				<code>0x2248</code>
			</td>
			<td style="text-align:center">
				2
			</td>
		</tr>
<tr>
<td style="text-align:center">
				?
			</td>
			<td style="text-align:center">
				<code>0x1d4b3</code>
			</td>
			<td style="text-align:center">
				4
			</td>
		</tr>
<tr>
<td style="text-align:center">
				?
			</td>
			<td style="text-align:center">
				<code>0x1d4b4</code>
			</td>
			<td style="text-align:center">
				4
			</td>
		</tr>
<tr>
<td style="text-align:center">
				?
			</td>
			<td style="text-align:center">
				<code>0x1f604</code>
			</td>
			<td style="text-align:center">
				4
			</td>
		</tr>
</tbody>
</table>
<p>
	تشغل محارف -مثل <code>a</code>- بايتين اثنين، ومحارف -مثل <code>?</code> و<code>?</code>- أربعة بايتات.
</p>

<p>
	كان ترميز Unicode في الأيام الأولى لظهور <a href="https://academy.hsoub.com/learn/javascript-application-development/" rel="">JavaScript</a> أكثر بساطةً، حيث لم توجد محارف من أربعة بايتات، لذلك ستجد أن بعض ميزات اللغة ستتعامل معها بشكل غير صحيح، فتعتقد الخاصية <code>length</code> مثلًا أنها عبارة عن محرفين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_64" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="str">'?'</span><span class="pun">.</span><span class="pln">length</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 2</span><span class="pln">
alert</span><span class="pun">(</span><span class="str">'?'</span><span class="pun">.</span><span class="pln">length</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 2</span></pre>

<p>
	والسبب في ذلك أنّ هذه الخاصية ستعامل المحرف ذا البايتات الأربعة على أنه محرفان لكل منهما بايتان فقط، وهذا أمر خاطئ، حيث ينبغي اعتبارها محرفًا واحدًا، وتُدعى "الزوج البديل" surrogate pair، وسنرى ذلك لاحقًا.
</p>

<p>
	تعامل التعابير النمطية regular expression المحارف الطويلة ذات البايتات الأربع مثل زوج من البايتات الثنائية، وقد يقود ذلك إلى أخطاء كما هو الحال في النصوص، لكن -وعلى خلاف النصوص- للتعابير النمطية الراية <code>u</code> التي تعالج هذه المشكلة، إذ يعالج التعبير المحارف ذات البايتات الأربع بشكل صحيح عند وجود هذه الراية، وسيصبح البحث متاحًا ضمن خصائص الترميز Unicode كما سنرى تاليًا.
</p>

<h3>
	خصائص ترميز Unicode باستعمال {…}p\
</h3>

<p>
	لمحارف Unicode العديد من الخصائص التي تصف الفئة التي ينتمي إليها المحرف، كما تقدم معلومات متنوعةً عنها، فلو كان للمحرف الخاصية <code>Letter</code> فسيعني ذلك أنه ينتمي إلى أبجدية ما، كما يعني امتلاكه الخاصية <code>Number</code> أنه رقم، وقد يكون رقمًا عربيًا أو صينيًا وهكذا، ويمكن البحث عن محرف يمتلك خاصيةً معينةً باستخدام الصنف <code>{...}p\</code>، ولاستخدام هذا الصنف لا بد أن نفعّل الراية <code>u</code> في التعبير النمطي.
</p>

<p>
	يبحث التعبير <code>{p{Letter\</code>مثلًا عن حرف في أي لغة، كما يمكن كتابته بالشكل <code>{p{L\</code>، وهو اسم مستعار للخاصية <code>Letter</code>، ولمعظم الخصائص أسماء مستعارة قصيرة.
</p>

<p>
	سيجد البحث في المثال التالي ثلاثة أنواع من الأحرف: انكليزي وجورجي وكوري.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_66" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="str">"A ბ ㄱ"</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\p{L}/</span><span class="pln">gu</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// A,ბ,ㄱ</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\p{L}/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null (no matches, \p doesn't work without the flag "u")</span></pre>

<p>
	إليك الفئات الأساسية للمحارف، وفئاتها الفرعية:
</p>

<ul>
<li>
		الحرف Letter: واسمها المستعار <code>L</code>.
	</li>
	<li>
		<code>Ll</code>: حرف صغير.
	</li>
	<li>
		<code>Lm</code>: حرف معدِّل modifier.
	</li>
	<li>
		<code>Lt</code>: عنوان.
	</li>
	<li>
		<code>Lu</code>: حرف كبير.
	</li>
	<li>
		<code>Lo</code>: غير ذلك.
	</li>
	<li>
		العدد Number: واسمها المستعار <code>N</code>.
	</li>
	<li>
		<code>Nd</code>: رقم بالصيغة العشرية.
	</li>
	<li>
		<code>Nl</code>: رقم حرف.
	</li>
	<li>
		<code>No</code>: غير ذلك.
	</li>
	<li>
		علامات الترقيم Punctuation: واسمها المستعار <code>P</code>.
	</li>
	<li>
		<code>Pc</code>: واصلة.
	</li>
	<li>
		<code>Pd</code>: خط فاصل dash.
	</li>
	<li>
		<code>Pi</code>: علامة اقتباس فاتحة.
	</li>
	<li>
		<code>Pf</code>: علامة اقتباس غالقة.
	</li>
	<li>
		<code>Ps</code>: مفتوح.
	</li>
	<li>
		<code>Pe</code>: مغلق.
	</li>
	<li>
		<code>Po</code>: غير ذلك.
	</li>
	<li>
		العلامة أو الحركة Mark: واسمها المستعار <code>M</code>، مثل العلامات أو الحركات فوق الأحرف وغيرها.
	</li>
	<li>
		<code>Mc</code>: علامة ضم مع فراغ spacing combining، وعلامة الضم هي محرف يعدّل محرفًا آخر بإضافة علامة أو حركة فوقه أو تحته وهكذا.
	</li>
	<li>
		<code>Me</code>: علامة ضم محيطة enclosing.
	</li>
	<li>
		<code>Mn</code>: علامة ضم دون فراغ non-spacing.
	</li>
	<li>
		الرمز Symbol: واسمه المستعار <code>S</code>.
	</li>
	<li>
		<code>Sc</code>: رموز عملة.
	</li>
	<li>
		<code>Sk</code>: رمز مُعدِّل.
	</li>
	<li>
		<code>Sm</code>: رمز رياضي.
	</li>
	<li>
		<code>So</code>: رمز آخر.
	</li>
	<li>
		الفواصل Separator: واسمها المستعار <code>Z</code>.
	</li>
	<li>
		<code>Zl</code>: خط.
	</li>
	<li>
		<code>Zp</code>: مقطع.
	</li>
	<li>
		<code>Zs</code>: فراغ.
	</li>
	<li>
		فئات أخرى: واسمها المستعار <code>C</code>.
	</li>
	<li>
		<code>Cc</code>: تحكم.
	</li>
	<li>
		<code>Cf</code>: تنسيق.
	</li>
	<li>
		<code>Cn</code>: لم يحدد بعد.
	</li>
	<li>
		<code>Co</code>: استخدام خاص.
	</li>
	<li>
		<code>Cs</code>: بديل.
	</li>
</ul>
<p>
	فلو أردنا مثلًا أحرفًا كبيرة فسنستخدم <code>{p{Ll\</code>، بينما نستخدم <code>{p{p\</code> لعلامات الترقيم وهكذا.
</p>

<p>
	ستجد أيضًا بعض الفئات المشتقة، مثل:
</p>

<ul>
<li>
		<code>Alphabetic</code>: واسمها المستعار <code>Alpha</code>، وتضم الأحرف <code>L</code>، والأرقام على شكل أحرف <code>Nl</code>، مثل Ⅻ وهو 12 بالرومانية.
	</li>
	<li>
		<code>Hex_Digit</code>: وتضم الأرقام الست عشرية: <code>0-9</code> و<code>a-f</code>.
	</li>
</ul>
<p>
	يدعم الترميز Unicode خصائص عديدةً، وسيزيد ذكرها من حجم المقالة بشكل كبير، لكن يمكنك الاطلاع على المراجع التالية:
</p>

<ul>
<li>
		<a href="https://unicode.org/cldr/utility/character.jsp" rel="external nofollow">قائمة بكل الخصائص وفقًا للمحرف</a>.
	</li>
	<li>
		<a href="https://unicode.org/cldr/utility/list-unicodeset.jsp" rel="external nofollow">قائمة بكل المحارف وفقًا للخاصية</a>.
	</li>
	<li>
		<a href="https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt" rel="external nofollow">قائمة بالاسماء المستعارة القصيرة للخصائص</a>.
	</li>
	<li>
		<a href="https://www.unicode.org/Public/UCD/latest/ucd/" rel="external nofollow">قاعدة كاملة بمحارف Unicode بتنسيق نصي مع كامل خصائصها</a>.
	</li>
</ul>
<h3>
	مثال: الأعداد الست عشرية
</h3>

<p>
	لننظر مثلًا إلى الأعداد الست عشرية المكتوبة بالشكل <code>xFF</code>، حيث <code>F</code> هو رقم ست عشري (0-1 أو A-F)، ويمكن الإشارة إلى الرقم الست عشري بالشكل <code>{p{Hex_Digit\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_68" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/x\p{Hex_Digit}\p{Hex_Digit}/</span><span class="pln">u</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="str">"number: xAF"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">));</span><span class="pln"> </span><span class="com">// xAF</span></pre>

<h3>
	مثال: الكتابة التصويرية الصينية
</h3>

<p>
	في ترميز Unicode خاصية تحدد نظام الكتابة، ويمكن أن تأخذ قيمًا مثل: <code>Cyrillic</code> و<code>Greek</code> و<code>Arabic</code> و<code>Han</code> (الصينية) و<a href="https://en.wikipedia.org/wiki/Script_(Unicode)" rel="external nofollow">غيرها من القيم المشابهة</a>، وللبحث عن محرف في نظام كتابة محدد علينا استخدام الخاصية <code>&lt;Script=&lt;value</code>، فللعربية مثلًا نستخدم <code>{p{sc=Arabic\</code>، وللصينية نستخدم <code>{p{sc=Han\</code> وهكذا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_70" style="">
<span class="pln">let regexpHan </span><span class="pun">=</span><span class="pln"> </span><span class="str">/\p{sc=Han}/</span><span class="pln">gu</span><span class="pun">;</span><span class="pln"> </span><span class="com">// يعيد التصويرية الصينية</span><span class="pln">
let regexpArab </span><span class="pun">=</span><span class="pln"> </span><span class="str">/\p{sc=Arabic}/</span><span class="pln">gu</span><span class="pun">;</span><span class="pln"> </span><span class="com">// يعيد العربية</span><span class="pln">

let str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Hello</span><span class="pln"> </span><span class="pun">Привет</span><span class="pln"> </span><span class="pun">你好</span><span class="pln"> </span><span class="pun">جميل</span><span class="pln"> </span><span class="lit">123</span><span class="pln">_456</span><span class="pun">`;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexpHan</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 你,好</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexpArab</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// ج,م,ي,ل</span></pre>

<h3>
	مثال: رموز العملة
</h3>

<p>
	للمحارف التي ترمز إلى العملات -مثل <code>$</code> و<code>€</code> و<code>¥</code>- خاصية في ترميز Unicode، هي <code>{p{Currency_Symbol\</code>، واسمها المختصر <code>{p{Sc\</code>، لنستخدم هذه الخاصية في الحصول على سعر منتج ضمن تنسيق يحوي رمز عملة يليه رقم:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_72" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/\p{Sc}\d/</span><span class="pln">gu</span><span class="pun">;</span><span class="pln">

let  str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Prices</span><span class="pun">:</span><span class="pln"> $2</span><span class="pun">,</span><span class="pln"> </span><span class="pun">€</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="pun">¥</span><span class="lit">9</span><span class="pun">`;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// $2,€1,¥9</span></pre>

<p>
	وسنتعرف لاحقًا في مقالات قادمة على آلية البحث عن أعداد تحتوي عدة أرقام.
</p>

<h2>
	محارف الارتكاز: محرفا بداية النص ^ ونهايته $
</h2>

<p>
	يحمل المحرفان <code>^</code> و<code>$</code> معانٍ خاصةً في التعابير النمطية regular expression، وتسمى بالمرتكزات أو المرابط Anchors، حيث تبحث العلامة <code>^</code> عن تطابق في بداية النص، بينما تبحث <code>$</code> عن تطابق في نهايته، لنر مثلًا إن بدأ نص ما بالكلمة "Mary":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_74" style="">
<span class="pln">let str1 </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Mary had a little lamb"</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">/^Mary/</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="pln">str1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// true</span></pre>

<p>
	يعني النمط أن نبحث عن الكلمة "Mary" في بداية السطر حصرًا، وبشكل مشابه يمكن أن نستخدم <code>$</code> للتحقق من وجود الكلمة "snow" مثلًا في نهاية نص:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_76" style="">
<span class="pln">let str1 </span><span class="pun">=</span><span class="pln"> </span><span class="str">"it's fleece was white as snow"</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">/snow$/</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="pln">str1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// true</span></pre>

<p>
	يمكن استخدام توابع التعامل مع النصوص <code>startsWith/endsWith</code> في هذه الحالات بالتحديد، لكن ينبغي استخدام التعابير النمطية في الحالات الأكثر تعقيدًا.
</p>

<h3>
	اختبار التطابق الكامل
</h3>

<p>
	يستخدم المرتكزان السابقان معًا <code>$...^</code> لاختبار تطابق نص بشكل كامل مع النمط، مثل التحقق من مطابقة ما يدخله المستخدم للتنسيق المطلوب، لنتحقق مثلًا أن نصًا له التنسيق الزمني التالي <code>12:34</code>، وهو عدد من رقمين، تليه نقطتان ":"، ثم عدد من رقمين، لذا سنستخدم التعبير <code>d\d:\d\d\</code> في عالم التعابير النمطية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_78" style="">
<span class="pln">let goodInput </span><span class="pun">=</span><span class="pln"> </span><span class="str">"12:34"</span><span class="pun">;</span><span class="pln">
let badInput </span><span class="pun">=</span><span class="pln"> </span><span class="str">"12:345"</span><span class="pun">;</span><span class="pln">

let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="str">/^\d\d:\d\d$/</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="pln">goodInput</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// true</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> regexp</span><span class="pun">.</span><span class="pln">test</span><span class="pun">(</span><span class="pln">badInput</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// false</span></pre>

<p>
	يجب أن يبدأ التطابق مباشرة ًعند بداية النص <code>^</code>، وينتهي بنهايته <code>$</code>، ويجب أن يطابق تنسيق النص تنسيق التعبير النمطي، فإذا وجدَت أي اختلافات أو محارف زائدة فستكون النتيجة "false"، وتسلك المرتكزات سلوكًا مختلفًا بوجود الراية <code>m</code>، وسنطلع على ذلك لاحقًا.
</p>

<p>
	انتبه إلى أن ليس للمرتكزات عرض، فالمرتكزات محارف اختبار، وليس لها عرض، فهي لا تطابق محارف محددةً، بل تجبر محرك التعبير النمطي للتحقق من الشرط (بداية ونهاية نص).
</p>

<h2>
	نمط الأسطر المتعددة: تأثير الراية "m" على المرتكزان ^ و $
</h2>

<p>
	يُفعّل نمط الأسطر المتعددة باستخدام الراية <code>m</code>، التي تؤثر على سلوك المرتكزين <code>$</code> و<code>^</code> فقط، فلا تطابق المرتكزات في نمط الأسطر المتعددة بداية ونهاية النص فقط، بل بداية ونهاية السطر.
</p>

<h3>
	البحث عند بداية سطر ^
</h3>

<p>
	يحتوي النص في المثال التالي على عدة أسطر، وسيأخذ النمط <code>d/gm\^/</code> رقمًا من بداية كل سطر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_82" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="lit">1st</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Winnie</span><span class="pln">
</span><span class="lit">2nd</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Piglet</span><span class="pln">
</span><span class="lit">3rd</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Eeyore</span><span class="pun">`;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/^\d/</span><span class="pln">gm</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 1, 2, 3</span></pre>

<p>
	سنحصل دون الراية <code>m</code> على الرقم الموجود في أول سطر فقط يطابق النمط:
</p>

<pre class="ipsCode">
let str = `1st place: Winnie
2nd place: Piglet
3rd place: Eeyore`;

alert( str.match(/^\d/g) ); // 1
</pre>

<p>
	والسبب أنّ العلامة <code>^</code> ستطابق افتراضيًا بداية النص فقط، بينما ستطابق في نمط الأسطر المتعددة بداية أي سطر.
</p>

<p>
	انتبه، فتعني بداية السطر رسميًا المحرف الذي يأتي بعد محرف السطر الجديد، وهكذا ستطابق العلامة <code>^</code> في نمط الأسطر المتعددة كل المحارف التي يسبقها محرف السطر الجديد <code>n\</code>.
</p>

<h3>
	البحث عند نهاية سطر $
</h3>

<p>
	يسلك المرتكز <code>$</code> السلوك السابق نفسه، إذ يجد التعبير النمطي <code>$d\</code> مثلًا آخر رقم في كل سطر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_84" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Winnie</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="typ">Piglet</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="typ">Eeyore</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pun">`;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d$/</span><span class="pln">gm</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 1,2,3</span></pre>

<p>
	سيطابق المرتكز <code>$</code> نهاية النص ككل دون استخدام الراية <code>m</code>، وبالتالي سنحصل فقط على آخر رقم.
</p>

<p>
	انتبه، فتعني نهاية السطر رسميًا المحرف الذي يأتي قبل محرف السطر الجديد، وهكذا ستطابق العلامة <code>$</code> في نمط الأسطر المتعددة كل المحارف التي تسبق محرف السطر الجديد <code>n\</code> مباشرةً.
</p>

<h3>
	البحث عن محرف السطر الجديد n\ بدل المرتكز $
</h3>

<p>
	يمكن البحث عن سطر جديد باستخدام المحرف <code>n\</code> بدلًا من البحث عن المرتكزين <code>^</code> و<code>$</code>، لكن ما الفرق؟
</p>

<p>
	سنبحث في المثال التالي عن <code>d\n\</code>بدلًا من <code>$d\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_86" style="">
<span class="pln">let str </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="typ">Winnie</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
</span><span class="typ">Piglet</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
</span><span class="typ">Eeyore</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span><span class="pun">`;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> str</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d\n/</span><span class="pln">gm</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 1\n,2\n</span></pre>

<p>
	ستجد الشيفرة تطابقين بدلًا من ثلاث، وذلك لعدم وجود سطر جديد بعد الرقم <code>3</code>، على الرغم من وجود مرتكز نهاية النص الذي يطابق $، أما الاختلاف الآخر فهو أنّ <code>n\</code>محرف، وسيظهر ضمن النتيجة، وليس مثل المرتكزين <code>^</code> و<code>$</code> اللذين يحددان شرط البحث في بداية ونهاية النص، وبالتالي سنستخدم <code>n\</code> إذا أردنا ظهور محرف السطر الجديد في النتيجة، وإلا استخدمنا <code>^</code> و<code>$</code>.
</p>

<h2>
	حدود الكلمة: b\
</h2>

<p>
	حدود الكلمة اختبار، مثل <code>^</code> و<code>$</code>، حيث يشير وجود العلامة <code>b\</code> بأنّ الموقع الذي وصل إليه محرك بحث التعابير النمطية هو حد لكلمة، وتوجد ثلاثة مواقع مختلفة يمكن اعتبارها حدودًا لكلمة، وهي:
</p>

<ul>
<li>
		في بداية نص، إذا كان أول محرف نصي فيه هو محرف كلمة <code>w\</code>.
	</li>
	<li>
		بين محرفين في نص، إذا كان أحدهما محرف كلمة <code>w\</code> والآخر ليس كذلك.
	</li>
	<li>
		في نهاية نص، إذا كان آخر محرف فيه هو محرف الكلمة <code>w\</code>.
	</li>
</ul>
<p>
	يمكن أن تجد التعبير <code>bJava\b\</code> مثلًا في النص <code>!Hello, Java</code> عندما تكون الكلمة <code>Java</code> منفصلةً عن غيرها، لكنك لن تحصل على نفس النتيجة في النص <code>!Hello, JavaScript</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_88" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello, Java!"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\bJava\b/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// Java</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello, JavaScript!"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\bJava\b/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null</span></pre>

<p>
	في الشكل التالي ستمثل المواقع المشار إليها العلامة <code>b\</code> في النص <code>!Hello, Java</code>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="85671" href="https://academy.hsoub.com/uploads/monthly_2021_12/hello_java_01.png.0821b1f036b17f93fbfc052c5934045b.png" rel=""><img alt="hello_java_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="85671" data-unique="cvs14xqcw" src="https://academy.hsoub.com/uploads/monthly_2021_12/hello_java_01.png.0821b1f036b17f93fbfc052c5934045b.png"></a>
</p>

<p>
	لذلك فهي تطابق النمط <code>bHello\b\</code> لأن:
</p>

<ol>
<li>
		بداية النص تتطابق مع العلامة <code>b\</code>.
	</li>
	<li>
		ثم تتطابق مع الكلمة <code>Hello</code>.
	</li>
	<li>
		ثم تتطابق مع العلامة <code>b\</code> مرةً أخرى، كما لو أنها بين المحرف <code>o</code> والفاصلة.
	</li>
</ol>
<p>
	إذًا سنعثر على النمط <code>bHello\b\</code>، لكن ليس النمط <code>bHell\b\</code>، لعدم وجود علامة حد الكلمة <code>b\</code> بعد المحرف <code>l</code>، كما لن نحصل على النمط <code>Java!\b</code> لأن علامة التعجب ليست محرف كلمة <code>w\</code>، وبالتالي لا وجود لعلامة حد الكلمة <code>b\</code> بعده.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_90" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello, Java!"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\bHello\b/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// Hello</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello, Java!"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\bJava\b/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">  </span><span class="com">// Java</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello, Java!"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\bHell\b/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln">  </span><span class="com">// null (no match)</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Hello, Java!"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\bJava!\b/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null (no match)</span></pre>

<p>
	يمكن استخدام <code>b\</code> مع الأرقام أيضًا، إذ يبحث النمط <code>b\d\d\b\</code> مثلًا عن عدد من رقمين -عدد بخانتين- منفصل عن غيره، أي يبحث عن عدد بخانتين غير محاط بمحارف الكلمة <code>w\</code>، مثل محارف الفراغات أو علامات الترقيم أو مرتكزات بداية ونهاية نص.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_92" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"1 23 456 78"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\b\d\d\b/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 23,78</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"12,34,56"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\b\d\d\b/</span><span class="pln">g</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 12,34,56</span></pre>

<p>
	انتبه، لا تعمل العلامة <code>b\</code> مع الأبجديات غير اللاتينية، إذ تتحقق العلامة <code>b\</code> من وجود محرف كلمة <code>w\</code> في طرف نص وعدم وجوده في الطرف الآخر، وبما أنّ محرف الكلمة <code>w\</code> سيدل على الأحرف اللاتينية <code>a-z</code> أو الأرقام أو الشرطة السفلية فقط، فلن يعمل مع محارف اللغات الأخرى، مثل العربية أو الصينية.
</p>

<h2>
	المحارف الخاصة وطريقة تجاوزها
</h2>

<p>
	يُستخدم المحرف <code>\</code> كما رأينا للإشارة إلى أصناف المحارف -مثل <code>d\</code>- فهو إذًا محرف خاص، وستجد محارف خاصةً أخرى تحمل معنىً خاصًا في التعابير النمطية، وتستخدم لتنفيذ عمليات بحث أكثر قدرةً، وهذه المحارف هي: <code>[ ]\ ^ $ . | ? * + ( )</code>، ولا حاجة طبعًا لتذكر هذه القائمة، فسنتعامل مع كل محرف منها بشكل منفصل وستحفظها تلقائيًا.
</p>

<h3>
	التجاوز
</h3>

<p>
	لنفترض أننا نريد البحث عن محرف النقطة بذاته، وليس أي محرف، في هذه الحالة -وبقية الحالات التي نريد فيها البحث عن محرف خاص بحد ذاته كما لو أنه محرف عادي- سنضع المحرف <code>\</code> قبله، أي <code>.\</code> في حالة النقطة، وتُدعى هذه العملية بالتجاوز أو تهريب محرف escaping a character، إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_94" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Chapter 5.1"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d\.\d/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 5.1 (تطابق!)</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Chapter 511"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\d\.\d/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null </span></pre>

<p>
	تعتبر الأقواس محارف خاصةً أيضًا، فلا بد من البحث عنها وفق النمط <code>)\</code>، ويبحث المثال التالي عن النص "()g":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_96" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"function g()"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/g\(\)/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// "g()"</span></pre>

<p>
	إذا أردنا البحث عن المحرف الخاص <code>\</code> فلا بدّ من مضاعفته <code>\\</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_98" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"1\\2"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="str">/\\/</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// '\'</span></pre>

<h3>
	الشرطة المائلة "/"
</h3>

<p>
	لا تعتبر الشرطة المائلة <code>'/'</code> من المحارف الخاصة، لكنها تستخدم في JavaScript لفتح وإغلاق التعابير النمطية <code>/...pattern.../</code> فلا بد من تجاوزها أيضًا، إليك الطريقة التي نبحث فيها عن الشرطة المائلة:
</p>

<pre class="ipsCode">
alert( "/".match(/\//) ); // '/'
</pre>

<p>
	لا داعي لتجاوز الشرطة المائلة في الحالة التي لا نستخدم فيها النمط <code>/.../</code>، بل ننشئ كائن تعبير نمطي باستخدام <code>new RegExp</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_100" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"/"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RegExp</span><span class="pun">(</span><span class="str">"/"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">//  / يجد</span></pre>

<h3>
	الإنشاء باستخدام new RegExp
</h3>

<p>
	عندما ننشئ تعبيرًا نمطيًا باستخدام <code>new RegExp</code>، فلا حاجة لتجاوز المحرف <code>/</code> لكن يتحتم علينا تجاوز غيره.
</p>

<p>
	تأمل الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_102" style="">
<span class="pln">let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RegExp</span><span class="pun">(</span><span class="str">"\d\.\d"</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Chapter 5.1"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// null</span></pre>

<p>
	لقد نجح البحث المشابه في أمثلة سابقة عندما استخدمنا <code>/\d\.\d/</code>، لكن لن يعمل البحث باستخدام <code>("new RegExp("\d\.\d</code> فما السبب؟
</p>

<p>
	السبب أن الشرطة المائلة ستُستهلك من قبل النص، وكما نتذكر فللنصوص العادية محارفها الخاصة -مثل <code>n\</code>- وقد استخدمت الشرطة المعكوسة backslash للتهريب.
</p>

<p>
	إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_104" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="str">"\d\.\d"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// d.d</span></pre>

<p>
	تستهلك إشارتا التنصيص الشرطة المعكوسة وتفسرها بطريقتها، فمثلًا:
</p>

<ul>
<li>
		تتحول <code>n\</code> إلى محرف السطر الجديد.
	</li>
	<li>
		تتحول <code>u1234\</code> إلى محرف Unicode بهذا الرمز.
	</li>
	<li>
		وفي حال لم يوجد معنىً لوجود الشرطة المعكوسة -مثل <code>d\</code> أو <code>z\</code>- فستُزال هذه الشرطة ببساطة.
	</li>
</ul>
<p>
	يحصل <code>new RegExp</code> على نص بلا شرطات معكوسة، لهذا لن يفلح البحث، ولحل المشكلة لا بدّ من مضاعفة الشرطة المعكوسة، لأن إشارتي التنصيص تحولان <code>\\</code> إلى <code>\</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_1581_106" style="">
<span class="pln">let regStr </span><span class="pun">=</span><span class="pln"> </span><span class="str">"\\d\\.\\d"</span><span class="pun">;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">regStr</span><span class="pun">);</span><span class="pln"> </span><span class="com">// \d\.\d (عملية صحيحة)</span><span class="pln">

let regexp </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RegExp</span><span class="pun">(</span><span class="pln">regStr</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">"Chapter 5.1"</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="pln">regexp</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 5.1</span></pre>

<h2>
	خلاصة
</h2>

<ul>
<li>
		يتألف التعبير النمطي من نمط pattern، ورايات flags اختيارية هي: <code>g</code> و<code>i</code> و<code>m</code> و<code>u</code> و<code>s</code> و<code>y</code>.
	</li>
	<li>
		لا تختلف عملية البحث دون استخدام الرايات والرموز الخاصة عن أي عملية بحث اعتيادية ضمن نص.
	</li>
	<li>
		يبحث التابع <code>(str.match(regexp</code> عن كل حالات التطابق التي يحددها التعبير <code>regexp</code> بوجود الراية <code>g</code>، وإلا فسيبحث عن أول تطابق فقط.
	</li>
	<li>
		يستبدل التابع <code>(str.replace(regexp, replacement</code> التطابقات التي يحددها التعبير <code>regexp</code> بالنص <code>replacement</code>، حيث يستبدل التطابقات كلها عند وجود الراية <code>g</code>، وإلا فسيستبدل أولها فقط.
	</li>
	<li>
		يعيد التابع <code>(regexp.test(str</code> القيمة <code>true</code> إن وجد تطابقًا على الأقل مع التعبير المستخدم، وإلا فسيعيد <code>false</code>.
	</li>
</ul>
<p>
	هنالك أصناف محارف متعددة، هي:
</p>

<ul>
<li>
		<code>d\</code>: للأرقام.
	</li>
	<li>
		<code>D\</code>: لغير الأرقام.
	</li>
	<li>
		<code>s\</code>: لمحارف الفراغات والجدولة والسطر الجديد.
	</li>
	<li>
		<code>S\</code>: لكل المحارف عدا محارف الفراغات والجدولة والسطر الجديد.
	</li>
	<li>
		<code>w\</code>: كل الأرقام والأحرف اللاتينية والشرطة السفلية (_).
	</li>
	<li>
		<code>W\</code>: كل المحارف عدا ما ذكرناه في السطر السابق.
	</li>
	<li>
		<code>.</code>: أي محرف عدا محرف السطر الجديد، وأي محرف تمامًا عند استخدام الراية <code>"s"</code>.
	</li>
</ul>
<p>
	تؤمن مجموعة المحارف Unicode التي تستخدمها JavaScript العديد من الميزات المتعلقة بالمحارف، مثل تحديد اللغة التي ينتمي إليها محرف معين، وهل يمثل المحرف علامة ترقيم وغيرها، ويتطلب ذلك استخدام الراية <code>u</code>.
</p>

<p>
	تدعم الراية <code>u</code> استخدام رموز Unicode في التعابير النمطية، ويعني ذلك أمرين اثنين:
</p>

<ol>
<li>
		ستتعامل الميزات والخصائص مع المحارف المكونة من أربع بايتات بشكل صحيح ومثل محرف واحد، وليس مثل محرفين يتكون كل منهما من بايتين.
	</li>
	<li>
		يمكن استخدام خصائص Unicode في عمليات البحث من خلال <code>{...}\p</code>.
	</li>
</ol>
<p>
	يمكن البحث عن كلمات في لغة محددة باستخدام خصائص Unicode، بالإضافة إلى البحث عن محارف خاصة مثل إشارات التنصيص، العملات،….
</p>

<ul>
<li>
		للبحث عن المحارف الخاصة <code>[ \ ^ $ . | ? * + ( )</code> بذاتها، لا بدّ من وضع الشرطة المائلة <code>\</code> قبلها لتجاوز الوظيفة الخاصة للمحرف.
	</li>
	<li>
		ينبغي تجاوز الشرطة المعكوسة <code>\</code> أيضًا في النمط <code>/.../</code>، لكن ليس ضمن <code>new RegExp</code>.
	</li>
	<li>
		عند تمرير نص إلى <code>new RegExp</code> فلا بدّ من مضاعفة الشرطة المعكوسة، لأن إشارة التنصيص <code>\\</code> تستهلك إحداهما.
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للفصول:
</p>

<ul>
<li>
		<a href="http://javascript.info/regexp-introduction" rel="external nofollow">Patterns and flags</a>
	</li>
	<li>
		<a href="http://javascript.info/regexp-character-classes" rel="external nofollow">character classes</a>
	</li>
	<li>
		<a href="http://javascript.info/regexp-unicode" rel="external nofollow">Unicode: flag "u" and class \p{…}</a>
	</li>
	<li>
		<a href="http://javascript.info/regexp-anchors" rel="external nofollow">Anchors: string start ^ and end $</a>
	</li>
	<li>
		<a href="http://javascript.info/regexp-multiline-mode" rel="external nofollow">Multiline mode of anchors ^ $, flag "m"</a>
	</li>
	<li>
		<a href="http://javascript.info/regexp-boundary" rel="external nofollow">Word boundary: \b</a>
	</li>
	<li>
		<a href="http://javascript.info/regexp-escaping" rel="external nofollow">Escaping special characters</a>
	</li>
</ul>
<p>
	من سلسلة <a href="https://javascript.info" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/html/%D9%85%D9%83%D9%88%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%B4%D8%AC%D8%B1%D8%A9-dom-%D8%A7%D9%84%D8%AE%D9%81%D9%8A%D8%A9-r1353/" rel="">مكونات الويب: التعامل مع شجرة DOM الخفية </a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/php/%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D8%A8%D9%8A%D8%B1-%D8%A7%D9%84%D9%86%D9%85%D8%B7%D9%8A%D8%A9-regexppcre-%D9%81%D9%8A-php-r1085/" rel="">التعابير النمطية (regexp/PCRE) في PHP</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/devops/linux/%d9%85%d9%82%d8%af%d9%85%d8%a9-%d9%81%d9%8a-%d8%a7%d9%84%d8%aa%d8%b9%d8%a7%d8%a8%d9%8a%d8%b1-%d8%a7%d9%84%d9%86%d9%85%d8%b7%d9%8a%d8%a9-regular-expressions-r63/" rel="">مقدمة في التعابير النمطية Regular Expressions</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%81%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r1341/" rel="">كيفية التعامل مع النصوص في البرمجة</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1399</guid><pubDate>Mon, 13 Dec 2021 16:00:00 +0000</pubDate></item><item><title>&#x625;&#x646;&#x634;&#x627;&#x621; &#x627;&#x644;&#x62D;&#x631;&#x643;&#x627;&#x62A; &#x639;&#x628;&#x631; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A7%D9%84%D8%AD%D8%B1%D9%83%D8%A7%D8%AA-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1351/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_10/6179186a8dbdd_-JavaScript-.png.83cdbd10ed8c03e4d3b26faf51d5664a.png" /></p>

<p>
	يمكن لرسوم JavaScript التعامل مع حالات لا يمكن أن تتعامل معها CSS، مثل التحرك على مسار معقد مختلف عن منحنيات بيزيه Bezier curves باستخدام دالة توقيت، أو رسوميات متحركة على لوحة رسم.
</p>

<h2>
	استخدام الدالة setInterval
</h2>

<p>
	يمكن إنجاز الرسوم المتحركة في صورة سلسلة من الإطارات، والتي تكون عادةً تغيرات صغيرةً في خصائص HTML/CSS، فعلى سبيل المثال: يؤدي تغيير قيمة الخاصية <code>style.left</code> من <code>0px</code> إلى <code>100px</code> إلى تحريك العنصر، وإذا زدنا هذه القيمة ضمن الدالة <code>setInterval</code>، فسيؤدي تغير مقداره <code>2px</code> مع تأخير ضئيل، لتكرار العملية بمقدار 50 مرةً في الثانية، مما يجعل الحركة سلسةً وناعمةً، ويُتَّبع هذا الأسلوب في السينما، فعرض 24 إطارًا في الثانية يجعل الصورة سلسةً.
</p>

<p>
	إليك الشيفرة المجردة pseudo-code للفكرة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_6" style="">
<span class="pln">let timer </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(</span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">animation complete</span><span class="pun">)</span><span class="pln"> clearInterval</span><span class="pun">(</span><span class="pln">timer</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">else</span><span class="pln"> increase style</span><span class="pun">.</span><span class="pln">left by </span><span class="lit">2px</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span><span class="pln"> </span><span class="com">// تغير بمقدار 2 بكسل بتأخير 20 ميلي ثانية يعطي 50 إطار في الثانية</span></pre>

<p>
	وهذا مثال أكثر تعقيدًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_8" style="">
<span class="pln">let start </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">();</span><span class="pln"> </span><span class="com">// تذكر وقت البدء</span><span class="pln">

let timer </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(</span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// كم مضى من الوقت منذ البداية</span><span class="pln">
  let timePassed </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> start</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timePassed </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    clearInterval</span><span class="pun">(</span><span class="pln">timer</span><span class="pun">);</span><span class="pln"> </span><span class="com">// أنهي الحركة بعد ثانيتين</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// ارسم إطار الحركة في اللحظة الحالية</span><span class="pln">
  draw</span><span class="pun">(</span><span class="pln">timePassed</span><span class="pun">);</span><span class="pln">

</span><span class="pun">},</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span><span class="pln">

</span><span class="com">// عندما يتغير الوقت بين 0 و2000 ميلي ثانية</span><span class="pln">
</span><span class="com">// تتغير قيمة الخاصية من 0 بكسل إلى 400 بكسل</span><span class="pln">
</span><span class="kwd">function</span><span class="pln"> draw</span><span class="pun">(</span><span class="pln">timePassed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  train</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> timePassed </span><span class="pun">/</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'px'</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إليك المثال النموذجي التالي:
</p>

<ul>
<li>
		شيفرة الملف index.html:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9071_10" style="">
<span class="dec">&lt;!DOCTYPE HTML&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">

</span><span class="tag">&lt;head&gt;</span><span class="pln">
  </span><span class="tag">&lt;style&gt;</span><span class="pln">
    </span><span class="com">#train {</span><span class="pln">
      position</span><span class="pun">:</span><span class="pln"> relative</span><span class="pun">;</span><span class="pln">
      cursor</span><span class="pun">:</span><span class="pln"> pointer</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">

  </span><span class="tag">&lt;img</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"train"</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://js.cx/clipart/train.gif"</span><span class="tag">&gt;</span><span class="pln">


  </span><span class="tag">&lt;script&gt;</span><span class="pln">
    train</span><span class="pun">.</span><span class="pln">onclick </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let start </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">();</span><span class="pln">

      let timer </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(</span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        let timePassed </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> start</span><span class="pun">;</span><span class="pln">

        train</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> timePassed </span><span class="pun">/</span><span class="pln"> </span><span class="lit">5</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'px'</span><span class="pun">;</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timePassed </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span><span class="pln"> clearInterval</span><span class="pun">(</span><span class="pln">timer</span><span class="pun">);</span><span class="pln">

      </span><span class="pun">},</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="tag">&lt;/script&gt;</span><span class="pln">


</span><span class="tag">&lt;/body&gt;</span><span class="pln">

</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	وستكون النتيجة:
</p>

<p>
	<iframe allowfullscreen="true" allowtransparency="true" data-ss1635235520="1" data-ss1635236603="1" frameborder="no" height="279" loading="lazy" scrolling="no" src="https://codepen.io/Hsoub/embed/KKvWLzK?default-tab=html%2Cresult" style="width: 100%;" title="JS-p3-05-JavaScript-animations -ex1">See the Pen JS-p3-05-JavaScript-animations -ex1 by Hsoub (@Hsoub) on CodePen.</iframe>
</p>

<h2>
	استخدام الدالة requestAnimationFrame
</h2>

<p>
	لنفترض تنفيذ عدة حركات انتقالية معًا. إذا شغّلنا هذه الرسوم منفصلةً فسيعيد المتصفح -وعلى الرغم من أنّ لكل حركة دالة <code>(setInterval(..., 20</code> خاصةً- رسم الصور بمعدل أكبر بكثير من 20 ميلي ثانية، ويحدث هذا لوجود أوقات بداية مختلفة لكل حركة، وبالتالي سيختلف تكرار التغيّر الذي ضبطناه عند 20 ميلي ثانية بالنسبة لكل حركة، وهكذا سنكوِّن حالات تشغيل مستقلةً لكل حركة انتقالية تتكرر كل 20 ميلي ثانية، وبكلمات أخرى انظر الشيفرة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_12" style="">
<span class="pln">setInterval</span><span class="pun">(</span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  animate1</span><span class="pun">();</span><span class="pln">
  animate2</span><span class="pun">();</span><span class="pln">
  animate3</span><span class="pun">();</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">20</span><span class="pun">)</span></pre>

<p>
	والتي ستكون أخفّ من ناحية التنفيذ على المتصفح من الشيفرة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_14" style="">
<span class="pln">setInterval</span><span class="pun">(</span><span class="pln">animate1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span><span class="pln"> </span><span class="com">// حركة انتقالية مستقلة</span><span class="pln">
setInterval</span><span class="pun">(</span><span class="pln">animate2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span><span class="pln"> </span><span class="com">// في أماكن مختلفة من السكربت</span><span class="pln">
setInterval</span><span class="pun">(</span><span class="pln">animate3</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">);</span></pre>

<p>
	ينبغي تجميع عمليات الرسم المستقلة لتسهيل الأمر على المتصفح، ولتخفيف الحِمل على وحدة المعالجة إلى جانب إظهار حركة أكثر نعومةً. تذكر دائمًا أنه يجب ألا نشغل عملية الرسم كل 20 ميلي ثانية، لأنها قد تزيد حمولة وحدة المعالجة، أو لوجود أسباب لتقليل عملية إعادة الرسم، مثل الحالة التي تكون ضمن نافذة مخفية للمتصفح. ولتمييز ذلك في JavaScript سنستخدم ميزةً تُدعى توقيت الحركة <a data-ss1635235520="1" data-ss1635236603="1" href="http://www.w3.org/TR/animation-timing/" rel="external nofollow">Animation timing</a>، والتي تزوّدنا بالدالة <code>requestAnimationFrame</code> التي تتعامل مع هذه الأمور وأكثر، وإليك صيغة استخدامها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_16" style="">
<span class="pln">let requestId </span><span class="pun">=</span><span class="pln"> requestAnimationFrame</span><span class="pun">(</span><span class="pln">callback</span><span class="pun">)</span></pre>

<p>
	تجدول الدالة <code>requestAnimationFrame</code> دالة الاستدعاء <code>callback</code> للعمل في أقرب وقت يريد فيه المتصفح تنفيذ الحركة، فإذا نفَّذنا أي تغييرات على العناصر ضمن الدالة <code>callback</code>، فستُجمّع مع غيرها من دوال الاستدعاء التي تجدولها الدالة <code>requestAnimationFrame</code> ومع رسوميات CSS، وهكذا سيعيد المتصفح الحسابات الهندسية، ثم يعيد الرسم مرةً واحدةً بدلًا من مرات متعددة.
</p>

<p>
	يمكن استخدام القيمة <code>requestId</code> التي تعيدها الدالة <code>requestAnimationFrame</code> في إلغاء الاستدعاء:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_18" style="">
<span class="com">// ألغ تنفيذ الاستدعاءات المجدولة</span><span class="pln">
cancelAnimationFrame</span><span class="pun">(</span><span class="pln">requestId</span><span class="pun">);</span></pre>

<p>
	لدالة الاستدعاء <code>callback</code> وسيط واحد، وهو الوقت الذي انقضى منذ بداية تحميل الصفحة مقدرًا بالميكروثانية، والذي يمكن الحصول عليه باستدعاء التابع <a data-ss1635235520="1" data-ss1635236603="1" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/now" rel="external nofollow">performance.now</a>.
</p>

<p>
	يُنفَّذ الاستدعاء <code>callback</code> مبكرًا إلا في حالة التحميل الزائد للمعالج، أو عندما تقارب بطارية الحاسوب المحمول على النفاد أو لأسباب مشابهة، وتظهر الشيفرة التالية الوقت المستغرق خلال مرات التنفيذ العشرة الأولى للدالة <code>requestAnimationFrame</code>، وهو عادةً بين 10-20 ميلي ثانية:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9071_20" style="">
<span class="tag">&lt;script&gt;</span><span class="pln">
  let prev </span><span class="pun">=</span><span class="pln"> performance</span><span class="pun">.</span><span class="pln">now</span><span class="pun">();</span><span class="pln">
  let times </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

  requestAnimationFrame</span><span class="pun">(</span><span class="kwd">function</span><span class="pln"> measure</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">insertAdjacentHTML</span><span class="pun">(</span><span class="str">"beforeEnd"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">(</span><span class="pln">time </span><span class="pun">-</span><span class="pln"> prev</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">" "</span><span class="pun">);</span><span class="pln">
    prev </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">times</span><span class="pun">++</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln"> requestAnimationFrame</span><span class="pun">(</span><span class="pln">measure</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	<iframe allowfullscreen="true" allowtransparency="true" data-ss1635235520="1" data-ss1635236603="1" frameborder="no" height="191" loading="lazy" scrolling="no" src="https://codepen.io/Hsoub/embed/preview/VwzpOaQ?default-tab=html%2Cresult" style="width: 100%;" title="JS-p3-05-JavaScript-animations -ex2">See the Pen JS-p3-05-JavaScript-animations -ex2 by Hsoub (@Hsoub) on CodePen.</iframe>
</p>

<h2>
	الرسومات المتحركة المهيكلة Structured animation
</h2>

<p>
	سننشئ الآن دالةً أكثر عموميةً مبنيةً على الدالة <code>requestAnimationFrame</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_22" style="">
<span class="kwd">function</span><span class="pln"> animate</span><span class="pun">({</span><span class="pln">timing</span><span class="pun">,</span><span class="pln"> draw</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  let start </span><span class="pun">=</span><span class="pln"> performance</span><span class="pun">.</span><span class="pln">now</span><span class="pun">();</span><span class="pln">

  requestAnimationFrame</span><span class="pun">(</span><span class="kwd">function</span><span class="pln"> animate</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// يتحرك الزمنين 0 و1</span><span class="pln">
    let timeFraction </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">time </span><span class="pun">-</span><span class="pln"> start</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> duration</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> timeFraction </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// حساب الحالة الراهنة للرسوم المتحركة</span><span class="pln">
    let progress </span><span class="pun">=</span><span class="pln"> timing</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln">

    draw</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">);</span><span class="pln"> </span><span class="com">// تنفيذ الرسم</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      requestAnimationFrame</span><span class="pun">(</span><span class="pln">animate</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تقبل الدالة <code>animate</code> ثلاثة معاملات تصف الحركة، وهي:
</p>

<ul>
<li>
		<code>duration</code>: الزمن الكلي لتنفيذ الحركة مقدرًا بالميلي ثانية.
	</li>
	<li>
		<code>(timing(timeFraction</code>: دالة توقيت تشابه الخاصية <code>transition-timing-function</code> في CSS، وتعطي نسبة الوقت الذي انقضى (0 عند البداية و1 عند النهاية)، وتعيد ما يدل على اكتمال الحركة، مثل الإحداثي <code>y</code> عند رسم منحني بيزيه، ولنتذكر أن الدالة الخطية تعني أن الحركة ستتقدم بانتظام وبالسرعة ذاتها:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_33" style="">
<span class="kwd">function</span><span class="pln"> linear</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> timeFraction</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وسيبدو الرسم البياني للدالة الخطية كالتالي:
</p>

<p style="text-align: center;">
	<img alt="linear_funcatio_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80733" data-unique="baek1t3wp" src="https://academy.hsoub.com/uploads/monthly_2021_10/linear_funcatio_01.png.29fb1764df14335a54240c4c42579e7a.png"></p>

<p>
	وهي مشابهة تمامًا للخاصية <code>transition-timing-function</code>، وسنرى لاحقًا بعض أشكال الاستخدام الأخرى.
</p>

<ul>
<li>
		<code>(draw(progress</code>: وهي الدالة التي تأخذ معاملًا هو مقدار اكتمال الحركة وترسمه، وتشير القيمة <code>progress=0</code> إلى حالة بداية الحركة، بينما تشير القيمة <code>progress=1</code> إلى حالة النهاية، فهي الدالة التي ترسم الحركة فعليًا، إذا يمكنها نقل عنصر مثلًا:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_29" style="">
<span class="kwd">function</span><span class="pln"> draw</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  train</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> progress </span><span class="pun">+</span><span class="pln"> </span><span class="str">'px'</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أو تنفيذ أي شيء آخر، وبالتالي يمكننا تحريك أي شيء بالطريقة التي نريد، لنحرّك العنصر <code>width</code> من 0 حتى 100% باستخدام هذه الدالة:
</p>

<ul>
<li>
		شيفرة الملف animate.js:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_35" style="">
<span class="pln">unction animate</span><span class="pun">({</span><span class="pln">duration</span><span class="pun">,</span><span class="pln"> draw</span><span class="pun">,</span><span class="pln"> timing</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  let start </span><span class="pun">=</span><span class="pln"> performance</span><span class="pun">.</span><span class="pln">now</span><span class="pun">();</span><span class="pln">

  requestAnimationFrame</span><span class="pun">(</span><span class="kwd">function</span><span class="pln"> animate</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let timeFraction </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">time </span><span class="pun">-</span><span class="pln"> start</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> duration</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> timeFraction </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">

    let progress </span><span class="pun">=</span><span class="pln"> timing</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln">

    draw</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      requestAnimationFrame</span><span class="pun">(</span><span class="pln">animate</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<ul>
<li>
		شيفرة الملف index.html:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9071_37" style="">
<span class="dec">&lt;!DOCTYPE HTML&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">

</span><span class="tag">&lt;head&gt;</span><span class="pln">
  </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;style&gt;</span><span class="pln">
    progress </span><span class="pun">{</span><span class="pln">
      width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">%;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="tag">&lt;/style&gt;</span><span class="pln">
  </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"animate.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">


  </span><span class="tag">&lt;progress</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"elem"</span><span class="tag">&gt;&lt;/progress&gt;</span><span class="pln">

  </span><span class="tag">&lt;script&gt;</span><span class="pln">
    elem</span><span class="pun">.</span><span class="pln">onclick </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      animate</span><span class="pun">({</span><span class="pln">
        duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">,</span><span class="pln">
        timing</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> timeFraction</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">},</span><span class="pln">
        draw</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          elem</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> progress </span><span class="pun">*</span><span class="pln"> </span><span class="lit">100</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'%'</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="tag">&lt;/script&gt;</span><span class="pln">


</span><span class="tag">&lt;/body&gt;</span><span class="pln">

</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	ستكون النتيجة كالتالي:
</p>

<p>
	<iframe class="code-tabs__result" data-ss1635235520="1" data-ss1635236603="1" src="https://javascript.info/article/js-animation/width/" style="display: block; height: 42px;"></iframe>
</p>

<p>
	وإليك الشيفرة المستخدمة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_39" style="">
<span class="pln">animate</span><span class="pun">({</span><span class="pln">
  duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">,</span><span class="pln">
  timing</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> timeFraction</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">},</span><span class="pln">
  draw</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    elem</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> progress </span><span class="pun">*</span><span class="pln"> </span><span class="lit">100</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'%'</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يمكننا بهذا الأسلوب تنفيذ أي دوال توقيت ورسم على خلاف CSS، ولا ترتبط دوال التوقيت بمنحني بيزيه فقط، كما يمكن للدالة <code>draw</code> أن تتخطى الخصائص إلى إنشاء عناصر جديدة لرسوميات نحتاجها، مثل الألعاب النارية.
</p>

<h2>
	دوال التوقيت
</h2>

<p>
	اطلعنا في الفقرات السابقة على أبسط الدوال وهي الدالة الخطية، لنرى الآن بعض الدوال الأخرى، حيث سنجرب بعض الحركات الانتقالية باستخدام دوال توقيت مختلفة.
</p>

<h3>
	دالة القوة من الدرجة n
</h3>

<p>
	يمكن استعمال <code>progress</code> بدلالة القوة من الدرجة <code>n</code> لتسريع الحركة، مثل الدالة التربيعية (أي من الدرجة 2):
</p>

<pre class="ipsCode">
function quad(timeFraction) {
  return Math.pow(timeFraction, 2)
}
</pre>

<p>
	إليك الرسم البياني:
</p>

<p style="text-align: center;">
	<img alt="timing_funcatio_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80735" data-unique="m61q2e3ou" src="https://academy.hsoub.com/uploads/monthly_2021_10/timing_funcatio_02.png.4f6743df3565443c9816eb5607cc6658.png"></p>

<p>
	لترى النتيجة انقر على الشكل التالي:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/article/js-animation/quad/" style="display: block; height: 40px;"></iframe>
</p>

<p>
	كما يمكنك استعمال الدالة التكعيبية (من الدرجة 3)، وسترى أن سرعة الحركة ستزداد بزيادة درجة القوة، إليك نموذجًا تكون فيه <code>progress</code> من الدرجة 5:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/article/js-animation/quint/" style="display: block;  height: 40px;"></iframe>
</p>

<h3>
	الدالة المثلثية القطعية arc
</h3>

<p>
	صيغة الدالة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_41" style="">
<span class="kwd">function</span><span class="pln"> circ</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">sin</span><span class="pun">(</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">acos</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الرسم البياني:
</p>

<p style="text-align: center;">
	<img alt="arc_funcatio_03.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80730" data-unique="ordpizlz2" src="https://academy.hsoub.com/uploads/monthly_2021_10/arc_funcatio_03.png.5d3844c3fe36dfe788c0d068aff5451a.png"></p>

<p>
	المثال النموذج:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/article/js-animation/circ/" style="display: block; height: 40px;"></iframe>
</p>

<h3>
	دالة إطلاق السهم back
</h3>

<p>
	عند اطلاق السهم bow shooting فسنسحب وتر القوس ثم نحرره، وخلافًا للدالتين السابقتين، ستعتمد الدالة على معامل إضافي <code>x</code> هو ثابت المرونة elasticity coefficient، والذي يُعرِّف المسافة التي نسحب بها وتر القوس:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_43" style="">
<span class="kwd">function</span><span class="pln"> back</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="pun">((</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction </span><span class="pun">-</span><span class="pln"> x</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الخط البياني للدالة عندما <code>x = 1.5</code>:
</p>

<p style="text-align: center;">
	<img alt="bow_shooting_funcatio_04.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80731" data-unique="m2xqbv5g8" src="https://academy.hsoub.com/uploads/monthly_2021_10/bow_shooting_funcatio_04.png.155180458a4ba0bb51964458f80298aa.png"></p>

<p>
	المثال النموذجي عند نفس القيمة للمعامل <code>x</code>:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/article/js-animation/back/" style="display: block; height: 40px;"></iframe>
</p>

<h3>
	دالة الارتداد bounce
</h3>

<p>
	عندما نرمي كرةً فستسقط للأسفل وترتد عدة مرات ثم تتوقف. تسلك الدالة <code>bounce</code> هذا السلوك تمامًا لكن بترتيب معكوس، حيث يبدأ الارتداد مباشرةً، وتستخدم هذه الدالة بعض الثوابت الخاصة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_45" style="">
<span class="kwd">function</span><span class="pln"> bounce</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> result</span><span class="pun">;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> a </span><span class="pun">+=</span><span class="pln"> b</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">/=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">7</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">11</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">-</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">((</span><span class="lit">11</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a </span><span class="pun">-</span><span class="pln"> </span><span class="lit">11</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">(</span><span class="pln">b</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إليك نموذجًا يستخدم دالة الإرتداد:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/article/js-animation/bounce/" style="display: block; height: 40px;"></iframe>
</p>

<h2>
	دالة الحركة المرنة elastic
</h2>

<p>
	إليك دالةً أخرى "مرنةً" تقبل معاملًا إضافيًا <code>x</code> يضبط المجال الابتدائي للحركة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_47" style="">
<span class="kwd">function</span><span class="pln"> elastic</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">))</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">cos</span><span class="pun">(</span><span class="lit">20</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI </span><span class="pun">*</span><span class="pln"> x </span><span class="pun">/</span><span class="pln"> </span><span class="lit">3</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الخط البياني للدالة عندما <code>x=1.5</code>:
</p>

<p style="text-align: center;">
	<img alt="elastic_funcatio_05.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80732" data-unique="8f8r3s0iu" src="https://academy.hsoub.com/uploads/monthly_2021_10/elastic_funcatio_05.png.1517a87177d77ff4b1ceb6a19736041c.png"></p>

<p>
	إليك نموذجًا عن استخدام الدالة:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/article/js-animation/elastic/" style="display: block; height: 40px;"></iframe>
</p>

<h2>
	الدوال بترتيب معكوس
</h2>

<p>
	تعرفنا حتى اللحظة على دوال التوقيت التي تُعرف بتحويلات الدخول السهل easeIn، لكننا قد نحتاج أحيانًا إلى عرض الحركة بترتيب معكوس. تُنفَّذ هذه الحركات بالتحويل الذي يُعرف باسم الخروج السهل easeOut.
</p>

<h3>
	التحويل easeOut
</h3>

<p>
	تُوضع الدالة <code>timing</code> في هذا التحويل ضمن المُغلِّف <code>timingEaseOut</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_49" style="">
<span class="pln">timingEaseOut</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timeFraction</span><span class="pun">)</span></pre>

<p>
	بعبارة أخرى لدينا دالة التحويل <code>makeEaseOut</code> التي تقبل دالة توقيت نظاميةً وتعيد المُغلّف الذي يحيط بها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_51" style="">
<span class="com">// accepts a timing function, returns the transformed variant</span><span class="pln">
</span><span class="kwd">function</span><span class="pln"> makeEaseOut</span><span class="pun">(</span><span class="pln">timing</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timeFraction</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يمكن على سبيل المثال اختيار الدالة <code>bounce</code> التي شرحناها سابقًا وتطبيقها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_53" style="">
<span class="pln">let bounceEaseOut </span><span class="pun">=</span><span class="pln"> makeEaseOut</span><span class="pun">(</span><span class="pln">bounce</span><span class="pun">);</span></pre>

<p>
	وهكذا لن تكون الحركة الارتدادية في بداية الحركة بل في نهايتها، وستبدو الحركة أفضل:
</p>

<ul>
<li>
		شيفرة الملف style.css:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_9071_55" style="">
<span class="com">#brick {</span><span class="pln">
  width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">40px</span><span class="pun">;</span><span class="pln">
  height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
  background</span><span class="pun">:</span><span class="pln"> </span><span class="com">#EE6B47;</span><span class="pln">
  position</span><span class="pun">:</span><span class="pln"> relative</span><span class="pun">;</span><span class="pln">
  cursor</span><span class="pun">:</span><span class="pln"> pointer</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">#path {</span><span class="pln">
  outline</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1px</span><span class="pln"> solid </span><span class="com">#E8C48E;</span><span class="pln">
  width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">540px</span><span class="pun">;</span><span class="pln">
  height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<ul>
<li>
		شيفرة الملف index.html:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9071_57" style="">
<span class="dec">&lt;!DOCTYPE HTML&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">

</span><span class="tag">&lt;head&gt;</span><span class="pln">
  </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"style.css"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://js.cx/libs/animate.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">


  </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"path"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"brick"</span><span class="tag">&gt;&lt;/div&gt;</span><span class="pln">
  </span><span class="tag">&lt;/div&gt;</span><span class="pln">

  </span><span class="tag">&lt;script&gt;</span><span class="pln">
    </span><span class="kwd">function</span><span class="pln"> makeEaseOut</span><span class="pun">(</span><span class="pln">timing</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timeFraction</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="kwd">function</span><span class="pln"> bounce</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> result</span><span class="pun">;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> a </span><span class="pun">+=</span><span class="pln"> b</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">/=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">7</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">11</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">-</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">((</span><span class="lit">11</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a </span><span class="pun">-</span><span class="pln"> </span><span class="lit">11</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">(</span><span class="pln">b</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    let bounceEaseOut </span><span class="pun">=</span><span class="pln"> makeEaseOut</span><span class="pun">(</span><span class="pln">bounce</span><span class="pun">);</span><span class="pln">

    brick</span><span class="pun">.</span><span class="pln">onclick </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      animate</span><span class="pun">({</span><span class="pln">
        duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">,</span><span class="pln">
        timing</span><span class="pun">:</span><span class="pln"> bounceEaseOut</span><span class="pun">,</span><span class="pln">
        draw</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          brick</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> progress </span><span class="pun">*</span><span class="pln"> </span><span class="lit">500</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'px'</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="tag">&lt;/script&gt;</span><span class="pln">


</span><span class="tag">&lt;/body&gt;</span><span class="pln">

</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	وستكون النتيجة:
</p>

<p>
	<iframe class="code-tabs__result" data-ss1635235520="1" data-ss1635236603="1" src="https://javascript.info/article/js-animation/bounce-easeout/" style="display: block; height: 182px;"></iframe>
</p>

<p>
	سنرى هنا كيف سيغيّر التحويل سلوك الدالة، إذ ستعرَض التأثيرات الموجودة في بداية الحركة -مثل الارتداد- في النهاية، ففي الشكل التالي ستجد الارتداد الاعتيادي باللون الأحمر، والارتداد وفق تحويل الخروج السهل باللون الأزرق.
</p>

<p style="text-align: center;">
	<img alt="regular_vs_easeout_bounce_06.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80734" data-unique="u2uz8rmsb" src="https://academy.hsoub.com/uploads/monthly_2021_10/regular_vs_easeout_bounce_06.png.1c31803fddec6b5be8c2f6f1f6e812e9.png"></p>

<ul>
<li>
		الارتداد الاعتيادي: يرتد الكائن عند القاع، ثم يقفز بحدّة إلى القمة في النهاية.
	</li>
	<li>
		باستخدام <code>easeOut</code> الخروج السهل: يقفز أولًا إلى القمة ثم يرتد هناك.
	</li>
</ul>
<h3>
	التحويل easeInOut
</h3>

<p>
	يمكن أن نُظهر التأثير المطلوب في بداية ونهاية الحركة معًا، ويُدعى هذا التحويل بالدخول والخروج السهل easeInOut. سنحسب حالة الحركة باعتماد تابع توقيت ما كالتالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_59" style="">
<span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&lt;=</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">//  نصف الحركة الأول</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// النصف الثاني للحركة</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timeFraction</span><span class="pun">)))</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<ul>
<li>
		شيفرة المُغلِّف:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_61" style="">
<span class="kwd">function</span><span class="pln"> makeEaseInOut</span><span class="pun">(</span><span class="pln">timing</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&lt;</span><span class="pln"> </span><span class="pun">.</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">else</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timeFraction</span><span class="pun">)))</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

bounceEaseInOut </span><span class="pun">=</span><span class="pln"> makeEaseInOut</span><span class="pun">(</span><span class="pln">bounce</span><span class="pun">);</span></pre>

<p>
	إليك مثالًا نموذجيًا:
</p>

<ul>
<li>
		شيفرة الملف style.css:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_9071_63" style="">
<span class="com">#brick {</span><span class="pln">
  width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">40px</span><span class="pun">;</span><span class="pln">
  height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
  background</span><span class="pun">:</span><span class="pln"> </span><span class="com">#EE6B47;</span><span class="pln">
  position</span><span class="pun">:</span><span class="pln"> relative</span><span class="pun">;</span><span class="pln">
  cursor</span><span class="pun">:</span><span class="pln"> pointer</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">#path {</span><span class="pln">
  outline</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1px</span><span class="pln"> solid </span><span class="com">#E8C48E;</span><span class="pln">
  width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">540px</span><span class="pun">;</span><span class="pln">
  height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<ul>
<li>
		شيفرة الملف index.html:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9071_65" style="">
<span class="dec">&lt;!DOCTYPE HTML&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">

</span><span class="tag">&lt;head&gt;</span><span class="pln">
  </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"style.css"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://js.cx/libs/animate.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">


  </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"path"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"brick"</span><span class="tag">&gt;&lt;/div&gt;</span><span class="pln">
  </span><span class="tag">&lt;/div&gt;</span><span class="pln">

  </span><span class="tag">&lt;script&gt;</span><span class="pln">
    </span><span class="kwd">function</span><span class="pln"> makeEaseInOut</span><span class="pun">(</span><span class="pln">timing</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&lt;</span><span class="pln"> </span><span class="pun">.</span><span class="lit">5</span><span class="pun">)</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">else</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timing</span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> timeFraction</span><span class="pun">)))</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    </span><span class="kwd">function</span><span class="pln"> bounce</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> result</span><span class="pun">;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> a </span><span class="pun">+=</span><span class="pln"> b</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">/=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">7</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">11</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">-</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">((</span><span class="lit">11</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a </span><span class="pun">-</span><span class="pln"> </span><span class="lit">11</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">(</span><span class="pln">b</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    let bounceEaseInOut </span><span class="pun">=</span><span class="pln"> makeEaseInOut</span><span class="pun">(</span><span class="pln">bounce</span><span class="pun">);</span><span class="pln">

    brick</span><span class="pun">.</span><span class="pln">onclick </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      animate</span><span class="pun">({</span><span class="pln">
        duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3000</span><span class="pun">,</span><span class="pln">
        timing</span><span class="pun">:</span><span class="pln"> bounceEaseInOut</span><span class="pun">,</span><span class="pln">
        draw</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          brick</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> progress </span><span class="pun">*</span><span class="pln"> </span><span class="lit">500</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'px'</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="tag">&lt;/script&gt;</span><span class="pln">


</span><span class="tag">&lt;/body&gt;</span><span class="pln">

</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	وستكون النتيجة:
</p>

<p>
	<iframe class="code-tabs__result" data-ss1635235520="1" data-ss1635236603="1" src="https://javascript.info/article/js-animation/bounce-easeinout/" style="display: block;  height: 182px;"></iframe>
</p>

<p>
	يدمج التحويل كائنين رسوميين معًا، وهما التحويل <code>easeIn</code> (الاعتيادي) في النصف الأول للحركة، والتحويل <code>easeOut</code> (المعكوس) للنصف الثاني. سنرى التأثير بوضوح عند الموازنة بين التحويلات الثلاث <code>easeIn</code> و<code>easeOut</code> و<code>easeInOut</code> عند تطبيقها على دالة التوقيت <code>circ</code>:
</p>

<p style="text-align: center;">
	<img alt="transform_compare_07.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80736" data-unique="j6579hp2t" src="https://academy.hsoub.com/uploads/monthly_2021_10/transform_compare_07.png.d7d5d7d6a6793faa53851f6070caa640.png"></p>

<ul>
<li>
		<code>easeIn</code>: باللون الأحمر.
	</li>
	<li>
		<code>easeOut</code>: باللون الأخضر.
	</li>
	<li>
		<code>easeInOut</code>: باللون الأزرق.
	</li>
</ul>
<p>
	كما نرى طبقنا على النصف الأول من الحركة <code>easeIn</code>، وعلى النصف الآخر <code>easeOut</code>، لذا ستبدأ الحركة وتنتهي بنفس التأثير.
</p>

<h2>
	دالة draw أكثر تميزًا
</h2>

<p>
	يمكننا القيام بأكثر من مجرد تحريك العنصر، وكل ما علينا فعله هو كتابة شيفرة مناسبة للدالة <code>draw</code>، إليك طريقةً لإظهار ارتداد أثناء كتابة نص مثلًا:
</p>

<ul>
<li>
		شيفرة الملف style.css:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_9071_67" style="">
<span class="pln">textarea </span><span class="pun">{</span><span class="pln">
  display</span><span class="pun">:</span><span class="pln"> block</span><span class="pun">;</span><span class="pln">
  border</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1px</span><span class="pln"> solid </span><span class="com">#BBB;</span><span class="pln">
  color</span><span class="pun">:</span><span class="pln"> </span><span class="com">#444;</span><span class="pln">
  font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">110</span><span class="pun">%;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

button </span><span class="pun">{</span><span class="pln">
  margin</span><span class="pun">-</span><span class="pln">top</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<ul>
<li>
		شيفرة الملف index.html:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_9071_69" style="">
<span class="dec">&lt;!DOCTYPE HTML&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">

</span><span class="tag">&lt;head&gt;</span><span class="pln">
  </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"style.css"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://js.cx/libs/animate.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/head&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">


  </span><span class="tag">&lt;textarea</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"textExample"</span><span class="pln"> </span><span class="atn">rows</span><span class="pun">=</span><span class="atv">"5"</span><span class="pln"> </span><span class="atn">cols</span><span class="pun">=</span><span class="atv">"60"</span><span class="tag">&gt;</span><span class="pln">He took his vorpal sword in hand:
Long time the manxome foe he sought—
So rested he by the Tumtum tree,
And stood awhile in thought.
  </span><span class="tag">&lt;/textarea&gt;</span><span class="pln">

  </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">onclick</span><span class="pun">=</span><span class="atv">"</span><span class="pln">animateText</span><span class="pun">(</span><span class="pln">textExample</span><span class="pun">)</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">Run the animated typing!</span><span class="tag">&lt;/button&gt;</span><span class="pln">

  </span><span class="tag">&lt;script&gt;</span><span class="pln">
    </span><span class="kwd">function</span><span class="pln"> animateText</span><span class="pun">(</span><span class="pln">textArea</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let text </span><span class="pun">=</span><span class="pln"> textArea</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln">
      let to </span><span class="pun">=</span><span class="pln"> text</span><span class="pun">.</span><span class="pln">length</span><span class="pun">,</span><span class="pln">
        from </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

      animate</span><span class="pun">({</span><span class="pln">
        duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5000</span><span class="pun">,</span><span class="pln">
        timing</span><span class="pun">:</span><span class="pln"> bounce</span><span class="pun">,</span><span class="pln">
        draw</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          let result </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">to </span><span class="pun">-</span><span class="pln"> from</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> progress </span><span class="pun">+</span><span class="pln"> from</span><span class="pun">;</span><span class="pln">
          textArea</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> text</span><span class="pun">.</span><span class="pln">substr</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">ceil</span><span class="pun">(</span><span class="pln">result</span><span class="pun">))</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">


    </span><span class="kwd">function</span><span class="pln"> bounce</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let a </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> result</span><span class="pun">;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> a </span><span class="pun">+=</span><span class="pln"> b</span><span class="pun">,</span><span class="pln"> b </span><span class="pun">/=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">7</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">11</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">-</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">((</span><span class="lit">11</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">6</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> a </span><span class="pun">-</span><span class="pln"> </span><span class="lit">11</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> timeFraction</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">4</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">pow</span><span class="pun">(</span><span class="pln">b</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="tag">&lt;/script&gt;</span><span class="pln">


</span><span class="tag">&lt;/body&gt;</span><span class="pln">

</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	وستكون النتيجة كالتالي:
</p>

<p>
	<iframe class="code-tabs__result" data-ss1635235520="1" data-ss1635236603="1" src="https://javascript.info/article/js-animation/text/" style="display: block; border: 0px; height: 182px;"></iframe>
</p>

<h2>
	خلاصة
</h2>

<p>
	يمكن أن تساعدك JavaScript في تنفيذ الرسوميات التي لا تستطيع CSS التعامل معها، أو تلك التي تتطلب تحكمًا دقيقًا.
</p>

<p>
	تّنفّذ حركات JavaScript باستخدام التابع المدمج <code>requestAnimationFrame</code> الذي يسمح بإعداد دالة استدعاء تُشغَّل عندما يحضّر المتصفح نفسه لعملية إعادة الرسم Repaint. لن تُنفَّذ عملية إعادة الرسم إطلاقًا عندما تكون الصفحة في الخلفية، وبالتالي لن تعمل دالة الاستدعاء وسيُعلّق تنفيذ الحركة، ولن يكون هناك استهلاك للموارد.
</p>

<p>
	إليك الدالة المساعدة <code>animate</code> التي تحضّر معظم الرسوميات المتحركة التي تحتاجها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_71" style="">
<span class="kwd">function</span><span class="pln"> animate</span><span class="pun">({</span><span class="pln">timing</span><span class="pun">,</span><span class="pln"> draw</span><span class="pun">,</span><span class="pln"> duration</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  let start </span><span class="pun">=</span><span class="pln"> performance</span><span class="pun">.</span><span class="pln">now</span><span class="pun">();</span><span class="pln">

  requestAnimationFrame</span><span class="pun">(</span><span class="kwd">function</span><span class="pln"> animate</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// timeFraction goes from 0 to 1</span><span class="pln">
    let timeFraction </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">time </span><span class="pun">-</span><span class="pln"> start</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> duration</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> timeFraction </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// calculate the current animation state</span><span class="pln">
    let progress </span><span class="pun">=</span><span class="pln"> timing</span><span class="pun">(</span><span class="pln">timeFraction</span><span class="pun">);</span><span class="pln">

    draw</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">);</span><span class="pln"> </span><span class="com">// draw it</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">timeFraction </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      requestAnimationFrame</span><span class="pun">(</span><span class="pln">animate</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الخيارات هي:
</p>

<ul>
<li>
		<code>duration</code>: الزمن الكلي للحركة مقدرًا بالميلي ثانية.
	</li>
	<li>
		<code>timing</code>: الدالة التي تحسب مقدار تقدم الحركة، حيث تقبل الدالة قيمًا زمنيةً هي نسبة بين 0 و1، وتعيد مقدار تقدم العملية.
	</li>
	<li>
		<code>draw</code>: الدالة التي ترسم الحركة.
	</li>
</ul>
<p>
	وبالطبع يمكن تحسين هذه الدوال وإضافة العديد من الأمور الأخرى، لكن لا يتكرر استخدام رسوميات JavaScript كثيرًا، فهي تستخدَم لإظهار شيء مهم لا لأمور تقليدية، لذا يمكنك إضافة الميزة التي تريدها عند الحاجة.
</p>

<p>
	يمكن أن تستخدم JavaScript أي دوال توقيت، وقد غطينا الكثير منها في هذا الفصل وطبقنا عليها تحويلات عدةً، إذًا لسنا مقيدين بمنحنيات بيزيه كما هي الحال في CSS.
</p>

<p>
	يمكننا استخدام <code>draw</code> لتحريك أي شيء وليس خصائص CSS فقط.
</p>

<h2>
	مهام لإنجازها
</h2>

<h3>
	1. تحريك كرة مرتدة
</h3>

<p>
	أنشئ كرةً ترتد كما في المثال التالي:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/task/animate-ball/solution/" style="display: block;  height: 250px;"></iframe>
</p>

<p>
	<a data-ss1635235520="1" data-ss1635236603="1" href="https://plnkr.co/edit/co0XGJ1r0UGWPZCw?p=preview" rel="external nofollow">افتح المثال في بيئة تجريبية</a>.
</p>

<h3>
	الحل
</h3>

<p>
	لجعل الكرة ترتد، سنسنتعمل الخاصية <code>top</code> والخاصية <code>position:absolute</code> مع الكرة والخاصية <code>position:relative</code> مع الملعب، ويكن إحداثيات أرضية الملعب هي <code>field.clientHeight</code>. تشير الخاصية <code>top</code> إلى بداية أعلى الملعب لذا يجب أن تتغير من 0 إلى <code>field.clientHeight - ball.clientHeight</code> وهو أدنى موضع يمكن أن تنخفض إليه حافة الكرة العلوية.
</p>

<p>
	يمكن تطبيق تأثير الارتداد باستعمال دالة التوقيت <code>bounce</code> في الوضع <code>easeOut</code>.
</p>

<p>
	إليك الشيفرة النهاية الناتجة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_73" style="">
<span class="pln">let to </span><span class="pun">=</span><span class="pln"> field</span><span class="pun">.</span><span class="pln">clientHeight </span><span class="pun">-</span><span class="pln"> ball</span><span class="pun">.</span><span class="pln">clientHeight</span><span class="pun">;</span><span class="pln">

animate</span><span class="pun">({</span><span class="pln">
  duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">,</span><span class="pln">
  timing</span><span class="pun">:</span><span class="pln"> makeEaseOut</span><span class="pun">(</span><span class="pln">bounce</span><span class="pun">),</span><span class="pln">
  draw</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ball</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> to </span><span class="pun">*</span><span class="pln"> progress </span><span class="pun">+</span><span class="pln"> </span><span class="str">'px'</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	<a data-ss1635235520="1" data-ss1635236603="1" href="https://plnkr.co/edit/ltbXZe5pp7wqNTN6?p=preview" rel="external nofollow">افتح الحل في بيئة تجريبية</a>.
</p>

<h4>
	2. حرك الكرة المرتدة إلى اليمين
</h4>

<p>
	اجعل الكرة ترتد إلى اليمين كالتالي:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635235520="1" data-ss1635236603="1" data-trusted="1" src="https://en.js.cx/task/animate-ball-hops/solution/" style="display: block; height: 250px;"></iframe>
</p>

<p>
	اكتب الشيفرة بحيث تكون مسافة الانتقال إلى اليمين هي <code>100px</code>.
</p>

<h3>
	الحل
</h3>

<p>
	احتجنا في التمرين السابق إلى تحريك خاصية واحدة فقط، بينما سنحتاج في هذا التمرين إلى تحريك خاصية إضافية هي <code>elem.style.left</code>.
</p>

<p>
	نريد تغيير الاحداثيات الأفقية للكرة بزيادتها تدريجيًا نحو اليمين أثناء السقوط، لذا سنضيف حركة إضافة <code>anumate</code> يمكن أن نستعمل معها دالة التوقيت <code>linear</code> ولكن تبدو <code>makeEaseOut(quad)‎</code> أفضل بكثير.
</p>

<p>
	إليك الشيفرة النهاية الناتجة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9071_75" style="">
<span class="pln">let height </span><span class="pun">=</span><span class="pln"> field</span><span class="pun">.</span><span class="pln">clientHeight </span><span class="pun">-</span><span class="pln"> ball</span><span class="pun">.</span><span class="pln">clientHeight</span><span class="pun">;</span><span class="pln">
let width </span><span class="pun">=</span><span class="pln"> </span><span class="lit">100</span><span class="pun">;</span><span class="pln">

</span><span class="com">// animate top (bouncing)</span><span class="pln">
animate</span><span class="pun">({</span><span class="pln">
  duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">,</span><span class="pln">
  timing</span><span class="pun">:</span><span class="pln"> makeEaseOut</span><span class="pun">(</span><span class="pln">bounce</span><span class="pun">),</span><span class="pln">
  draw</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ball</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> height </span><span class="pun">*</span><span class="pln"> progress </span><span class="pun">+</span><span class="pln"> </span><span class="str">'px'</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// animate left (moving to the right)</span><span class="pln">
animate</span><span class="pun">({</span><span class="pln">
  duration</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">,</span><span class="pln">
  timing</span><span class="pun">:</span><span class="pln"> makeEaseOut</span><span class="pun">(</span><span class="pln">quad</span><span class="pun">),</span><span class="pln">
  draw</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">progress</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ball</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> width </span><span class="pun">*</span><span class="pln"> progress </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	<a data-ss1635235520="1" data-ss1635236603="1" href="https://plnkr.co/edit/rnGU4RsR71ShA8kE?p=preview" rel="external nofollow">افتح الحل في بيئة تجريبية</a>.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1635235520="1" data-ss1635236603="1" href="https://javascript.info/js-animation" rel="external nofollow">JavaScript Animation</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/css/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%B1%D8%B3%D9%88%D9%85-%D9%85%D8%AA%D8%AD%D8%B1%D9%83%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-css-r1340/" rel="">إنشاء رسوم متحركة باستخدام CSS</a>
	</li>
	<li>
		النسخة العربية الكاملة لكتاب: <a href="https://academy.hsoub.com/files/14-%D8%A7%D9%84%D8%AA%D8%AD%D8%B1%D9%8A%D9%83-%D8%B9%D8%A8%D8%B1-css/" rel="">التحريك عبر CSS</a>
	</li>
</ul>
<style type="text/css">
iframe {
   border: 1px solid #e7e5e3 !important;
width: 100%;
}
iframe div#path {
    margin: auto;
}</style>
]]></description><guid isPermaLink="false">1351</guid><pubDate>Sat, 30 Oct 2021 15:08:00 +0000</pubDate></item><item><title>HTTP &#x648;&#x627;&#x644;&#x627;&#x633;&#x62A;&#x645;&#x627;&#x631;&#x627;&#x62A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/http-%D9%88%D8%A7%D9%84%D8%A7%D8%B3%D8%AA%D9%85%D8%A7%D8%B1%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1367/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_11/61825c8dca1f2_HTTP-01.jpg.4eefd259c87eeaa67cf316238d12f191.jpg" /></p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		يجب ألا تكون الاتصالات ذات طبيعة حالية، إذ يجب أن يحتوي كل طلب من العميل إلى الخادم على جميع المعلومات الضرورية لفهم الطلب، ولا يحتاج إلى أيّ سياق مخزَّن على الخادم، بل يجب ألا يتمكن من استغلاله.
	</p>

	<p>
		ـــ روي فيلدينج Roy Fielding، كتاب الأنماط المعمارية وتصميم معماريات البرمجيات المبنية على الشبكات Architectural Styles and the Design of Network-based Software Architectures.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81271" href="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_18.jpg.b5fa7473ace0b8b9d312901facf80ef4.jpg" rel=""><img alt="chapter_picture_18.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="81271" data-unique="d35u89si2" src="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_18.jpg.b5fa7473ace0b8b9d312901facf80ef4.jpg"></a>
</p>

<p>
	يُعَدّ بروتوكول نقل النصوص الفائقة Hypertext Transfer Protocol الذي ذكرناه في مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D8%A8%D8%AA%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-r1310/" rel="">علاقة جافاسكريبت بتطور الإنترنت والمتصفحات </a>آليةً تُطلب البيانات وتوفَّر من خلالها على الشبكة العالمية، كما سننظر فيه بالتفصيل ونشرح الطريقة التي تستخدِمه بها جافاسكربت المتصفحات.
</p>

<h2>
	البروتوكول
</h2>

<p>
	إذا كتبت eloquentjavascript.net/18_http.html في شريط العنوان لمتصفحك، فسيبحث المتصفح أولًا عن عنوان الخادم المرتبط بـ eloquentjavascript.net ويحاول فتح اتصال TCP معه على المنفَذ 80 الذي هو المنفَذ الافتراضي لحركة مرور <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">HTTP</a>، فإذا كان الخادم موجودًا ويقبل الاتصال فقد يرسل المتصفح شيئًا مثل هذا:
</p>

<pre class="ipsCode">
GET /18_http.html HTTP/1.1
Host: eloquentjavascript.net
User-Agent: Your browser's name
</pre>

<p>
	ثم يستجيب الخادم من خلال نفس قناة الاتصال:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_7" style="">
<span class="pln">HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> OK
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pun">:</span><span class="pln"> </span><span class="lit">65585</span><span class="pln">
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> text</span><span class="pun">/</span><span class="pln">html
</span><span class="typ">Last</span><span class="pun">-</span><span class="typ">Modified</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Mon</span><span class="pun">,</span><span class="pln"> </span><span class="lit">08</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln"> </span><span class="lit">2018</span><span class="pln"> </span><span class="lit">10</span><span class="pun">:</span><span class="lit">29</span><span class="pun">:</span><span class="lit">45</span><span class="pln"> GMT

</span><span class="pun">&lt;!</span><span class="pln">doctype html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">...</span><span class="pln"> the rest of the document</span></pre>

<p>
	تكون أول كلمة هي التابع الخاص بالطلب، إذ تعني <code>GET</code> أننا نريد الحصول على مصدر بعينه، كما هناك توابع أخرى مثل <code>DELETE</code> لحذف المصدر و<code>PUT</code> لإنشائه أو استبداله و<code>POST</code> لإرسال معلومات إليه. لاحظ أنّ الخادم ليس عليه تنفيذ جميع الطلبات التي تأتيه، فإذا ذهبتَ إلى موقع ما وطلبت حذف صفحته الرئيسية فسيرفض الخادم؛ أما الجزء الذي يلي اسم التابع، فيكون مسار المورد الذي يُطبق الطلب عليه، حيث يكون ملفًا على الخادم في أبسط حالاته، لكن البروتوكول لا يشترط كونه ملفًا فقط، بل قد يكون أي شيء يمكن نقله كما لو كان ملفًا، كما تولِّد العديد من الخوادم الاستجابات التي تنتجها لحظيًا، فإذا فتحت <a href="https://github.com/marijnh" rel="external nofollow">https://github.com/marijnh</a> مثلًا، فسيبحث الخادم في قاعدة بياناته عن مستخدِم باسم marijnh، فإذا وجده فسيولِّد صفحة مستخدِم له.
</p>

<p>
	يذكر أول سطر في الطلب بعد مسار المورد الـ <code>HTTP/1.1</code> للإشارة إلى نسخة بروتوكول HTTP الذي يستخدِمه، كما تستخدِم مواقع كثيرة النسخة الثانية من HTTP عمليًا، إذ تدعم المفاهيم نفسها التي تدعمها النسخة الأولى 1.1، لكنها أعقد منها لتكون أسرع، كما ستبدِّل المتصفحات إلى البروتوكول المناسب تلقائيًا أثناء التحدث مع الخادم المعطى، وسيكون خرج الطلب هو نفسه بغض النظر عن النسخة المستخدَمة، لكننا سنركز على النسخة 1.1 بما أنها أبسط وأسهل في التعديل عليها.
</p>

<p>
	ستبدأ استجابة الخادم بالنسخة أيضًا تليها بحالة الاستجابة مثل شيفرة حالة من ثلاثة أرقام أولًا، ثم مثل سلسلة نصية مقروءة من قِبَل المستخدِم.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_9" style="">
<span class="pln">HTTP</span><span class="pun">/</span><span class="lit">1.1</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> OK</span></pre>

<p>
	تبدأ رموز الحالة بـ 2 لتوضح نجاح الطلب؛ أما الطلبات التي تبدأ بـ 4 فتعني أنّ ثمة شيء خطأ في الطلب، ولعل أشهر رمز حالة HTTP هنا هي 404، والتي تعني أن المصدر غير موجود أو لا يمكن العثور عليه؛ أما الرموز التي تبدأ بالرقم 5، فتعني حدوث خطأ على الخادم ولا تتعلق المشكلة بالطلب نفسه، وقد يُتبع أول سطر من الطلب أو الاستجابة بعدد من الترويسات، وهي أسطر في صورة <code>name: value</code> توضِّح معلومات إضافية عن الطلب أو الاستجابة، وهي جزء من المثال الذي يوضح الاستجابة:
</p>

<pre class="ipsCode">
Content-Length: 65585
Content-Type: text/html
Last-Modified: Thu, 04 Jan 2018 14:05:30 GMT
</pre>

<p>
	يخبرنا هذا بحجم مستند الاستجابة ونوعه، وهو مستند HTML في هذه الحالة حجمه 65585 بايت، كما يخبرنا متى كانت آخر مرة عُدِّل فيها.
</p>

<p>
	يملك كل من العميل والخادم في أغلب الترويسات حرية إدراجها في الطلب أو الاستجابة، لكن بعض الترويسات يكون إدراجها إلزاميًا مثل ترويسة <code>HOST</code> التي تحدد اسم المضيف hostname، إذ يجب إدراجها في الطلب لأن الخادم قد يخدِّم عدة أسماء مضيفين على عنوان IP واحد، فبدون الترويسة لن يعرف أيّ واحد فيها يقصده العميل الذي يحاول التواصل معه، وقد تدرِج الطلبات أو الاستجابات سطرًا فارغًا بعد اسم المضيف متبوعًا بمتن body يحتوي على البيانات المرسلة، كما لا ترسل طلبات <code>GET</code> و<code>DELETE</code> أيّ بيانات، على عكس طلبات <code>PUT</code> و<code>POST</code>، وبالمثل فقد لا تحتاج بعض أنواع الاستجابات إلى متن مثل استجابات الخطأ error responses.
</p>

<h2>
	المتصفحات وHTTP
</h2>

<p>
	رأينا في المثال السابق أنّ المتصفح سينشئ الطلب حين نكتب الرابط URL في شريط عنوانه، فإذا أشارت صفحة HTML الناتجة إلى ملفات أخرى مثل صور أو ملفات جافاسكربت، فسيجلب المتصفح هذه الملفات أيضًا، ومن المعتاد للمواقع متوسطة التعقيد إدراج من 10 إلى 200 مصدر مع الصفحة، كما سترسل المتصفحات عدة طلبات <code>GET</code> في الوقت نفسه بدلًا من انتظار الاستجابة الواحدة، ثم إرسال طلب آخر من أجل تحميل الصفحة بسرعة، وقد تحتوي صفحات HTML على استمارات forms تسمح للمستخدِم بملء بيانات وإرسالها إلى الخادم، وفيما يلي مثال عن استمارة:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_11" style="">
<span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">method</span><span class="pun">=</span><span class="atv">"GET"</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"example/message.html"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;</span><span class="pln">Name: </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"name"</span><span class="tag">&gt;&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;</span><span class="pln">Message:</span><span class="tag">&lt;br&gt;&lt;textarea</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"message"</span><span class="tag">&gt;&lt;/textarea&gt;&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;p&gt;&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="tag">&gt;</span><span class="pln">Send</span><span class="tag">&lt;/button&gt;&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span></pre>

<p>
	تصف الشيفرة السابقة استمارةً لها حقلين أحدهما صغير يطلب الاسم والآخر أكبر لكتابة رسالة فيه، وتُرسَل الاستمارة عند الضغط على زر إرسال Send، أي يحزَّم محتوى حقلها في طلب HTTP وينتقل المتصفح إلى نتيجة ذلك الطلب.
</p>

<p>
	تضاف المعلومات التي في الاستمارة إلى نهاية رابط <code>action</code> على أساس سلسلة استعلام نصية إذا كانت سمة العنصر <code>method</code> الخاص بالاستمارة <code>&lt;form&gt;</code> هي <code>GET</code> -أو إذا أُهملت-، وقد ينشئ المتصفح طلبًا إلى هذا الرابط:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_13" style="">
<span class="pln">GET /example/message.html?name=Jean&amp;message=Yes%3F HTTP/1.1</span></pre>

<p>
	تحدِّد علامة الاستفهام نهاية جزء المسار من الرابط وبداية الاستعلام، كما تُتبع بأزواج من الأسماء والقيم تتوافق مع سمة <code>name</code> في عناصر حقول الاستمارة ومحتوى تلك العناصر على الترتيب، ويُستخدَم محرف الإضافة ampersand أي <code>&amp;</code> لفصل تلك الأزواج، في حين تكون الرسالة الفعلية المرمَّزة في الرابط هي "Yes?‎"، لكن ستُستبدَل شيفرة غريبة بعلامة الاستفهام، كما يجب تهريب بعض المحارف في سلاسل الاستعلامات النصية، فعلامة الاستفهام الممثلة بـ <code>‎%3F</code> هي أحد تلك المحارف، وسنجد أن هناك شبه قاعدة غير مكتوبة تقول أنّ كل صيغة تحتاج إلى طريقتها الخاصة في تهريب المحارف، وهذه التي بين أيدينا تُسمى ترميز الروابط التشعبية URL encoding، حيث تستخدِم علامة النسبة المئوية ويليها رقمين ست-عشريين يرمِّزان شيفرة المحرف، وفي حالتنا تكون 3F -التي هي 63 في النظام العشري- شيفرة محرف علامة الاستفهام، وتوفِّر جافاسكربت الدالتين <code>encodeURIComponent</code> و<code>decodeURIComponent</code> من أجل ترميز تلك الصيغة وفك ترميزها أيضًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_16" style="">
<span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">encodeURIComponent</span><span class="pun">(</span><span class="str">"Yes?"</span><span class="pun">));</span><span class="pln">
</span><span class="com">// → Yes%3F</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">decodeURIComponent</span><span class="pun">(</span><span class="str">"Yes%3F"</span><span class="pun">));</span><span class="pln">
</span><span class="com">// → Yes?</span></pre>

<p>
	إذا غيرنا السمة <code>method</code> لاستمارة HTML في المثال الذي رأيناه إلى <code>POST</code>، فسيستخدِم طلب HTTP الذي أنشئ لإرسال الاستمارة التابع <code>POST</code> ويضع سلسلة الاستعلام النصية في متن الطلب بدلًا من إضافتها إلى الرابط.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_18" style="">
<span class="pln">POST /example/message.html HTTP/1.1
Content-length: 24
Content-type: application/x-www-form-urlencoded

name=Jean&amp;message=Yes%3F</span></pre>

<p>
	يجب استخدام طلبات <code>GET</code> للطلبات التي تطلب معلومات فقط وليس لها تأثيرات جانبية؛ أما الطلبات التي تغيِّر شيئًا في الخادم مثل إنشاء حساب جديد أو نشر رسالة، فيجب التعبير عنها بتوابع أخرى مثل <code>POST</code>، كما تدرك برامج العميل مثل المتصفحات أنها يجب ألا تنشئ طلبات <code>POST</code> عشوائيًا وإنما تنشئ طلبات <code>GET</code> أولًا ضمنيًا لجلب المصدر التي تظن أنّ المستخدِم سيحتاجه قريبًا، كما سنعود إلى كيفية التفاعل مع الاستمارات من جافاسكربت لاحقًا في هذا المقال.
</p>

<h2>
	واجهة Fetch
</h2>

<p>
	تُسمى الواجهة التي تستطيع جافاسكربت الخاصة بالمتصفح إنشاء طلبات HTTP من خلالها باسم <code><a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-fetch-%D9%81%D9%8A-javascript-r739/" rel="">fetch</a></code>، وبما أنها جديدة نسبيًا فستستخدم الوعود promises وهو الأمر النادر بالنسبة لواجهات المتصفحات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_20" style="">
<span class="pln">fetch</span><span class="pun">(</span><span class="str">"example/data.txt"</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">response </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → 200</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">"Content-Type"</span><span class="pun">));</span><span class="pln">
  </span><span class="com">// → text/plain</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يعيد استدعاء <code>fetch</code> وعدًا يُحل إلى كائن <code>Response</code> حاملًا معلومات عن استجابة الخادم مثل شيفرة حالته وترويساته، وتغلَّف الترويسات في كائن شبيه بالخارطة ‎<code>Map</code>-like الذي يهمل حالة الأحرف في مفاتيحه -أي أسماء الترويسات- لأنه لا يفترض أن تكون أسماء الترويسات حساسةً لحالة الأحرف. هذا يعني أن كلا من الآتي:
</p>

<ul>
<li>
		 ‎<code>headers.get("Content-Type")</code>‎0.
	</li>
	<li>
		‎<code>headers.get("content-TYPE")</code>‎.
	</li>
</ul>
<p>
	سيُعيدان القيمة نفسها.
</p>

<p>
	لاحظ أن الوعد الذي تعيده <code>fetch</code> يُحل بنجاح حتى لو استجاب الخادم برمز خطأ، وقد يُرفض إذا كان ثمة خطأ في الشبكة أو لم يوجد الخادم الذي أرسل إليه الطلب، كما يجب أن يكون أول وسيط لواجهة <code>fetch</code> هو الرابط الذي يراد طلبه، فإذا لم يبدأ الرابط باسم البروتوكول - http‎:‎ مثلًا- فسيعامَل نسبيًا، أي يفسَّر وفق المستند الحالي، وإذا بدأ بشرطة مائلة <code>/</code> فسيستبدل المسار الحالي الذي يكون جزء المسار الذي يلي اسم الخادم، أما إذا لم يبدأ بالشرطة المائلة، فسيوضع جزء المسار الحالي إلى آخر محرف شرطة مائلة -مع الشرطة نفسها- أمام الرابط النسبي.
</p>

<p>
	يُستخدَم التابع <code>text</code> للحصول على المحتوى الفعلي للاستجابة، ويعيد هذا التابع وعدًا لأن الوعد الأولي يُحَل عند استقبال ترويسات الاستجابة، ولأنّ قراءة متن الاستجابة قد تستغرق وقتًا أطول.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_22" style="">
<span class="pln">fetch("example/data.txt")
  .then(resp =&gt; resp.text())
  .then(text =&gt; console.log(text));
// → This is the content of data.txt</span></pre>

<p>
	يعيد التابع <code>json</code> -وهو تابع شبيه بالسابق- وعدًا يُحل إلى القيمة التي تحصل عليها حين تحلل المتن مثل <a href="https://academy.hsoub.com/programming/javascript/%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r826/" rel="">JSON </a>أو يُرفض إذا لم يكن JSON صالحًا، كما تستخدِم واجهة <code>fetch</code> التابع <code>GET</code> افتراضيًا لإنشاء طلبها ولا تدرِج متن الطلب، كما يمكنك إعدادها لغير ذلك بتمرير كائن له خيارات إضافية على أساس وسيط ثاني، فهذا الطلب مثلًا يحاول حذف <code>example/data.txt</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_24" style="">
<span class="pln">fetch</span><span class="pun">(</span><span class="str">"example/data.txt"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">method</span><span class="pun">:</span><span class="pln"> </span><span class="str">"DELETE"</span><span class="pun">}).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">resp </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">resp</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → 405</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	يعني رمز الحالة 405 أنّ "الطلب غير مسموح به" وهو أسلوب خادم HTTP ليقول "لا أستطيع فعل هذا"، كما يمكن إضافة الخيار <code>body</code> لإضافة متن الطلب، كما يُستخدَم الخيار <code>headers</code> لضبط الترويسات، فهذا الطلب مثلًا يضمِّن الترويسة <code>Range</code> التي تخبر الخادم بإعادة جزء من الاستجابة فقط.
</p>

<pre class="ipsCode">
fetch("example/data.txt", {headers: {Range: "bytes=8-19"}})
  .then(resp =&gt; resp.text())
  .then(console.log);
// → المحتوى
</pre>

<p>
	سيضيف المتصفح بعض ترويسات الطلب تلقائيًا مثل Host وتلك المطلوبة كي يعرف الخادم حجم المتن، لكن ستكون إضافة ترويساتك الخاصة مفيدةً إذا أردنا إضافة أشياء مثل معلومات التوثيق، أو لنخبر الخادم بصيغة الملف التي نريد استقبالها.
</p>

<h2>
	صندوق اختبارات HTTP
</h2>

<p>
	لا شك أنّ إنشاء طلبات HTTP في سكربتات صفحة الويب سيرفع علامات استفهام حول الأمان، فالشخص الذي يتحكم بالسكربت قد لا تكون لديه دوافع الشخص نفسها التي يشغلها على حاسوبه، فإذا زرنا الموقع themafia.org مثلًا، فلا نريد لسكربتاته أن تكون قادرةً على إنشاء طلب إلى mybank.com باستخدام معلومات التعريف من متصفحنا مع تعليمات بتحويل جميع أموالنا إلى حساب عشوائي، لهذا تحمينا المتصفحات من خلال عدم السماح للسكربتات بإنشاء طلبات HTTP إلى نطاقات أخرى (أسماء نطاقات مثل themafia.org وmybank.com)، وتُعَدّ هذه مشكلةً مؤرقةً عند بناء أنظمة تريد الوصول إلى عدة نطاقات من أجل أسباب مشروعة ومنطقية، ولحسن الحظ تستطيع الخوادم إدراج ترويسة لهذا الغرض في استجابتها لإخبار المتصفح صراحةً أن هذا الطلب يمكن أن يأتي من نطاق آخر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_26" style="">
<span class="typ">Access</span><span class="pun">-</span><span class="typ">Control</span><span class="pun">-</span><span class="typ">Allow</span><span class="pun">-</span><span class="typ">Origin</span><span class="pun">:</span><span class="pln"> </span><span class="pun">*</span></pre>

<h2>
	تقدير HTTP
</h2>

<p>
	هناك عدة طرق مختلفة لنمذجة التواصل بين برامج جافاسكربت العاملة في المتصفح -جانب العميل- والبرنامج الذي على الخادم -أي جانب الخادم-، وإحدى أكثر تلك الطرق استخدامًا هي استدعاءات الإجراءات البعيدة remote procedure calls، إذ يتبع التواصل في هذا النموذج أنماط استدعاءات الدوال العادية عدا أنّ الدالة تعمل فعليًا على حاسوب آخر، حيث يتطلب استدعاؤها إنشاء طلب إلى الخادم الذي يتضمن اسم الدالة والوسائط، كما تحتوي استجابة ذلك الطلب على القيمة المعادة.
</p>

<p>
	عند التفكير في شأن استدعاءات الإجراء البعيد، لا يكون HTTP أكثر من أنه وسيلة تواصل، وستكتب على الأرجح طبقةً مجردةً تخفيه كليًا، كما يوجد هناك منظور آخر نبني فيه التواصل حول مفهوم الموارد وتوابع HTTP، فبدلًا من <code>addUser</code> المستدعى استدعاءًا بعيدًا، فإننا سنستخدم طلب <code>PUT</code> إلى <code>‎/users/larry</code>، وبدلًا من ترميز خصائص ذلك المستخدِم في وسائط دالة، فإنك تعرِّف صيغة مستند JSON من أجل تمثيل المستخدِم أو تستخدم صيغةً موجودةً من قبل لذلك.
</p>

<p>
	كما يُجلَب المورد بإنشاء طلب <code>GET</code> إلى رابط المورد -<code>‎/users/larry</code> مثلًا- والذي يُعيد المستند الممثل للمورد، إذ يسهل هذا المنظور استخدام بعض المزايا التي يوفرها HTTP مثل دعم تخزين الموارد -أي إنشاء نسخة مؤقتة عند العميل من أجل تسريع الوصول-، كما توفر المفاهيم المستخدَمة في HTTP مجموعة مبادئ مفيدة في تصميم واجهة الخوادم الخاصة بك بما أنها جيدة التصميم.
</p>

<h2>
	الأمان وHTTP
</h2>

<p>
	تمر البيانات المتنقلة عبر الانترنت في طرق محفوفة بالمخاطر، إذ تمر خلال أي طريق نتواجد فيه سواء كان نقطة اتصال في مقهى أو شبكات تتحكم بها شركات أو دول بغية الحصول على وجهتها، وقد تُعتَرض وتفتَّش في أي نقطة في طريقها وقد تُعدَّل أيضًا، وهنا لا يكفي بروتوكول HTTP العادي بما أنّ بعض البيانات سرية مثل كلمات مرور بريدك أو يجب عليها الوصول إلى وجهتها دون تعديل مثل رقم الحساب الذي تحوِّل المال إليه من خلال موقع البنك الذي تتعامل معه.
</p>

<p>
	نستخدِم هنا بروتوكولًا أحدث هو HTTPS الذي نجده في الروابط التي تبدأ بـ https://‎، إذ يغلِّف حركة مرور HTTP بطريقة تصعب قراءتها والتعديل عليها، ويؤكد الطرف العميل قبل إرسال البيانات أنّ الخادم الذي يطلبها هو نفسه وليس منتحلًا له من خلال التأكد من شهادة مشفرة مصدَرة من جهة توثيق يعتمدها المتصفح، ثم تشفَّر جميع البيانات بطريقة تمنع استراق النظر إليها أو التعديل عليها، وعليه يمنع HTTPS أيّ جهة خارجية من انتحال الموقع الذي تريد التواصل معه ومن اختلاس النظر أو التجسس على تواصلكما، لكنه ليس مثاليًا بالطبع فقد وقعت عدة حوادث فشل فيها HTTPS بسبب شهادات مزورة أو مسروقة وبسبب برامج مخترَقة أو معطوبة، لكنه أكثر أمانًا من HTTP العادي.
</p>

<h2>
	حقول الاستمارات
</h2>

<p>
	صُمِّمت الاستمارات ابتداءً للويب قبل مجيء جافاسكربت من أجل السماح لمواقع الويب إرسال البيانات التي يدخلها المستخدِم في هيئة طلب <a href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">HTTP</a>، حيث يفترِض هذا التصميم أنّ التفاعل مع الخادم سيحدث دائمًا من خلال الانتقال إلى صفحة جديدة، غير أنّ عناصرها جزء من نموذج كائن مستند <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%81-%D8%B9%D9%84%D9%89-%D8%B4%D8%AC%D8%B1%D8%A9-dom-%D9%84%D8%AA%D8%B9%D8%AF%D9%8A%D9%84%D9%87%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1104/" rel="">DOM </a>مثل بقية الصفحة، كما تدعم عناصر DOM التي تمثِّل حقول الاستمارة عددًا من الخصائص والأحداث التي ليست موجودةً في العناصر الأخرى، حيث تمكننا من فحص حقول الإدخال تلك والتحكم فيها ببرامج جافاسكربت وأمور أخرى مثل إضافة وظيفة جديدة إلى استمارة أو استخدام استمارات وحقول على أساس وحدات بناء في تطبيق جافاسكربت.
</p>

<p>
	تتكون استمارة الويب من عدد من حقول الإدخال تُجمع في وسم <code>&lt;form&gt;</code>، وتسمح HTML بعدة تنسيقات من الحقول بدايةً من أزرار الاختيار checkboxes إلى القوائم المنسدلة وحقول إدخال النصوص، ولن نناقش كل أنواع الحقول في هذا الكتاب لكن سنبدأ بنظرة عامة عليها.
</p>

<p>
	تستخدِم أكثر أنواع الحقول وسم <code>&lt;input&gt;</code> وتُستخدَم السمة <code>type</code> الخاصة بهذا الوسم من أجل اختيار تنسيق الحقل، وفيما يلي أكثر أنواع <code>&lt;input&gt;</code> المستخدَمة:
</p>
<style type="text/css">
table {
    width: 100%;
}

thead {
    vertical-align: middle;
    text-align: center;
} 

td, th {
    border: 1px solid #dddddd;
    text-align: right;
    padding: 8px;
    text-align: inherit;

}
tr:nth-child(even) {
    background-color: #dddddd;
}</style>
<p>
	أجل تمثيل المستخدِم أو تستخدم صيغةً موجودةً من قبل لذل
</p>

<table>
<thead><tr>
<th>
				<code>text</code>
			</th>
			<th style="text-align:right">
				حقل نصي ذو سطر واحد
			</th>
		</tr></thead>
<tbody>
<tr>
<td>
				<code>password</code>
			</td>
			<td style="text-align:right">
				حقل نصي مثل <code>text</code> لكن يخفي النص الذي يُكتَب فيه
			</td>
		</tr>
<tr>
<td>
				<code>checkbox</code>
			</td>
			<td style="text-align:right">
				مفتاح تشغيل/إغلاق
			</td>
		</tr>
<tr>
<td>
				<code>radio</code>
			</td>
			<td style="text-align:right">
				جزء من حقل اختيار من متعدد
			</td>
		</tr>
<tr>
<td>
				<code>file</code>
			</td>
			<td style="text-align:right">
				يسمح للمستخدِم اختيار ملف من حاسوبه
			</td>
		</tr>
</tbody>
</table>
<p>
	يمكن وضع حقول الاستمارات في أي مكان في الصفحة ولا يشترط ظهورها في وسوم <code>&lt;form&gt;</code> وحدها، كما لا يمكن إرسال تلك الحقول التي تكون مستقلة بذاتها وخارج استمارة، فالاستمارات وحدها هي التي ترسَل، لكن على أيّ حال لا نحتاج إلى إرسال محتوى حقولنا بالطريقة التقليدية عند استخدام جافاسكربت في الاستجابة للمدخلات.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_28" style="">
<span class="tag">&lt;p&gt;&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"abc"</span><span class="tag">&gt;</span><span class="pln"> (text)</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"password"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"abc"</span><span class="tag">&gt;</span><span class="pln"> (password)</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"checkbox"</span><span class="pln"> </span><span class="atn">checked</span><span class="tag">&gt;</span><span class="pln"> (checkbox)</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"radio"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"A"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"choice"</span><span class="tag">&gt;</span><span class="pln">
   </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"radio"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"B"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"choice"</span><span class="pln"> </span><span class="atn">checked</span><span class="tag">&gt;</span><span class="pln">
   </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"radio"</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"C"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"choice"</span><span class="tag">&gt;</span><span class="pln"> (radio)</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"file"</span><span class="tag">&gt;</span><span class="pln"> (file)</span><span class="tag">&lt;/p&gt;</span></pre>

<p>
	تختلف واجهة جافاسكربت لمثل تلك العناصر باختلاف نوع العنصر.
</p>

<p>
	تمتلك الحقول النصية متعددة الأسطر وسمًا خاصًا بها هو <code>&lt;textarea&gt;</code>، وذلك بسبب غرابة استخدام سمة لتحديد قيمة ابتدائية لسطر متعدد، كما يجب إغلاق هذا الوسم بأسلوب الإغلاق المعتاد في HTML بإضافة ‎<code>&lt;/textarea&gt;</code>‎، حيث يُستخدَم النص الموجود بين هذين الوسمين على أساس نص ابتدائي بدلًا من سمة <code>value</code>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_32" style="">
<span class="tag">&lt;textarea&gt;</span><span class="pln">
one
two
three
</span><span class="tag">&lt;/textarea&gt;</span></pre>

<p>
	أخيرًا، يُستخدَم الوسم <code>&lt;select&gt;</code> لإنشاء حقل يسمح للمستخدِم بالاختيار من عدد من الخيارات المعرَّفة مسبقًا، ويُطلَق الحدث <code>"change"</code> كلما تغيرت قيمة حقل من حقول الاستمارة.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_36" style="">
<span class="tag">&lt;select&gt;</span><span class="pln">
  </span><span class="tag">&lt;option&gt;</span><span class="pln">Pancakes</span><span class="tag">&lt;/option&gt;</span><span class="pln">
  </span><span class="tag">&lt;option&gt;</span><span class="pln">Pudding</span><span class="tag">&lt;/option&gt;</span><span class="pln">
  </span><span class="tag">&lt;option&gt;</span><span class="pln">Ice cream</span><span class="tag">&lt;/option&gt;</span><span class="pln">
</span><span class="tag">&lt;/select&gt;</span></pre>

<h2>
	التركيز Focus
</h2>

<p>
	تستطيع حقول الاستمارات الحصول على نشاط لوحة المفاتيح عند النقر أو تفعيلها بأيّ شكل آخر على عكس أغلب عناصر مستندات HTML، بحيث تصبح هي العنصر المفعَّل الحالي ومستقبل إدخال لوحة المفاتيح، وعلى ذلك نستطيع الكتابة في الحقل النصي حين يكون مركَّزًا فقط؛ أما الحقول الأخرى فتختلف في استجابتها لأحداث لوحة المفاتيح، إذ تحاول قائمة <code>&lt;select&gt;</code> مثلًا الانتقال إلى الخيار الذي يحتوي النص الذي كتبه المستخدِم وتستجيب لمفاتيح الأسهم عبر تحريك اختيارها لأعلى وأسفل.
</p>

<p>
	يمكننا التحكم في التركيز باستخدام جافاسكربت من خلال التابعَين <code>focus</code> و<code>blur</code>، حيث ينقل <code>focus</code> التركيز إلى عنصر DOM الذي استدعي عليه؛ أما الثاني فسيزيل التركيز منه، وتتوافق القيمة التي في <code>document.activeElement</code> مع العنصر المركَّز حاليًا.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_38" style="">
<span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">).</span><span class="pln">focus</span><span class="pun">();</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">activeElement</span><span class="pun">.</span><span class="pln">tagName</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → INPUT</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">).</span><span class="pln">blur</span><span class="pun">();</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">activeElement</span><span class="pun">.</span><span class="pln">tagName</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → BODY</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	يُتوقَّع من المستخدِم في بعض الصفحات أن يرغب في التفاعل مع أحد حقول الاستمارة فورًا، ويمكن استخدام جافاسكربت لتركيز ذلك الحقل عند تحميل المستند، لكن توفر HTML السمة <code>autofocus</code> أيضًاـ، والتي تعطينا التأثير نفسه وتخبر المتصفح بما نحاول فعله، وهذا يعطي المتصفح خيار تعطيل السلوك إذا كان غير مناسب كما في حالة محاولة المستخدِم تركيز حقل آخر أو عنصر آخر، وقد تعارفت المتصفحات على تمكين المستخدم من نقل التركيز خلال المستند بمجرد ضغط زر جدول أو tab على لوحة المفاتيح، ونستطيع هنا التحكم في الترتيب الذي تستقبل به العناصر ذلك التركيز باستخدام السمة <code>tabindex</code>، وسيجعل المثال التالي التركيز يقفز من المدخلات النصية إلى زر OK بدلًا من المرور على رابط المساعدة أولًا:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_40" style="">
<span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">tabindex</span><span class="pun">=</span><span class="atv">1</span><span class="tag">&gt;</span><span class="pln"> </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"."</span><span class="tag">&gt;</span><span class="pln">(help)</span><span class="tag">&lt;/a&gt;</span><span class="pln">
</span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">onclick</span><span class="pun">=</span><span class="atv">"</span><span class="pln">console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'ok'</span><span class="pun">)</span><span class="atv">"</span><span class="pln"> </span><span class="atn">tabindex</span><span class="pun">=</span><span class="atv">2</span><span class="tag">&gt;</span><span class="pln">OK</span><span class="tag">&lt;/button&gt;</span></pre>

<p>
	السلوك الافتراضي لأغلب عناصر HTML أنها لا يمكن تركيزها، غير أنك تستطيع إضافة سمة <code>tabindex</code> إلى أي عنصر لجعله قابلًا للتركيز؛ أما إذا جعلنا قيمتها <code>‎-1</code> فسيتم تخطي العنصر حتى لو كان قابلًا للتركيز.
</p>

<h2>
	الحقول المعطلة
</h2>

<p>
	يمكن تعطيل أيّ حقل من حقول الاستمارات من خلال السمة <code>disabled</code> الخاصة بها، وهي سمة يمكن تحديدها دون قيمة، إذ يعطِّل وجودها الحقل مباشرةً، ولا يمكن تركيز الحقول المعطَّلة أو تغييرها، كما ستظهرها المتصفحات بلون رمادي وباهت.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_42" style="">
<span class="tag">&lt;button&gt;</span><span class="pln">أنا مركَّز الآن</span><span class="tag">&lt;/button&gt;</span><span class="pln">
</span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">disabled</span><span class="tag">&gt;</span><span class="pln">خرجت!‏</span><span class="tag">&lt;/button&gt;</span></pre>

<p>
	إذا عالج البرنامج إجراءً سببه زر أو تحكم آخر قد يحتاج إلى تواصل مع الخادم وسيستغرق وقتًا، فمن الأفضل تعطيل التحكم حتى انتهاء ذلك الإجراء، وهكذا لن يتكرر الإجراء إذا نفذ صبر المستخدِم ونقر عليه مرةً أخرى.
</p>

<h2>
	الاستمارات على أساس عنصر كامل
</h2>

<p>
	إذا احتوى الحقل على العنصر <code>&lt;form&gt;</code> فسيحتوي عنصر DOM الخاص به على الخاصية <code>form</code> التي تربطه إلى عنصر DOM الخاص بالاستمارة، ويحتوي العنصر <code>&lt;form&gt;</code> بدوره على خاصية تسمى <code>elements</code> تحوي تجميعةً شبيهةً بالمصفوفة من الحقول التي بداخلها. كذلك تحدِّد السمة <code>name</code> الموجودة في حقل الاستمارة الطريقة التي تعرَّف بها قيمتها عند إرسال الاستمارة، كما يمكن استخدامها على أساس اسم خاصية عند الوصول إلى الخاصية <code>elements</code> الخاصة بالاستمارة والتي تتصرف على أساس كائن شبيه بالمصفوفة -يمكن الوصول إليه بعدد-، وخريطة map -يمكن الوصول إليها باسم-.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_44" style="">
<span class="tag">&lt;form</span><span class="pln"> </span><span class="atn">action</span><span class="pun">=</span><span class="atv">"example/submit.html"</span><span class="tag">&gt;</span><span class="pln">
  Name: </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"name"</span><span class="tag">&gt;&lt;br&gt;</span><span class="pln">
  Password: </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"password"</span><span class="pln"> </span><span class="atn">name</span><span class="pun">=</span><span class="atv">"password"</span><span class="tag">&gt;&lt;br&gt;</span><span class="pln">
  </span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"submit"</span><span class="tag">&gt;</span><span class="pln">Log in</span><span class="tag">&lt;/button&gt;</span><span class="pln">
</span><span class="tag">&lt;/form&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let form </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"form"</span><span class="pun">);</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">elements</span><span class="pun">[</span><span class="lit">1</span><span class="pun">].</span><span class="pln">type</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → password</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">elements</span><span class="pun">.</span><span class="pln">password</span><span class="pun">.</span><span class="pln">type</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → password</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">form</span><span class="pun">.</span><span class="pln">elements</span><span class="pun">.</span><span class="pln">name</span><span class="pun">.</span><span class="pln">form </span><span class="pun">==</span><span class="pln"> form</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → true</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	يرسل الزر الذي فيه سمة <code>type</code> الخاصة بـ <code>submit</code> الاستمارة عند الضغط عليه، كما سنحصل على التأثير نفسه عند الضغط على زر الإدخال Enter وإذا كان حقل الاستمارة مركَّزًا، ويعني إرسال الاستمارة غالبًا أنّ المتصفح ينتقل إلى الصفحة التي تحددها سمة <code>action</code> الخاصة بالاستمارة مستخدِمًا أحد الطلبَين <code>GET</code> أو <code>POST</code>، لكن يُطلَق الحدث <code>"submit"</code> قبل حدوث ذلك، وتستطيع معالجة هذا الحدث بجافاسكربت، ونمنع ذلك السلوك الافتراضي من خلال استدعاء <code>preventDefault</code> على كائن الحدث.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_46" style="">
<span class="pun">&lt;</span><span class="pln">form action</span><span class="pun">=</span><span class="str">"example/submit.html"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="typ">Value</span><span class="pun">:</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"value"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">button type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pun">&gt;</span><span class="typ">Save</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let form </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"form"</span><span class="pun">);</span><span class="pln">
  form</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"submit"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Saving value"</span><span class="pun">,</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">elements</span><span class="pun">.</span><span class="pln">value</span><span class="pun">.</span><span class="pln">value</span><span class="pun">);</span><span class="pln">
    event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	يملك اعتراض أحداث <code>"submit"</code> في جافاسكربت فوائدً عديدةً، حيث نستطيع كتابة شيفرة للتحقق من أنّ القيم التي أدخلها المستخدِم منطقية، ونخرِج رسائل خطأ له إذا وجدنا أخطاءً في تلك القيم بدلًا من إرسال الاستمارة، أو نستطيع تعطيل الطريقة المعتادة في إرسال الاستمارة بالكامل كما في المثال، ونجعل البرنامج يعالِج المدخلات باستخدام <code>fetch</code> لإرسالها إلى خادم دون إعادة تحميل الصفحة.
</p>

<h2>
	الحقول النصية
</h2>

<p>
	تشترك الحقول التي أنشئت بواسطة الوسوم <code>&lt;textarea&gt;</code> أو <code>&lt;input&gt;</code> مع النوع <code>text</code> و<code>password</code> واجهةً مشتركةً، كما تحتوي عناصر DOM الخاصة بها على الخاصية <code>value</code> التي تحمل محتواها الحالي على أساس قيمة نصية، وإذا عيّنا تلك الخاصية إلى سلسلة نصية أخرى فسيتغيَّر محتوى الحقل.
</p>

<p>
	تعطينا كذلك الخصائص <code>selectionStart</code> و<code>selectionEnd</code> الخاصة بالحقول النصية معلومات عن المؤشر والجزء المحدَّد في النص، فإذا لم يكن ثمة نص محدَّد، فستحمل هاتان الخاصيتان العدد نفسه والذي يشير إلى موضع المؤشر، حيث يشير 0 مثلًا إلى بداية النص، ويشير 10 إلى أنّ المؤشر بعد المحرف العاشر؛ أما إذا حدِّد جزء من الحقل، فستختلف هاتين الخاصيتين لتعطينا بداية النص المحدَّد ونهايته، كما يمكن الكتابة فيهما مثل <code>value</code> تمامًا.
</p>

<p>
	لنفترض أنك تكتب مقالةً عن الفرعون "خع سخموي Khasekhemwy" لكن لا نستطيع تهجئة اسمه، لذا تساعدنا هنا الشيفرة التالية التي توصل وسم <code>&lt;textarea&gt;</code> بمعالج حدث يُدخل النص Khasekhemwy لك إذا ضغطت على مفتاح F2.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_48" style="">
<span class="pun">&lt;</span><span class="pln">textarea</span><span class="pun">&gt;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let textarea </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"textarea"</span><span class="pun">);</span><span class="pln">
  textarea</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"keydown"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// The key code for F2 happens to be 113</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">keyCode </span><span class="pun">==</span><span class="pln"> </span><span class="lit">113</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      replaceSelection</span><span class="pun">(</span><span class="pln">textarea</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Khasekhemwy"</span><span class="pun">);</span><span class="pln">
      event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> replaceSelection</span><span class="pun">(</span><span class="pln">field</span><span class="pun">,</span><span class="pln"> word</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let from </span><span class="pun">=</span><span class="pln"> field</span><span class="pun">.</span><span class="pln">selectionStart</span><span class="pun">,</span><span class="pln"> to </span><span class="pun">=</span><span class="pln"> field</span><span class="pun">.</span><span class="pln">selectionEnd</span><span class="pun">;</span><span class="pln">
    field</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> field</span><span class="pun">.</span><span class="pln">value</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> from</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> word </span><span class="pun">+</span><span class="pln">
                  field</span><span class="pun">.</span><span class="pln">value</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="pln">to</span><span class="pun">);</span><span class="pln">
    </span><span class="com">// ضع المؤشر بعد الكلمة</span><span class="pln">
    field</span><span class="pun">.</span><span class="pln">selectionStart </span><span class="pun">=</span><span class="pln"> from </span><span class="pun">+</span><span class="pln"> word</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln">
    field</span><span class="pun">.</span><span class="pln">selectionEnd </span><span class="pun">=</span><span class="pln"> from </span><span class="pun">+</span><span class="pln"> word</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تستبدِل الدالة <code>replaceSelection</code> الكلمة الصعبة المعطاة والتي نريدها بالجزء المحدَّد حاليًا من محتوى الحقل النصي، ثم تنقل المؤشر بعد تلك الكلمة كي يستطيع المستخدِم متابعة الكتابة، ولا يُطلَق الحدث <code>"change"</code> للحقل النصي في كل مرة يُكتب شيء ما، بل عندما يزال التركز عن أحد الحقول بعد تغيُّر محتواه فقط، ويجب علينا تسجيل معالج للحدث <code>"input"</code> من أجل الاستجابة الفورية للتغيرات الحادثة في الحقل النصي، حيث يُطلَق في كل مرة يكتب المستخدِم فيها محرفًا أو يحذف نصًا أو يعدِّل في محتوى الحقل، ويظهر المثال التالي حقلًا نصيًا وعدّادًا يعرض الطول الحالي للنص في الحقل:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_50" style="">
<span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"text"</span><span class="tag">&gt;</span><span class="pln"> length: </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"length"</span><span class="tag">&gt;</span><span class="pln">0</span><span class="tag">&lt;/span&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let text </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">);</span><span class="pln">
  let output </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"#length"</span><span class="pun">);</span><span class="pln">
  text</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    output</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> text</span><span class="pun">.</span><span class="pln">value</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<h2>
	أزرار الاختيار وأزرار الانتقاء
</h2>

<p>
	يُعَدّ حقل زر الانتقاء checkbox مخيّر فهو يخير بين اختياره من المجموعة أو لا ويمكن استخراج قيمته أو تغييرها من خلال الخاصية <code>checked</code> الخاصة به والتي تحمل قيمةً بوليانيةً.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_52" style="">
<span class="tag">&lt;label&gt;</span><span class="pln">
  </span><span class="tag">&lt;input</span><span class="pln"> </span><span class="atn">type</span><span class="pun">=</span><span class="atv">"checkbox"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"purple"</span><span class="tag">&gt;</span><span class="pln"> Make this page purple
</span><span class="tag">&lt;/label&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let checkbox </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"#purple"</span><span class="pun">);</span><span class="pln">
  checkbox</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"change"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">background </span><span class="pun">=</span><span class="pln">
      checkbox</span><span class="pun">.</span><span class="pln">checked </span><span class="pun">?</span><span class="pln"> </span><span class="str">"mediumpurple"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="str">""</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	يربط وسم العنوان <code>&lt;label&gt;</code> جزءًا من المستند بحقل إدخال، فإذا نقرنا في أيّ مكان على العنوان فسنفعِّل الحقل ونغير قيمته إذا كان زر اختيار أو زر انتقاء.
</p>

<p>
	يشبه زر انتقاء أو زر الانتقاء زر الاختيار لكنه يرتبط ضمنيًا بأزرار انتقاء أخرى لها سمة <code>name</code> نفسها كي يُنتقى واحد منها فقط، وقد سميت بهذا الاسم لأنها تشبه أزرار اختيار محطات الراديو في أجهزة المذياع في شكلها ووظيفتها، حيث إذا ضغطنا على أحد تلك الأزرار فإن بقية الأزرار تقفز إلى الخارج ولا تعمل سوى المحطة صاحبة الزر المنضغط.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_54" style="">
<span class="typ">Color</span><span class="pun">:</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"radio"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"color"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"orange"</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Orange</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"radio"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"color"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"lightgreen"</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Green</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"radio"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"color"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"lightblue"</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Blue</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">label</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let buttons </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelectorAll</span><span class="pun">(</span><span class="str">"[name=color]"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let button of </span><span class="typ">Array</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">buttons</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    button</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"change"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">background </span><span class="pun">=</span><span class="pln"> button</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تُستخدَم الأقواس المربعة في استعلام CSS المعطى إلى <code>querySelectorAll</code> لمطابقة السمات، وهي تختار العناصر التي تكون سمة <code>name</code> الخاصة بها هي <code>"color"</code>.
</p>

<h2>
	حقول التحديد
</h2>

<p>
	تسمح حقول التحديد select fields للمستخدِم الاختيار من بين مجموعة خيارات كما في حالة أزرار الانتقاء، لكن مظهر الوسم <code>&lt;select&gt;</code> يختلف بحسب المتصفح على عكس أزرار الانتقاء التي نتحكم في مظهرها، كما تحتوي هذه الحقول على متغير يشبه قائمة أزرار الاختيار أكثر من أزرار الانتقاء، فإذا أعطينا وسم <code>&lt;select&gt;</code> السمة <code>multiple</code>، فسيسمح للمستخدِم اختيار أيّ عدد من الخيارات التي يريدها بدلًا من خيار واحد، وسيكون مظهر هذا مختلفًا باختلاف المتصفح، لكنه سيختلف عن حقل التحديد العادي الذي يكون تحكمًا منسدلًا drop-down control لا يعرض الخيارات إلا عند فتحه.
</p>

<p>
	يحتوي كل وسم <code>&lt;option&gt;</code> على قيمة يمكن تعريفها باستخدام السمة <code>value</code>، وإذا لم تعطى تلك القيمة، فسيُعَدّ النص الذي بداخل الخيار هو قيمته، كما تعكس خاصية <code>value</code> الخاصة بالعنصر <code>&lt;select&gt;</code> الخيار المحدَّد حاليًا، لكن لن تكون هذه الخاصية ذات شأن في حالة الحقل <code>multiple</code> بما أنها ستعطي قيمة خيار واحد فقط من الخيارات المحدَّدة الحالية، ويمكن الوصول إلى وسوم <code>&lt;option&gt;</code> الخاصة بحقل <code>&lt;select&gt;</code> على أساس كائن شبيه بالمصفوفة من خلال خاصية الحقل <code>options</code>، كما يحتوي كل خيار على خاصية تسمى <code>selected</code> توضِّح هل الخيار محدَّد حاليًا أم لا، ويمكن كتابة الخاصية لتحديد خيار ما أو إلغاء تحديده.
</p>

<p>
	يستخرِج المثال التالي القيم المحدَّدة من حقل التحديد <code>multiple</code> ويستخدِمها لتركيب عدد ثنائي من بِتّات منفصلة، لتحديد عدة خيارات اضغط باستمرار على زر control -أو command على ماك Mac-.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4186_56" style="">
<span class="tag">&lt;select</span><span class="pln"> </span><span class="atn">multiple</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"1"</span><span class="tag">&gt;</span><span class="pln">0001</span><span class="tag">&lt;/option&gt;</span><span class="pln">
  </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"2"</span><span class="tag">&gt;</span><span class="pln">0010</span><span class="tag">&lt;/option&gt;</span><span class="pln">
  </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"4"</span><span class="tag">&gt;</span><span class="pln">0100</span><span class="tag">&lt;/option&gt;</span><span class="pln">
  </span><span class="tag">&lt;option</span><span class="pln"> </span><span class="atn">value</span><span class="pun">=</span><span class="atv">"8"</span><span class="tag">&gt;</span><span class="pln">1000</span><span class="tag">&lt;/option&gt;</span><span class="pln">
</span><span class="tag">&lt;/select&gt;</span><span class="pln"> = </span><span class="tag">&lt;span</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"output"</span><span class="tag">&gt;</span><span class="pln">0</span><span class="tag">&lt;/span&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let select </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"select"</span><span class="pun">);</span><span class="pln">
  let output </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"#output"</span><span class="pun">);</span><span class="pln">
  select</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"change"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let number </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let option of </span><span class="typ">Array</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">select</span><span class="pun">.</span><span class="pln">options</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">option</span><span class="pun">.</span><span class="pln">selected</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        number </span><span class="pun">+=</span><span class="pln"> </span><span class="typ">Number</span><span class="pun">(</span><span class="pln">option</span><span class="pun">.</span><span class="pln">value</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    output</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> number</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<h2>
	حقول الملفات
</h2>

<p>
	صُمِّمت حقول الملفات ابتداءً على أساس طريقة لرفع الملفات من حاسوب المستخدِم من خلال استمارة؛ أما في المتصفحات الحديثة، فهي توفر طريقةً لقراءة تلك الملفات، ولكن من خلال برامج جافاسكربت، إذ يتصرف الحقل على أساس حارس لبوابة، بحيث لا تستطيع السكربت البدء بقراءة ملفات خاصة بالمستخدِم من حاسوبه، لكن إذا اختار المستخدِم ملفًا في حقل كهذا، فسيفسِّر المتصفح ذلك الإجراء على أنه سماح للسكربت بقراءة الملف، ويبدو حقل الملف على أساس زر عليه عنوان مثل "اختر الملف" أو "تصفح الملف" مع معلومات عن الملف المختار تظهر إلى جانبه.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_58" style="">
<span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"file"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let input </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">);</span><span class="pln">
  input</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"change"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">input</span><span class="pun">.</span><span class="pln">files</span><span class="pun">.</span><span class="pln">length </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let file </span><span class="pun">=</span><span class="pln"> input</span><span class="pun">.</span><span class="pln">files</span><span class="pun">[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"You chose"</span><span class="pun">,</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">name</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">file</span><span class="pun">.</span><span class="pln">type</span><span class="pun">)</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"It has type"</span><span class="pun">,</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">type</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تحتوي الخاصية <code>files</code> لعنصر حقل الملف على الملفات المختارة في الحقل، وهي كائن شبيه بالمصفوفة وليست مصفوفةً حقيقيةً، كما تكون فارغةً في البداية، والسبب في عدم وجود خاصية مستقلة باسم <code>file</code>، هو دعم الحقول لسمة <code>multiple</code> التي تجعل من الممكن تحديد عدة ملفات في الوقت نفسه، كما تحتوي الكائنات في كائن <code>files</code> على خاصيات مثل <code>name</code> لاسم الملف و<code>size</code> لحجمه مقدَّرًا بالبايت -الذي هو وحدة قياس تخزينية تتكون من 8 بِتّات-، كما تحتوي على الخاصية <code>type</code> التي تمثِّل نوع وسائط media الملف التي قد تكون نصًا عاديًا <code>text/plain</code> أو صورةً <code>image/jpeg</code>، لكن ليس لتلك الكائنات خاصيةً يكون فيها محتوى الملف، وبما أنّ قراءة الملف من القرص ستستغرق وقتًا، فيجب أن تكون الواجهة غير تزامنية لتجنب تجميد أو تعليق المستند أثناء قراءته.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_60" style="">
<span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"file"</span><span class="pln"> multiple</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let input </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">);</span><span class="pln">
  input</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"change"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let file of </span><span class="typ">Array</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">input</span><span class="pun">.</span><span class="pln">files</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let reader </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileReader</span><span class="pun">();</span><span class="pln">
      reader</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"load"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"File"</span><span class="pun">,</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"starts with"</span><span class="pun">,</span><span class="pln">
                    reader</span><span class="pun">.</span><span class="pln">result</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">20</span><span class="pun">));</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
      reader</span><span class="pun">.</span><span class="pln">readAsText</span><span class="pun">(</span><span class="pln">file</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تتم عملية قراءة الملف من خلال إنشاء كائن <code>FileReader</code> الذي يسجِّل معالج الحدث <code>"load"</code> له، ويستدعي التابع <code>readAsText</code> الخاص به ليعطيه الملف الذي نريد قراءته، كما ستحتوي الخاصية <code>result</code> الخاصة بالقارئ على محتويات الملف بمجرد انتهاء التحميل، ويطلق الكائن <code>FileReader</code> أيضًا حدث خطأ <code>"error"</code> عند فشل قراءة الملف لأيّ سبب، إذ سيؤول كائن الخطأ نفسه في خاصية <code>error</code> الخاصة بالقارئ، ورغم تصميم تلك الواجهة قبل أن تصبح الوعود promises جزءًا من جافاسكربت، إلا أنك تستطيع تغليفها بوعد كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_62" style="">
<span class="kwd">function</span><span class="pln"> readFileText</span><span class="pun">(</span><span class="pln">file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let reader </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FileReader</span><span class="pun">();</span><span class="pln">
    reader</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="pln">
      </span><span class="str">"load"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> resolve</span><span class="pun">(</span><span class="pln">reader</span><span class="pun">.</span><span class="pln">result</span><span class="pun">));</span><span class="pln">
    reader</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="pln">
      </span><span class="str">"error"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> reject</span><span class="pun">(</span><span class="pln">reader</span><span class="pun">.</span><span class="pln">error</span><span class="pun">));</span><span class="pln">
    reader</span><span class="pun">.</span><span class="pln">readAsText</span><span class="pun">(</span><span class="pln">file</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	تخزين البيانات في جانب العميل
</h2>

<p>
	ستكون صفحات HTML البسيطة التي فيها قليل من جافاسكربت صيغةً رائعةً من أجل التطبيقات المصغَّرة، وهي برامج مساعِدة صغيرة تؤتمت مهامًا أساسية عبر توصيل بعض حقول الاستمارات بمعالِجات الأحداث، حيث يمكنك فعل أيّ شيء بدءًا من التحويل بين وحدات القياس المختلفة إلى حساب كلمات المرور من كلمة مرور رئيسية واسم موقع.
</p>

<p>
	لكن لا نستطيع استخدام رابطات جافاسكربت إذا احتاج مثل ذلك التطبيق إلى تذكر أمر بين جلساته sessions، ذلك أنّ هذه الرابطات تُحذَف عند كل إغلاق للصفحة، غير أنه يمكن إعداد خادم وتوصيله بالانترنت وجعل التطبيق يخزن هناك، وسنرى كيفية فعل ذلك في مقال لاحق، لكن المقام يقصر هنا عن شرحه لتعقيده، وعلى أيّ حال، من المفيد أحيانًا إبقاء البيانات في المتصفح، كما يمكن استخدام الكائن <code>localStorage</code> لتخزين البيانات بطريقة تبقيها موجودةً حتى مع إعادة تحميل الصفحات، حيث يسمح لك ذلك الكائن بتصنيف قيم السلاسل النصية تحت أسماء.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_64" style="">
<span class="pln">localStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">"username"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"marijn"</span><span class="pun">);</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">localStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="str">"username"</span><span class="pun">));</span><span class="pln">
</span><span class="com">// → marijn</span><span class="pln">
localStorage</span><span class="pun">.</span><span class="pln">removeItem</span><span class="pun">(</span><span class="str">"username"</span><span class="pun">);</span></pre>

<p>
	تبقى القيمة الموجودة في <code>localStorage</code> إلى أن يُكتَب فوقها، كما تُحذَف باستخدام <code>removeItem</code> أو بمحو المستخدِم لبياناته المحلية، وتحصل المواقع التي هي من نطاقات مختلفة على أقسام تخزين مختلفة، ويعني هذا نظريًا عدم إمكانية قراءة وتعديل البيانات المخزَّنة في <code>localStorage</code> من قِبل موقع ما إلا بسكربتات من نفس الموقع، كما تضع المتصفحات حدًا لحجم البيانات التي يمكن للمتصفح أن يخزِّنها في <code>localStorage</code>، وذلك لمنع تلك الخاصية من ملء أقراص المستخدِمين الصلبة ببيانات عديمة الفائدة واستخدام مساحات كبيرة بها، وتنفِّذ الشيفرة التالية تطبيقًا لكتابة الملاحظات، حيث يحتفظ بمجموعة من الملاحظات المسماة ويسمح للمستخدِم بتعديل الملاحظات وإنشاء الجديدا منها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_66" style="">
<span class="typ">Notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">select</span><span class="pun">&gt;&lt;</span><span class="str">/select&gt; &lt;button&gt;Add&lt;/</span><span class="pln">button</span><span class="pun">&gt;&lt;</span><span class="pln">br</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">textarea style</span><span class="pun">=</span><span class="str">"width: 100%"</span><span class="pun">&gt;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let list </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"select"</span><span class="pun">);</span><span class="pln">
  let note </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"textarea"</span><span class="pun">);</span><span class="pln">

  let state</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> setState</span><span class="pun">(</span><span class="pln">newState</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    list</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let name of </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">(</span><span class="pln">newState</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let option </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"option"</span><span class="pun">);</span><span class="pln">
      option</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> name</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newState</span><span class="pun">.</span><span class="pln">selected </span><span class="pun">==</span><span class="pln"> name</span><span class="pun">)</span><span class="pln"> option</span><span class="pun">.</span><span class="pln">selected </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
      list</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">option</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    note</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> newState</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">[</span><span class="pln">newState</span><span class="pun">.</span><span class="pln">selected</span><span class="pun">];</span><span class="pln">

    localStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">"Notes"</span><span class="pun">,</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">newState</span><span class="pun">));</span><span class="pln">
    state </span><span class="pun">=</span><span class="pln"> newState</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  setState</span><span class="pun">(</span><span class="pln">JSON</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">localStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="str">"Notes"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    notes</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"shopping list"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Carrots\nRaisins"</span><span class="pun">},</span><span class="pln">
    selected</span><span class="pun">:</span><span class="pln"> </span><span class="str">"shopping list"</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  list</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"change"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    setState</span><span class="pun">({</span><span class="pln">notes</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">,</span><span class="pln"> selected</span><span class="pun">:</span><span class="pln"> list</span><span class="pun">.</span><span class="pln">value</span><span class="pun">});</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  note</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"change"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    setState</span><span class="pun">({</span><span class="pln">
      notes</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">,</span><span class="pln">
                           </span><span class="pun">{[</span><span class="pln">state</span><span class="pun">.</span><span class="pln">selected</span><span class="pun">]:</span><span class="pln"> note</span><span class="pun">.</span><span class="pln">value</span><span class="pun">}),</span><span class="pln">
      selected</span><span class="pun">:</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">selected
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">)</span><span class="pln">
    </span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let name </span><span class="pun">=</span><span class="pln"> prompt</span><span class="pun">(</span><span class="str">"Note name"</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> setState</span><span class="pun">({</span><span class="pln">
        notes</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">assign</span><span class="pun">({},</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">notes</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{[</span><span class="pln">name</span><span class="pun">]:</span><span class="pln"> </span><span class="str">""</span><span class="pun">}),</span><span class="pln">
        selected</span><span class="pun">:</span><span class="pln"> name
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تحصل هذه السكربت على حالتها من القيمة <code>"Notes"</code> المخزَّنة في <code>localStorage</code>، وإذا لم تكن موجودة، فستنشئ حالة مثال وليس فيها إلا قائمة تسوق، ونحصل على القيمة <code>null</code>عند محاولة قراءة حقل غير موجود من <code>localStorage</code>، كما أنّ تمرير <code>null</code> إلى <code>JSON.parse</code> سيجعله يحلل السلسلة النصية <code>"null"</code> ويُعيد <code>null</code>، وعلى ذلك يمكن استخدام العامِل <code>||</code> لتوفير قيمة افتراضية في مثل هذه المواقف؛ أما التابع <code>setState</code> فيتأكد أنّ DOM يُظهِر حالةً معطاةً ويخزِّن الحالة الجديدة في <code>localStorage</code>، كما تستدعي معالِجات الأحداث هذه الدالة للانتقال إلى حالة جديدة.
</p>

<p>
	كان استخدام <code>Object.assign</code> في المثال السابق من أجل إنشاء كائن جديد يكون نسخة من <code>state.notes</code> القديم، لكن مع خاصية واحدة مضافة أو مكتوب فوقها، وتأخذ <code>Object.assign</code> وسيطها الأول وتضيف جميع الخاصيات من أيّ وسائط آخرين إليه، فإذا أعطيناها كائنًا فارغًا فسنجعلها تملأ كائنًا جديدًا، وتُستخدَم الأقواس المربعة في الوسيط الثالث لإنشاء خاصية اسمها يكون مبنيًا على قيمة ديناميكية، كما لدينا كائن آخر يشبه <code>localStorage</code> اسمه <code>sessionStorage</code>، والاختلاف بين الاثنين هو أنّ محتوى الأخير يُنسى بنهاية كل جلسة، حيث يحدث عند إغلاق المتصفح وذلك بالنسبة لأغلب المتصفحات.
</p>

<h2>
	خاتمة
</h2>

<p>
	ناقشنا في هذا المقال كيفية عمل بروتوكول HTTP، وقلنا أنّ العميل يرسل الطلب الذي يحتوي على تابع يكون <code>GET</code> في الغالب ومسار يحدِّد المصدر، ثم يقرر الخادم بعدها ماذا يفعل بذلك الطلب ويستجيب بشيفرة حالة ومتن استجابة، وقد يحتوي كل من الطلب والاستجابة على ترويسات توفر لنا معلومات إضافية، كما تُسمى الواجهة التي تستطيع جافاسكربت التي في المتصفح إنشاء طلبات HTTP منها باسم <code>fetch</code>، إذ يبدو إنشاء الطلب كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_68" style="">
<span class="pln">fetch</span><span class="pun">(</span><span class="str">"/18_http.html"</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">r </span><span class="pun">=&gt;</span><span class="pln"> r</span><span class="pun">.</span><span class="pln">text</span><span class="pun">()).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">text </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">The</span><span class="pln"> page starts </span><span class="kwd">with</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">text</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">15</span><span class="pun">)}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	كما عرفنا من قبل، تنشئ المتصفحات طلبات <code>GET</code> لجلب الموارد المطلوبة لعرض صفحة ويب، وقد تحتوي الصفحة على استمارات تسمح للمستخدِم بإدخال المعلومات التي سترسلها بعدها في صورة طلب إلى الصفحة الجديدة عند إرسال الاستمارة، كما تستطيع HTML تمثيل عدة أنواع من حقول الاستمارات مثل الحقول النصية وأزرار الاختيار وحقول الاختيار من متعدد ومختارات الملفات file pickers، إذ يمكن فحص مثل تلك الحقول وتعديلها باستخدام جافاسكربت، وهي تطلق الحدث <code>"change"</code> عند تعديلها والحدث <code>"input"</code> عند كتابة نص فيها، كما تستقبل أحداث لوحة المفاتيح عند انتقال نشاط لوحة المفاتيح إليها.
</p>

<p>
	عرفنا أيضًا أنّ الخاصيات مثل <code>value</code> -المستخدَمة في النصوص وحقول التحديد- أو <code>checked</code> -المستخدَمة في أزرار الاختيار وأزرار الانتقاء-، تُستخدَم من أجل قراءة أو تعيين محتوى الحقل، كما رأينا أن الحدث <code>"submit"</code> يُطلَق عند إرسال الاستمارة عليها، ويستطيع معالِج جافاسكربت استدعاء <code>preventDefault</code> على ذلك الحدث من أجل تعطيل السلوك الافتراضي للمتصفح، كما قد توجد عناصر حقول الاستمارات خارج وسم الاستمارة نفسه.
</p>

<p>
	إذا اختار المستخدم ملفًا من نظام ملفاته المحلي في حقل مختار الملفات، فيمكن استخدام الواجهة <code>FileReader</code> من أجل الوصول إلى محتويات ذلك الملف من برنامج جافاسكربت، كما يُستخدم الكائنان <code>localStorage</code> و<code>sessionStorage</code> لحفظ المعلومات حتى مع إعادة التحميل، إذ يحتفظ الكائن الأول بالبيانات احتفاظًا دائمًا أو إلى أن يقرر المستخدِم محوها؛ أما الثاني فيحفظها إلى حين إغلاق المتصفح.
</p>

<h2>
	تدريبات
</h2>

<h3>
	التفاوض على المحتوى
</h3>

<p>
	أحد الأمور التي يفعلها بروتوكول HTTP هو التفاوض على المحتوى content negotiation، حيث تُستخدَم ترويسة الطلب <code>Accept</code> لإخبار الخادم بنوع المستند الذي يريده العميل، وتتجاهل العديد من الخوادم هذه الترويسة، لكن إذا عرف الخادم عدة طرق لترميز أحد الموارد، فسينظر حينها في هذه الترويسة ويرسل نوع الملف الذي يريده العميل.
</p>

<p>
	لقد هُيء الرابط <a href="https://eloquentjavascript.net/author" rel="external nofollow">https://eloquentjavascript.net/author</a> ليستجيب للنصوص المجردة أو HTML أو JSON وفقًا لما يطلبه العميل، وتعرَّف تلك الصيغ بأنواع وسائط قياسية هي <code>text/plain</code> و<code>text/html</code> و<code>application/json</code>.
</p>

<p>
	أرسِل طلبات لجلب هذه الصيغ الثلاث من ذلك الرابط، واستخدم الخاصية <code>headers</code> في كائن الخيارات الممرَّر إلى <code>fetch</code> لضبط الترويسة <code>Accept</code> إلى نوع الوسائط media المفضل، ثم حاول طلب نوع الوسائط <code>application/rainbows+unicorns</code>، وانظر إلى شيفرة الحالة التي تنتجها.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_70" style="">
<span class="com">// شيفرتك هنا.</span></pre>

<h4>
	إرشادات الحل
</h4>

<ul>
<li>
		ابن شيفرتك على أمثلة <code>fetch</code> الموجودة في المقال أعلاه.
	</li>
	<li>
		إذا طلبت أنواع وسائط كاذبة فستحصل على استجابة بالرمز 406، بعنى "غير مقبول" أو Not acceptable، وهو الرمز الذي يجب أن يعيده الخادم إذا لم يستطع تحقيق الترويسة <code>Accept</code>.
	</li>
</ul>
<h3>
	طاولة عمل جافاسكربت
</h3>

<p>
	ابن واجهةً تسمح للناس بكتابة شيفرات جافاسكربت ويشغلونها، وضَع زرًا بجانب حقل <code>&lt;textarea&gt;</code>، بحيث إذا ضُغط عليه يمرِّر الباني <code>Function</code> الذي رأيناه في مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1286/" rel="">الوحدات Modules في جافاسكريبت</a> لتغليف النص في دالة واستدعائها، ثم حوِّل القيمة التي تعيدها الدالة أو أيّ خطأ ترفعه إلى سلسلة نصية واعرضها تحت الحقل النصي.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_72" style="">
<span class="pun">&lt;</span><span class="pln">textarea id</span><span class="pun">=</span><span class="str">"code"</span><span class="pun">&gt;</span><span class="kwd">return</span><span class="pln"> </span><span class="str">"hi"</span><span class="pun">;&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">button id</span><span class="pun">=</span><span class="str">"button"</span><span class="pun">&gt;</span><span class="typ">Run</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">pre id</span><span class="pun">=</span><span class="str">"output"</span><span class="pun">&gt;&lt;/</span><span class="pln">pre</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// شيفرتك هنا.</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<p>
	استخدم <code>document.querySelector</code> أو <code>document.getElementById</code> من أجل الوصول إلى العناصر المعرَّفة في HTML لديك، وبالنسبة للحدَثين <code>"click"</code> و<code>"mousedown"</code>، فسيستطيع معالِج الحدث الحصول على الخاصية <code>value</code> للحقل النصي واستدعاء <code>Function</code> عليها، وتأكد من تغليف كل من الاستدعاء إلى <code>Function</code> والاستدعاء إلى نتيجته في كتلة <code>try</code> كي تستطيع التقاط الاستثناءات التي ترفعها، كما أننا لا نعرف في حالتنا هذه نوع الاستثناء الذي لدينا، لذا يجب التقاط كل شيء.
</p>

<p>
	يمكن استخدام الخاصية <code>textContent</code> الخاصة بعنصر الخرج لملئها برسالة نصية؛ أما في حالة الرغبة في الحفاظ على المحتوى القديم، فأنشئ عقدةً نصيةً جديدةً باستخدام <code>document.createTextNode</code> وألحقها بالعنصر، وتذكّر إضافة محرف سطر جديد إلى النهاية كي لا يظهر كل الخرج على سطر واحد.
</p>

<h3>
	لعبة حياة كونويل
</h3>

<p>
	تُعَدّ لعبة <a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" rel="external nofollow">حياة كونويل Conway game of life</a> محاكاةً بسيطةً تنشئ حياةً صناعيةً على شبكة، بحيث تكون كل خلية في تلك الشبكة إما حيةً أو ميتةً، وتطبق القواعد التالية في كل جيل -منعطف-:
</p>

<ul>
<li>
		تموت أيّ خلية حية لها أكثر من ثلاثة جيران أحياء أو أقل من اثنين.
	</li>
	<li>
		تبقى أيّ خلية حية على قيد الحياة حتى الجيل التالي إذا كان لها جاران أحياء أو ثلاثة.
	</li>
	<li>
		تعود أيّ خلية ميتة إلى الحياة إذا كان لها ثلاثة جيران أحياء حصرًا.
	</li>
</ul>
<p>
	يُعرَّف الجار على أنه أيّ خلية مجاورة بما في ذلك الخلايا المجاورة له قطريًا. لاحظ أنّ تلك القواعد تطبَّق على كامل الشبكة مرةً واحدةً وليس على مربع واحد في المرة، وهذا يعني أنّ عدد الجيران مبني على الموقف الابتدائي، ولا تؤثر التغيرات الحادثة في الخلايا المجاورة في هذا الجيل على الحالة الجديدة للخلية.
</p>

<p>
	نفِّذ هذه اللعبة باستخدام أيّ هيكل بيانات تجده مناسبًا، واستخدم <code>Math.random</code> لتوليد عشوائي لأماكن الخلايا في الشبكة في أول مرة، واعرضها على هيئة شبكة من حقول أزرار الاختيار مع زر بجانبها للانتقال إلى الجيل التالي، كما يجب عند حساب الجيل التالي إدراج التغييرات الحادثة عند اختيار المستخدم لزر الاختيار أو إلغاء الاختيار له.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4186_74" style="">
<span class="pun">&lt;</span><span class="pln">div id</span><span class="pun">=</span><span class="str">"grid"</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">button id</span><span class="pun">=</span><span class="str">"next"</span><span class="pun">&gt;</span><span class="typ">Next</span><span class="pln"> generation</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// شيفرتك هنا.</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<ul>
<li>
		حاول النظر إلى حساب الجيل على أنه دالة محضة تأخذ شبكةً واحدةً وتنتج شبكةً جديدةً تمثِّل الدورة التالية.
	</li>
	<li>
		يمكن تمثيل المصفوفة matrix بالطريقة المبينة في مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AD%D9%8A%D8%A7%D8%A9-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%A9-%D9%84%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1243/" rel="">الحياة السرية للكائنات في جافاسكريبت</a>، حيث تَعد الجيران الأحياء بحلقتين تكراريتين متشعبتَين تكرران على إحداثيات متجاورة في كلا البعدين.
	</li>
	<li>
		لا تَعد الخلايا التي تكون خارج الحقل وتجاهل الخلية التي تكون في المركز عند عَد خلايا مجاورة لها.
	</li>
	<li>
		يمكن ضمان حدوث التغييرات على أزرار الاختيار في الجيل التالي بطريقتين؛ إما أن يلاحظ معالج حدث تلك التغييرات ويحدِّث الشبكة الحالية لتوافق ذلك، أو نولد شبكةً جديدةً من القيم التي في أزرار الاختيار قبل حساب الدورة التالية.
	</li>
	<li>
		إذا اخترت أسلوب معالج الحدث فربما يجب عليك إلحاق سمات تعرِّف الموضع الموافق لكل زر اختيار كي يسهل معرفة الخلية التي يجب تغييرها؛ أما لرسم شبكة من أزرار الاختيار، فيمكن استخدام العنصر <code>&lt;table&gt;</code> أو وضعها جميعًا في العنصر نفسه ووضع عناصر <code>&lt;br&gt;</code> -أي فواصل الأسطر- بين الصفوف.
	</li>
</ul>
<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/18_http.html" rel="external nofollow">للفصل الثامن عشر من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D8%B9%D9%84%D9%89-%D9%84%D9%88%D8%AD%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1364/" rel="">الرسم على لوحة في جافاسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D8%B1%D9%85%D9%88%D8%B2-%D8%A7%D9%84%D8%A5%D8%AC%D8%A7%D8%A8%D8%A9-%D9%81%D9%8A-http-r75/" rel="">رموز الإجابة فيHTTP</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1367</guid><pubDate>Sun, 24 Oct 2021 15:00:00 +0000</pubDate></item><item><title>&#x645;&#x646;&#x62D;&#x646;&#x649; &#x628;&#x64A;&#x632;&#x64A;&#x647; &#x648;&#x623;&#x647;&#x645;&#x64A;&#x62A;&#x647; &#x641;&#x64A; &#x627;&#x644;&#x631;&#x633;&#x648;&#x645;&#x64A;&#x627;&#x62A; &#x648;&#x635;&#x646;&#x627;&#x639;&#x629; &#x627;&#x644;&#x62D;&#x631;&#x643;&#x627;&#x62A; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D9%85%D9%86%D8%AD%D9%86%D9%89-%D8%A8%D9%8A%D8%B2%D9%8A%D9%87-%D9%88%D8%A3%D9%87%D9%85%D9%8A%D8%AA%D9%87-%D9%81%D9%8A-%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-%D9%88%D8%B5%D9%86%D8%A7%D8%B9%D8%A9-%D8%A7%D9%84%D8%AD%D8%B1%D9%83%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1339/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_10/615fe496aac8a_-.png.fbd39997d28f9b45ae8f6dcef81e6ca1.png" /></p>

<p>
	تستخدم منحنيات بيزيه Bezier curves في رسوميات الحاسوب لإنشاء الأشكال والرسوم المتحركة المعتمدة على أوراق التنسيق المتتالية CSS وغيرها. هذه المنحنيات بسيطة وتستحق الدراسة، وبعدها ستكون مرتاحًا عند التعامل مع الرسوميات الشعاعية vector graphics والرسوم المتحركة المتقدمة.
</p>

<h2>
	نقاط التحكم
</h2>

<p>
	يحدد <a data-ss1635001296="1" data-ss1635001913="1" href="https://academy.hsoub.com/design/graphic/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%85%D9%86%D8%AD%D9%86%D9%89-%D8%A8%D9%8A%D8%B2%D9%8A%D9%87-bezier-curve-%D9%81%D9%8A-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D9%88%D8%B3-r623/" rel="">منحني بيزيه</a> بواسطة مجموعة من النقاط الحاكمة، وقد يكون عددها 2 أو 3 أو 4 أو أكثر، لاحظ مثلًا منحنيًا من نقطتين:
</p>

<p style="text-align: center;">
	<img alt="two_points_curve_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79138" data-unique="w2bykx5ve" src="https://academy.hsoub.com/uploads/monthly_2021_10/two_points_curve_01.png.2891768269f4358da4f829ca5b15f711.png"></p>

<p>
	والمنحني التالي محدد بثلاث نقاط:
</p>

<p style="text-align: center;">
	<img alt="three_points_curve_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79137" data-unique="6paz6o78q" src="https://academy.hsoub.com/uploads/monthly_2021_10/three_points_curve_02.png.c33ed9c974a95a6f584febb542cde073.png"></p>

<p>
	وهذا بأربع نقاط:
</p>

<p style="text-align: center;">
	<img alt="four_points_curve_03.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79136" data-unique="zqmojmyvk" src="https://academy.hsoub.com/uploads/monthly_2021_10/four_points_curve_03.png.ef4788cfc961632ca271df5d18c1d1b1.png"></p>

<p>
	لو تمعنت في هذه المنحنيات، فيمكنك أن تلاحظ التالي:
</p>

<ol>
<li>
		لا تنتمي النقاط بالضرورة إلى المنحني وهذا أمر طبيعي، وستفهم ذلك عندما سنشرح طريقة بنائه لاحقًا.
	</li>
	<li>
		درجة المنحني curve order تساوي عدد النقاط ناقصًا واحدًا، فالمنحني المؤلف من نقطتين هو خط مستقيم من الدرجة الأولى، والمنحني المؤلف من ثلاث نقاط هو منحن تربيعي، أي قطع مكافئ، وهكذا.
	</li>
	<li>
		ينحصر المنحني ضمن المضلع المحدّب <a data-ss1635001296="1" data-ss1635001913="1" href="https://en.wikipedia.org/wiki/Convex_hull" rel="external nofollow">convex hull</a> الذي تشكله النقاط الحاكمة:
	</li>
</ol>
<p style="text-align: center;">
	<img alt="curve_inside_convex_hull_04.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79135" data-unique="71j1ro1pm" src="https://academy.hsoub.com/uploads/monthly_2021_10/curve_inside_convex_hull_04.png.5bcdf6177527498f70e4b2c0fbd7281c.png"></p>

<p>
	سنتمكن من أَمثَلة اختبارات التداخل في <a data-ss1635001296="1" data-ss1635001913="1" href="https://academy.hsoub.com/design/general/%D9%82%D9%88%D8%A7%D8%B9%D8%AF-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D8%A7%D9%84%D8%B5%D9%88%D8%B1-%D9%88%D8%A7%D9%84%D8%B1%D8%B3%D9%88%D9%85%D9%8A%D8%A7%D8%AA-r544/" rel="">رسوميات </a>الحاسوب اعتمادًا على الخاصية الأخيرة، فلن تتداخل المنحنيات إن لم تتداخل المضلعات المحدبة، وبالتالي ستعطي دراسة تداخل المضلعات المحدبة جوابًا سريعًا لتداخل المنحنيات، بالإضافة إلى أنّ دراسة تداخل المضلعات أسهل لأنّ أشكالها مثل المثلث والمربع، تُعَد مفهومة موازنةً بالمنحنيات.
</p>

<p>
	تكمن الفائدة الرئيسية لاستخدام منحني بيزيه في تغيير شكل المنحني بمجرد تحريك النقاط الحاكمة. لاحظ المنحني الذي تولده الشيفرة التالية، واستخدم الفأرة لتحريك النقاط الحاكمة وراقب تغير المنحني:
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635001296="1" data-ss1635001913="1" data-trusted="1" src="https://en.js.cx/article/bezier-curve/demo.svg?nocpath=1&amp;p=0,0,0.5,0,0.5,1,1,1" style="display: block; height: 370px;"></iframe>
</p>

<p>
	ستتمكن بعد التمرن قليلًا من رسم المنحني المطلوب بمجرد تحريك النقاط الحاكمة، ويمكنك عمليًا الحصول على أي شكل تريده بوصل عدة منحنيات، إليك بعض الأمثلة:
</p>

<p style="text-align: center;">
	<img alt="connecting_curves_05.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79134" data-unique="wf0kw6lc6" src="https://academy.hsoub.com/uploads/monthly_2021_10/connecting_curves_05.png.61b82e95b22160a474077f513de0048f.png"></p>

<h2>
	خوارزمية دي كاستلجو De Casteljau
</h2>

<p>
	يمكننا استخدام صيغة رياضية لرسم منحنيات بيزيه، وهذا ما سنشرحه لاحقًا، لأنّ خوارزمية دي كاستلجو <a data-ss1635001296="1" data-ss1635001913="1" href="https://en.wikipedia.org/wiki/De_Casteljau's_algorithm" rel="external nofollow">De Casteljau’s algorithm</a> التي نسبت لمخترعها، تتطابق مع التعريف الرياضي، وتساعدنا على تمييز طريقة إنشاء المنحنيات بصريًا.
</p>

<p>
	سنرى أولًا مثالًا لمنحن من ثلاث نقاط حاكمة، لاحظ أنه يمكنك تحريك النقاط 1 و 2 و 3 بالفأرة، ثم انقر زر التشغيل الأخضر.
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635001296="1" data-ss1635001913="1" data-trusted="1" src="https://en.js.cx/article/bezier-curve/demo.svg?p=0,0,0.5,1,1,0&amp;animate=1" style="display: block;  height: 370px;"></iframe>
</p>

<h3>
	خوازمية دي كاستلجو لبناء منحني بيزيه من ثلاث نقاط
</h3>

<ol>
<li>
		ارسم النقاط الثلاث، والتي تمثلها النقاط 1 و2 و3 في المثال السابق.
	</li>
	<li>
		صل بين النقاط السابقة باتجاه واحد 3 → 2 → 1، وهي الخطوط البنية في المثال السابق.
	</li>
	<li>
		يأخذ المعامل <code>t</code> قيمه بين "0" و"1"، وقد استخدمنا في مثالنا النموذجي السابق خطوةً مقدارها "0.05"، أي تتحرك حلقة التنفيذ وفق الآتي: 0 ثم 0.05 ثم 0.1 ثم 0.15 وهكذا.
	</li>
</ol>
<p>
	لكل قيمة من قيم <code>t</code>:
</p>

<ul>
<li>
		نأخذ على كل خط بني بين نقطتين متتاليتين نقطةً تبعد عن بدايته مسافةً تتناسب مع <code>t</code>، وطالما أن هناك خطان، فسنحصل على نقطتين، وستكون كلتا النقطتين في بداية الخط عندما يكون <code>t=0</code> مثلًا، كما ستبعد النقطة الأولى عن بداية الخط مسافةً تعادل 25% من طوله عندما تكون <code>t=0.25</code>، وهكذا.
	</li>
	<li>
		نصل بين النقطتين المتشكلتين، والخط الواصل في المثال التالي باللون الأزرق.
	</li>
</ul>
<style type="text/css">
table {
    width: 100%;
}

thead {
    vertical-align: middle;
    text-align: center;
} 

td, th {
    border: 1px solid #dddddd;
    text-align: right;
    padding: 8px;
    text-align: inherit;

}
tr:nth-child(even) {
    background-color: #dddddd;
}
p iframe.code-result__iframe {
    border: 1px solid #e7e5e3 !important;
}</style>
<table>
<thead><tr>
<th style="text-align:left">
				<code>t=0.25</code>
			</th>
			<th style="text-align:left">
				<code>t=0.5</code>
			</th>
		</tr></thead>
<tbody><tr>
<td style="text-align:left">
				<img alt="building_curve_second_07.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79133" data-unique="orm7b8kpi" src="https://academy.hsoub.com/uploads/monthly_2021_10/building_curve_second_07.png.7734a35bd05343c2137e4716df1ea6a5.png">
</td>
			<td style="text-align:left">
				<img alt="building_curve_first_06.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79132" data-unique="qe9y4cge9" src="https://academy.hsoub.com/uploads/monthly_2021_10/building_curve_first_06.png.12bac1418bc484a338bb636ba4682fe1.png">
</td>
		</tr></tbody>
</table>
<ol start="4">
<li>
		خذ على الخط الأزرق السابق نقطةً تبعد عن طرفه مقدارًا يتناسب مع <code>t</code>، ستكون النقطة في نهاية الربع الأخير من الخط من أجل <code>t=0.25</code>، وستكون في منتصف الخط من أجل <code>t=0.5</code>، وهي النقطة الحمراء في الشكل السابق.
	</li>
	<li>
		عندما تتحول <code>t</code> بين 0 و1 ستضيف كل قيمة لهذا المعامل نقطةً من نقاط المنحني، وتمثل هذه المجموعة من النقاط منحني بيزيه.
	</li>
</ol>
<p>
	نتبع الأسلوب ذاته بالنسبة لمنحنى مكون من أربع نقاط حاكمة.
</p>

<p>
	<iframe class="code-result__iframe" data-ss1635001296="1" data-ss1635001913="1" data-trusted="1" src="https://en.js.cx/article/bezier-curve/demo.svg?p=0,0,0.5,0,0.5,1,1,1&amp;animate=1" style="display: block; height: 370px;"></iframe>
</p>

<p>
	الخوارزمية المتبعة لأربع نقاط هي:
</p>

<ul>
<li>
		صل بين النقاط الحاكمة 1 إلى 2، و2 إلى 3، و3 إلى 4، وستحصل على ثلاثة خطوط بنية.
	</li>
	<li>
		لكل قيمة للمعامل <code>t</code> بين 0 و1:
	</li>
	<li>
		نأخذ نقاطًا على الخطوط الثلاثة تتناسب مع <code>t</code> كما فعلنا سابقًا، وبوصل هذه النقاط الثلاثة المتشكلة سنحصل على خطين جديدين، باللون الأخضر مثلًا.
	</li>
	<li>
		نأخذ على الخطين الأخضرين نقاطًا متناسبةً مع <code>t</code>، وعندها سنصل بين النقطتين المتشكلتين فنحصل على خط واحد، وليكن باللون الأزرق.
	</li>
	<li>
		نأخذ على الخط الأزرق نقطةً تبعد عن أحد طرفي الخط مسافةً متناسبةً مع <code>t</code>، فنحصل على نقطة حمراء واحدة تمثل أحد نقاط منحني بيزيه.
	</li>
	<li>
		تمثل النقاط الحمراء كلها المنحني الكامل.
	</li>
</ul>
<p>
	إن الخوارزمية السابقية تعاودية، أي قادرة على إعادة نفسها، لذلك يمكن تعميمها إلى N نقطة حاكمة:
</p>

<ol>
<li>
		نصل بين النقاط فنحصل على N-1 خطًا.
	</li>
	<li>
		لكل قيمة للمعامل <code>t</code> بين 0 و1 نأخذ على كل خط نقطةً تبعد عن طرف الخط مسافةً متناسبةً مع <code>t</code>، ونصل بين هذه النقاط فنحصل على N-2 خطًا.
	</li>
	<li>
		نكرر الخطوة 2 حتى نحصل على نقطة واحدة تمثل إحدى نقاط منحني بيزيه.
	</li>
	<li>
		تشكل جميع النقاط الناتجة في الخطوة 3 منحني بيزيه.
	</li>
</ol>
<p>
	شغّل الرسوميات التالية ولاحظ شكل المنحني:
</p>

<ul>
<li>
		منحن يبدو مثل الخط البياني للدالة <code>y=1/t</code>.
	</li>
</ul>
<p>
	<iframe class="code-result__iframe" data-ss1635001296="1" data-ss1635001913="1" data-trusted="1" src="https://en.js.cx/article/bezier-curve/demo.svg?p=0,0,0,0.75,0.25,1,1,1&amp;animate=1" style="display: block; height: 370px;"></iframe>
</p>

<ul>
<li>
		نقاط حاكمة بشكل Zig-zag تبدو جيدةً أيضًا:
	</li>
</ul>
<p>
	<iframe class="code-result__iframe" data-ss1635001296="1" data-ss1635001913="1" data-trusted="1" src="https://en.js.cx/article/bezier-curve/demo.svg?p=0,0,1,0.5,0,0.5,1,1&amp;animate=1" style="display: block; height: 370px;"></iframe>
</p>

<ul>
<li>
		يمكن رسم حلقة:
	</li>
</ul>
<p>
	<iframe class="code-result__iframe" data-ss1635001296="1" data-ss1635001913="1" data-trusted="1" src="https://en.js.cx/article/bezier-curve/demo.svg?p=0,0,1,0.5,0,1,0.5,0&amp;animate=1" style="display: block; height: 370px;"></iframe>
</p>

<ul>
<li>
		يمكن رسم منحني بيزيه غير أملس أيضًا:
	</li>
</ul>
<p>
	<iframe class="code-result__iframe" data-ss1635001296="1" data-ss1635001913="1" data-trusted="1" src="https://en.js.cx/article/bezier-curve/demo.svg?p=0,0,1,1,0,1,1,0&amp;animate=1" style="display: block;height: 370px;"></iframe>
</p>

<p>
	تابع الأمثلة الحية السابقة لترى كيف يُبنى المنحني، إذا وجدت شيئًا مبهمًا في وصف الخوارزمية.
</p>

<p>
	يمكن استخدام العدد الذي نريده من النقاط الحاكمة لرسم منحني بيزيه من أي درجة، ما دامت الخوارزمية تعاوديةً، لكن يفضل عمليًا استخدام عدد قليل من النقاط -عادةً 2 أو 3 نقاط-، ثم نربط بين عدة منحنيات بسيطة إذا أردنا شكلًا أكثر تعقيدًا.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		<strong>كيف ترسم منحنيًا من خلال مجموعة محددة من النقاط؟</strong> لتحديد منحني بيزيه لا بدّ من استخدام نقاط حاكمة، وكما رأينا، لا تنتمي النقاط الحاكمة بالضرورة إلى المنحني عدا النقطتان الأولى والأخيرة. ينبغي علينا أحيانًا تنفيذ مهمة أخرى، فلرسم منحن يمر عبر عدة نقاط جميعها على منحن أملس واحد، فلا بدّ من تنفيذ مهمة تُدعى <a data-ss1635001296="1" data-ss1635001913="1" href="https://en.wikipedia.org/wiki/Interpolation" rel="external nofollow">الاستيفاء interpolation</a>، لكننا لن نغطي ذلك الآن. توجد صيغ رياضية لرسم منحنيات كهذه مثل <a data-ss1635001296="1" data-ss1635001913="1" href="https://en.wikipedia.org/wiki/Lagrange_polynomial" rel="external nofollow">كثير حدود لاغرانج Lagrange polynomial</a>، لكن ما نستخدمه في عالم رسوميات الحاسب هو <a data-ss1635001296="1" data-ss1635001913="1" href="https://en.wikipedia.org/wiki/Spline_interpolation" rel="external nofollow">الاستيفاء القِطَعي spline interpolation</a> لبناء منحنيات ملساء تصل بين عدة نقاط.
	</p>
</blockquote>

<h2>
	الصيغ الرياضية
</h2>

<p>
	يمكن توصيف منحني بيزيه باستخدام صيغة رياضية، وكما رأينا فلا حاجة لمعرفة هذه الصيغة. يرسم المستخدمون المنحني بمجرد تحريك النقاط بالفأرة، لكن إذا أردت استخدام الرياضيات فلك هذا.
</p>

<p>
	لنفترض أنّ إحداثيي نقطة حاكمة هو <code>Pi</code>. ستكون النقطة الأولى <code>(P1 = (x1, y1</code>، والثانية <code>(P2 = (x2, y2</code>، وهكذا حتى آخر نقطة <code>(Pi = (xi, yi</code>، ويمكن أن نصف منحني بيزيه بمعادلة متغيرها <code>t</code> يأخذ قيمه بين 0 و1.
</p>

<ul>
<li>
		صيغة منحن يمر بنقطتين هي:
	</li>
</ul>
<p style="text-align: center;">
	<img alt="Bezier_curve_Extended_formula_10.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80522" data-unique="nw1wt477i" src="https://academy.hsoub.com/uploads/monthly_2021_10/Bezier_curve_Extended_formula_10.png.4f3c2bd6c96574bc6972f47358d8d872.png"></p>

<ul>
<li>
		صيغة منحن يمر من 3 نقاط هي:
	</li>
</ul>
<p style="text-align: center;">
	<img alt="Bezier_curve_math_formula_11.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80523" data-unique="0swqmj3xe" src="https://academy.hsoub.com/uploads/monthly_2021_10/Bezier_curve_math_formula_11.png.da4b37c6993fcae948f30afba8bc0582.png"></p>

<ul>
<li>
		صيغة منحن يمر من 4 نقاط هي:
	</li>
</ul>
<p style="text-align: center;">
	<img alt="Bezier_curve_math_formula_12.png" class="ipsImage ipsImage_thumbnailed" data-fileid="80524" data-unique="52sybednb" src="https://academy.hsoub.com/uploads/monthly_2021_10/Bezier_curve_math_formula_12.png.3f1b00fb257a3278f68c84971fa898b7.png"></p>

<ul>
<li>
		إنّ المعادلات السابقة هي معادلات شعاعية، أي يمكننا وضع الإحداثيين <code>x</code> و<code>y</code> بدلًا من <code>P</code>، فمن أجل المنحني المار من ثلاث نقاط مثلًا ستصبح الصيغة بدلالة <code>x</code> و<code>y</code> هي:
	</li>
</ul>
<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="79131" data-ss1635001296="1" data-ss1635001913="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/Bezier_curve_math_formula_08.png.41e165712c356de7b97b05fecd82c588.png" rel=""><img alt="Bezier_curve_math_formula_08.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79131" data-unique="kdnjh1s87" src="https://academy.hsoub.com/uploads/monthly_2021_10/Bezier_curve_math_formula_08.png.41e165712c356de7b97b05fecd82c588.png"></a>
</p>

<ul>
<li>
		ينبغي تعويض القيم <code>x1, y1, x2, y2, x3, y3</code> في المعادلات وكتابة الصيغة بدلالة المتغير <code>t</code>، فلو فرضنا أنّ إحداثيات النقاط الحاكمة الثلاثة هي: <code>(0,0)</code> و<code>(0.5, 1)</code> و<code>(1, 0)</code>،فستصبح قيمة إحداثيي النقطة المقابلة من منحني بيزيه:
	</li>
</ul>
<p style="text-align: center;">
	<img alt="Bezier_curve_Extended_formula_09.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79130" data-unique="m8y2qdeve" src="https://academy.hsoub.com/uploads/monthly_2021_10/Bezier_curve_Extended_formula_09.png.3f19084eacf44b7b5e97ef504c8a4e24.png"></p>

<p>
	سنحصل على إحداثيي نقطة جديدة <code>(x,y)</code> من نقاط منحني بيزيه كلما تغيرت قيمة <code>t</code> بين 0 و1.
</p>

<h2>
	خلاصة
</h2>

<p>
	تُحدَّد منحنيات بيزيه بثلاث نقاط حاكمة، ويمكن رسمها بأسلوبين كما رأينا:
</p>

<ul>
<li>
		باستخدام عملية رسومية: أي خوارزمية دي كاستلجو.
	</li>
	<li>
		باستخدام صيغة رياضية.
	</li>
</ul>
<p>
	من الصفات الجيدة لمنحنيات بيزيه:
</p>

<ul>
<li>
		إمكانية رسم منحنيات ملساء عبر تحريك النقاط الحاكمة باستخدام الفأرة.
	</li>
	<li>
		إمكانية رسم أشكال أكثر تعقيدًا بوصل عدة منحنيات بيزيه معًا.
	</li>
</ul>
<p>
	استخدامات منحنيات بيزيه:
</p>

<ul>
<li>
		في رسوميات الحاسوب والنمذجة ومحررات الرسوميات الشعاعية، كما توصف بها أنواع خطوط الكتابة.
	</li>
	<li>
		في تطوير تطبيقات الويب، من خلال الرسم ضمن لوحات وفي الملفات بتنسيق <a data-ss1635001296="1" data-ss1635001913="1" href="https://academy.hsoub.com/design/illustration/%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A3%D8%B4%D9%83%D8%A7%D9%84-%D8%A8%D8%B3%D9%8A%D8%B7%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-svg-r382/" rel="">SVG</a>، وقد كتبت الأمثلة النموذجية السابقة بتنسيق SVG، وهي عمليًا مستند SVG مفرد أُعطي نقاطًا مختلفةً مثل معاملات، ويمكن فتح هذه النماذج في نوافذ منفصلة والاطلاع على الشيفرة المصدرية: <a data-ss1635001296="1" data-ss1635001913="1" href="https://javascript.info/article/bezier-curve/demo.svg?p=0,0,1,0.5,0,0.5,1,1&amp;animate=1" rel="external nofollow">demo.svg</a>.
	</li>
	<li>
		في الرسوميات المتحركة لوصف مسارها وسرعتها.
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للفصل <a data-ss1635001296="1" data-ss1635001913="1" href="https://javascript.info/bezier-curve" rel="external nofollow">Bezier curve</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a data-ss1635001296="1" data-ss1635001913="1" href="https://academy.hsoub.com/programming/javascript/%D9%83%D8%A7%D8%A6%D9%86%D8%A7-localstorage-%D9%88sessionstorage-%D9%84%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1338/" rel="">كائنا localStorage وsessionStorage لتخزين بيانات الويب في جافاسكربت</a>
	</li>
	<li>
		<a data-ss1635001296="1" data-ss1635001913="1" href="https://academy.hsoub.com/design/graphic/%D8%A3%D8%B3%D8%A7%D8%B3%D9%8A%D8%A7%D8%AA-%D9%85%D9%86%D8%AD%D9%86%D9%89-%D8%A8%D9%8A%D8%B2%D9%8A%D9%87-bezier-curve-%D9%81%D9%8A-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D9%88%D8%B3-r623/" rel="">أساسيات منحنى بيزيه Bezier Curve في سكريبوس</a>
	</li>
	<li>
		<a data-ss1635001296="1" data-ss1635001913="1" href="https://academy.hsoub.com/programming/css/%D8%AA%D8%A3%D8%AB%D9%8A%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%A7%D9%86%D8%AA%D9%82%D8%A7%D9%84-%D9%88%D8%A7%D9%84%D8%AD%D8%B1%D9%83%D8%A9-%D9%81%D9%8A-css-r517/" rel="">تأثيرات الانتقال والحركة في CSS</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1339</guid><pubDate>Tue, 19 Oct 2021 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x631;&#x633;&#x645; &#x639;&#x644;&#x649; &#x644;&#x648;&#x62D;&#x629; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%B1%D8%B3%D9%85-%D8%B9%D9%84%D9%89-%D9%84%D9%88%D8%AD%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1364/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_11/618259ff2fdd1_-01.jpg.4290dda6f3276d245ea4bc349d42c910.jpg" /></p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		الرسم خدعة.
	</p>

	<p>
		إم سي إسكر M.C. Escher، مقتبس بواسطة برونو إرنست Bruno Ernst في المرآة السحرية لإم سي إسكر.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81262" href="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_17.jpg.943cefc7cd340325b87ff594217d4896.jpg" rel=""><img alt="chapter_picture_17.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="81262" data-unique="vkhf3nwm1" src="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_17.jpg.943cefc7cd340325b87ff594217d4896.jpg"></a>
</p>

<p>
	تعطينا المتصفحات طرقًا عدة لعرض الرسوميات على الشاشة، وأبسط تلك الطرق هي استخدام التنسيقات لموضعة وتلوين عناصر شجرة DOM العادية، ويمكن فعل الكثير بهذا كما رأينا في اللعبة التي في <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D9%84%D8%B9%D8%A8%D8%A9-%D9%85%D9%86%D8%B5%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1355/" rel="">المقال السابق</a>، كما يمكننا جعل العقد كما نريد بالضبط من خلال إضافة صور خلفية شبه شفافة إليها، أو أن نديرها أو نزخرفها باستخدام التنسيق <code>transform</code>، لكننا سنستخدِم عناصر DOM هكذا لغير الغرض الذي صُممت له، كما ستكون بعض المهام مثل رسم سطر بين نقطتين عشوائيتين غريبةً إذا نفذناها باستخدام عناصر HTML عادية.
</p>

<p>
	لدينا بديلين هنا، حيث أن البديل الأول مبني على DOM ويستخدِم الرسوميات المتجهية القابلة للتحجيم Scalable Vector Graphics -أو SVG اختصارًا- بدلًا من HTML، كما يمكن النظر إلى SVG على أنها صيغة توصيف مستندات تركِّز على الأشكال بدلًا من النصوص، ويمكن تضمين مستند SVG مباشرةً في مستند HTML أو إدراجه باستخدام الوسم <code>&lt;img&gt;</code>؛ أما البديل الثاني فيدعى اللوحة Canvas، وهو عنصر DOM واحد يغلف صورةً ما، ويوفر واجهةً برمجيةً لرسم الأشكال على مساحة تشغلها عقدة ما.
</p>

<p>
	الفرق الأساسي بين اللوحة وصورة SVG هو أن الوصف الأصلي للشكل في الأخيرة محفوظ بحيث يمكن نقله أو إعادة تحجيمه في أيّ وقت؛ أما اللوحة فتحوِّل الأشكال إلى بكسلات -وهي النقاط الملونة على الشاشة- بمجرد رسمها، كما لا تتذكر ما تمثله تلك البكسلات، والطريقة الوحيدة لنقل شكل على لوحة هي بمسح اللوحة أو الجزء الذي يحيط بالشكل، ثم إعادة رسمه بالشكل في موضع جديد.
</p>

<h2>
	الرسوميات المتجهية القابلة للتحجيم SVG
</h2>

<p>
	لن تخوض هذه السلسلة في تفاصيل SVG وإنما سنشرح كيفية عملها باختصار، كما سنعود إلى عيوبها في نهاية المقال، والتي يجب وضعها في حسبانك حين تريد اتخاذ قرار بشأن آلية الرسم المناسبة لتطبيق ما.
</p>

<p>
	فيما يلي مستند HTML مع صورة SVG بسيطة:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8250_7" style="">
<span class="tag">&lt;p&gt;</span><span class="pln">Normal HTML here.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;svg</span><span class="pln"> </span><span class="atn">xmlns</span><span class="pun">=</span><span class="atv">"http://www.w3.org/2000/svg"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;circle</span><span class="pln"> </span><span class="atn">r</span><span class="pun">=</span><span class="atv">"50"</span><span class="pln"> </span><span class="atn">cx</span><span class="pun">=</span><span class="atv">"50"</span><span class="pln"> </span><span class="atn">cy</span><span class="pun">=</span><span class="atv">"50"</span><span class="pln"> </span><span class="atn">fill</span><span class="pun">=</span><span class="atv">"red"</span><span class="tag">/&gt;</span><span class="pln">
  </span><span class="tag">&lt;rect</span><span class="pln"> </span><span class="atn">x</span><span class="pun">=</span><span class="atv">"120"</span><span class="pln"> </span><span class="atn">y</span><span class="pun">=</span><span class="atv">"5"</span><span class="pln"> </span><span class="atn">width</span><span class="pun">=</span><span class="atv">"90"</span><span class="pln"> </span><span class="atn">height</span><span class="pun">=</span><span class="atv">"90"</span><span class="pln">
        </span><span class="atn">stroke</span><span class="pun">=</span><span class="atv">"blue"</span><span class="pln"> </span><span class="atn">fill</span><span class="pun">=</span><span class="atv">"none"</span><span class="tag">/&gt;</span><span class="pln">
</span><span class="tag">&lt;/svg&gt;</span></pre>

<p>
	تغيِّر السمة <code>xmlns</code> عنصرًا ما -وعناصره الفرعية- إلى فضاء اسم XML مختلف، حيث يحدِّد ذلك الفضاء المعرَّف بواسطة رابط تشعبي URL الصيغة التي نستخدِمها الآن، وعلى ذلك يكون للوسمين <code>&lt;circle&gt;</code> و<code>&lt;rect&gt;</code> معنىً هنا في SVG، رغم أنهما لا يمثِّلان شيئًا في لغة HTML، كما يرسمان هنا الأشكال باستخدام التنسيق والموضع اللذين يحدَّدان بواسطة سماتهما.
</p>

<p>
	تنشئ هذه الوسوم عناصر DOM وتستطيع السكربتات أن تتفاعل معها كما تفعل وسوم HTML تمامًا، إذ تغيِّر الشيفرة التالية مثلًا عنصر <code>&lt;circle&gt;</code> ليُلوَّن باللون السماوي Cyan:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_12" style="">
<span class="pln">let circle </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"circle"</span><span class="pun">);</span><span class="pln">
circle</span><span class="pun">.</span><span class="pln">setAttribute</span><span class="pun">(</span><span class="str">"fill"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"cyan"</span><span class="pun">);</span></pre>

<h2>
	عنصر اللوحة
</h2>

<p>
	يمكن رسم رسوميات اللوحة على عنصر <code>&lt;canvas&gt;</code>، كما تستطيع إعطاء مثل هذا العنصر سمات عرض <code>width</code> وطول <code>height</code> لتحديد حجمها بالبكسلات، وتكون اللوحة الجديدة فارغةً تمامًا، مما يعني أنها شفافة وتظهر مثل مساحة فارغة في المستند، كما يسمح الوسم <code>&lt;canvas&gt;</code> بتنسيقات مختلفة من الرسم، ونحتاج إلى إنشاء سياق context أولًا للوصول إلى واجهة الرسم الحقيقية، وهو كائن توفر توابعه واجهة الرسم.
</p>

<p>
	لدينا حاليًا تنسيقَين من أنماط الرسم المدعومَين دعمًا واسعًا هما <code>"2d"</code> للرسم ثنائي الأبعاد و<code>"webgl"</code> للرسم ثلاثي الأبعاد من خلال واجهة OpenGL، كما أننا لن نناقش واجهة OpenGL هنا، وإنما سنقتصر على الرسم ثنائي الأبعاد، لكن إذا أردت النظر في الرسم ثلاثي الأبعاد فاقرأ في WebGL، إذ توفر واجهةً مباشرةً لعتاد الرسوميات، وتسمح لك بإخراج مشاهد معقدة بكفاءة عالية باستخدَام جافاسكربت.
</p>

<p>
	نستطيع إنشاء سياق بواسطة التابع <code>getContext</code> على <code>&lt;canvas&gt;</code> لعنصر DOM كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_8250_14" style="">
<span class="tag">&lt;p&gt;</span><span class="pln">Before canvas.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;canvas</span><span class="pln"> </span><span class="atn">width</span><span class="pun">=</span><span class="atv">"120"</span><span class="pln"> </span><span class="atn">height</span><span class="pun">=</span><span class="atv">"60"</span><span class="tag">&gt;&lt;/canvas&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;</span><span class="pln">After canvas.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let canvas </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">);</span><span class="pln">
  let context </span><span class="pun">=</span><span class="pln"> canvas</span><span class="pun">.</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  context</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> </span><span class="str">"red"</span><span class="pun">;</span><span class="pln">
  context</span><span class="pun">.</span><span class="pln">fillRect</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	يرسم المثال مستطيلًا أحمرًا بعرض 100 بكسل وارتفاع 50 بكسل بعد إنشاء كائن السياق، ويكون الركن الأيسر العلوي في الإحداثيات هو (10,10)، كما يضع نظام الإحداثيات في عنصر اللوحة الإحداثيات الصفرية (0,0) في الركن الأيسر العلوي كما في HTML وSVG، بحيث يتجه محور الإحداثي y لأسفل من هناك، وبالتالي يكون (10,10) مزاحًا عشرة بكسلات إلى الأسفل وإلى يمين الركن الأيسر العلوي.
</p>

<h2>
	الأسطر والأسطح
</h2>

<p>
	نستطيع ملء الشكل في واجهة اللوحة، مما يعني أننا سنعطي مساحته لونًا أو نقشًا بعينه، أو يمكن تحديده stroked بأن يُرسَم خطًا حول حوافه، وما قيل هنا سيقال في شأن SVG أيضًا، كما يملأ التابع <code>fillRect</code> مستطيلًا ويأخذ إحداثيات x وy للركن العلوي الأيسر للمستطيل ثم عرضه ثم ارتفاعه، ويرسم التابع <code>strokeRect</code> بالمثل الخطوط الخارجية للمستطيل، لكن لا يأخذ هذان التابعان معاملات أخرى، فلا يحدَّد وسيط ما لون الملء ولا سماكة التحديد ولا غيرها، كما قد يُتوقَّع في مثل هذه الحالة، والذي يحدِّد تلك العناصر هي خصائص سياق الكائن، حيث تتحكم الخاصية <code>fillStyle</code> بطريقة ملء الأشكال، ويمكن تعيينها لتكون سلسلةً نصيةً تحدِّد لونًا ما باستخدام ترميز الألوان في CSS؛ أما الخاصية <code>strokeStyle</code> فهي شبيهة بأختها السابقة، لكن تحدد اللون المستخدَم في التحديد، كما يُحدَّد عرض الخط بواسطة الخاصية <code>lineWidth</code> التي قد تحتوي أي عدد موجب.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_8250_17" style="">
<span class="tag">&lt;canvas&gt;&lt;/canvas&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">strokeStyle </span><span class="pun">=</span><span class="pln"> </span><span class="str">"blue"</span><span class="pun">;</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">strokeRect</span><span class="pun">(</span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">lineWidth </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pun">;</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">strokeRect</span><span class="pun">(</span><span class="lit">135</span><span class="pun">,</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	إذا لم تُحدَّد سمة عرض <code>width</code> أو طول <code>height</code> كما في المثال، فسيحصل عنصر اللوحة على عرض افتراضي مقداره 300 بكسل وطول مقداره 150 بكسل.
</p>

<h2>
	المسارات
</h2>

<p>
	المسار هو متسلسلة من الأسطر، ويأخذ عنصر اللوحة ثنائي الأبعاد منظورًا استثنائيًا لوصف مثل تلك المسارات من خلال التأثيرات الجانبية بالكامل، كما لا تُعَدّ المسارات قيمًا يمكن تخزينها وتمريرها من مكان إلى آخر، بل إذا أردنا فعل شيء بمسار ما، فيمكن إنشاء متسلسلة من استدعاءات التوابع لوصف شكله.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_19" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let y </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">;</span><span class="pln"> y </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">100</span><span class="pun">;</span><span class="pln"> y </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">);</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="lit">90</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">stroke</span><span class="pun">();</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	ينشئ هذا المثال مسارًا فيه عدد من أجزاء أسطر أفقية ثم يحدِّدها باستخدام التابع <code>stroke</code>، ويبدأ كل جزء أُنشئ بواسطة <code>lineTo</code> عند الموضع الحالي للمسار، كما يكون ذلك الموضع عادةً في نهاية الجزء الأخير، إلا إذا استدعيت <code>moveTo</code>، حيث سيبدأ الجزء التالي في تلك الحالة عند الموضع الممرَّر إلى <code>moveTo</code>.
</p>

<p>
	يُملأ كل شكل لوحده عند ملء المسار باستخدام التابع <code>fill</code>، وقد يحتوي المسار على عدة أشكال، بحيث تبدأ كل حركة <code>moveTo</code> شكلًا جديدًا، ولكن سيحتاج المسار إلى أن يغلَق قبل إمكانية ملئه، بحيث تكون بدايته ونهايته في الموضع نفسه، فإذا لم يكن المسار مغلقًا، فسيضاف السطر من نهايته إلى بدايته، ويُملأ الشكل المغلَّف بالمسار المكتمِل.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_21" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">70</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="lit">90</span><span class="pun">,</span><span class="pln"> </span><span class="lit">70</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">fill</span><span class="pun">();</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	يرسم هذا المثال مثلثًا مملوءًا. لاحظ أنّ ضلعيَن فقط من أضلاع المثلث هما اللذان رُسما صراحةً؛ أما الثالث الذي يبدأ من الركن السفلي الأيمن إلى القمة فيُضمَّن ولن يكون موجودًا عند تحديد المسار، كما يُستخدَم التابع <code>closePath</code> لغلق المسار صراحةً من خلال إضافة جزء سطر حقيقي إلى بداية المسار، وسيُرسم هذا الجزء عند تحديد المسار.
</p>

<h2>
	المنحنيات
</h2>

<p>
	قد يحتوي المسار على خطوط منحنية، وتكون هذه الخطوط أعقد في رسمها من الخطوط المستقيمة، حيث يرسم التابع <code>quadraticCurveTo</code> منحني إلى نقطة ما، كما يُعطى التابع نقطة تحكم ونقطة وجهة لتحديد انحناء الخط، ويمكن تخيل نقطة التحكم على أنها تسحب الخط لتعطيه انحناءه؛ أما الخط نفسه فلن يمر عليها وإنما سيكون اتجاهه عند نقطتي البدء والنهاية، بحيث إذا رُسم خط مستقيم في ذلك الاتجاه فسيشير إلى نقطة التحكم، ويوضِّح المثال التالي ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_23" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">90</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// control=(60,10) goal=(90,90)</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">quadraticCurveTo</span><span class="pun">(</span><span class="lit">60</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">90</span><span class="pun">,</span><span class="pln"> </span><span class="lit">90</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="lit">60</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">closePath</span><span class="pun">();</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">stroke</span><span class="pun">();</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	سنرسم المنحنى التربيعي من اليسار إلى اليمين، وتكون نقطة التحكم هي (60,10)، ثم نرسم خطين يمران بنقطة التحكم تلك ويعودان إلى بداية الخط. سيكون الشكل الناتج أشبه بشعار أفلام ستار تريك Star Trek، كما تستطيع رؤية تأثير نقطة التحكم، بحيث تبدأ الخطوط تاركة الأركان السفلى في اتجاه نقطة التحكم ثم تنحني مرةً أخرى إلى هدفها.
</p>

<p>
	يرسم التابع <code>bezierCurveTo</code> انحناءً قريبًا من ذلك، لكن يكون له نقطتي تحكم أي واحدة عند كل نهاية خط بدلًا من نقطة تحكم واحدة، ويوضِّح المثال التالي سلوك هذا المنحنى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_25" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">moveTo</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">90</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// control1=(10,10) control2=(90,10) goal=(50,90)</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">bezierCurveTo</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">90</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">90</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="lit">90</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">closePath</span><span class="pun">();</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">stroke</span><span class="pun">();</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تحدد نقطتا التحكم الاتجاه عند كلا النهايتين للمنحنى، وكلما ابتعدنا عن النقطة الموافقة لهما زاد انتفاخ المنحنى في ذلك الاتجاه، كما ستكون تلك المنحنيات أصعب من حيث العمل عليها، فليس من السهل معرفة كيفية إيجاد نقاط التحكم التي توفر الشكل الذي تبحث عنه، حيث نستطيع حسابها أحيانًا، لكن سيكون علينا إيجاد قيمة مناسبة من خلال التجربة والخطأ أحيانًا أخرى.
</p>

<p>
	يُستخدَم التابع <code>arc</code> على أساس طريقة لرسم خط ينحني على حواف دائرة، كما يأخذ زوجًا من الإحداثيات من أجل مركز القوس، ونصف قطر، ثم زاوية بداية وزاوية نهاية.
</p>

<p>
	نستطيع من خلال هذين المعاملَين الأخيرين رسم جزء من الدائرة فقط دون رسمها كلها، كما تقاس الزوايا بالراديان radian وليس بالدرجات، ويعني هذا أن الدائرة الكاملة لها زاوية مقدارها <code>2π</code> أو <code>2‎ * Math.PI</code>، وهي تساوي 6.28 تقريبًا، كما تبدأ الزاوية العد عند النقطة التي على يمين مركز الدائرة وتدور باتجاه عقارب الساعة من هناك، وهنا تستطيع استخدام 0 للبداية ونهاية تكون أكبر من 2π -لتكن 7 مثلًا- من أجل رسم الدائرة كلها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_27" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
  </span><span class="com">// center=(50,50) radius=40 angle=0 to 7</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">arc</span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// center=(150,50) radius=40 angle=0 to ½π</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">arc</span><span class="pun">(</span><span class="lit">150</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">stroke</span><span class="pun">();</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	رسم المخطط الدائري
</h2>

<p>
	لنقل أننا نريد رسم مخطط دائري لنتائج استبيان رضا العملاء عن شركة ما ولتكن EconomiCorp مثلًا، بحيث تحتوي رابطة <code>results</code> على مصفوفة من الكائنات التي تمثل نتائج الاستبيان.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_29" style="">
<span class="kwd">const</span><span class="pln"> results </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Satisfied"</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1043</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> </span><span class="str">"lightblue"</span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Neutral"</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">:</span><span class="pln"> </span><span class="lit">563</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> </span><span class="str">"lightgreen"</span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Unsatisfied"</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">:</span><span class="pln"> </span><span class="lit">510</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> </span><span class="str">"pink"</span><span class="pun">},</span><span class="pln">
  </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"No comment"</span><span class="pun">,</span><span class="pln"> count</span><span class="pun">:</span><span class="pln"> </span><span class="lit">175</span><span class="pun">,</span><span class="pln"> color</span><span class="pun">:</span><span class="pln"> </span><span class="str">"silver"</span><span class="pun">}</span><span class="pln">
</span><span class="pun">];</span></pre>

<p>
	سنرسم عددًا من الشرائح الدائرية يتكون كل منها من قوس وزوج من الخطوط ينتهيان إلى مركز ذلك القوس، كما نستطيع حساب الزاوية التي يأخذها كل قوس من خلال قسمة الدائرة الكلية 2π على العدد الكلي للاستجابات، ومن ثم ضرب ذلك العدد -زاوية الاستجابة- في عدد الأشخاص الذين اختاروا عنصرًا ما.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_31" style="">
<span class="pun">&lt;</span><span class="pln">canvas width</span><span class="pun">=</span><span class="str">"200"</span><span class="pln"> height</span><span class="pun">=</span><span class="str">"200"</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  let total </span><span class="pun">=</span><span class="pln"> results
    </span><span class="pun">.</span><span class="pln">reduce</span><span class="pun">((</span><span class="pln">sum</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">count</span><span class="pun">})</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> sum </span><span class="pun">+</span><span class="pln"> count</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// ابدأ من القمة</span><span class="pln">
  let currentAngle </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">0.5</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let result of results</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let sliceAngle </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">result</span><span class="pun">.</span><span class="pln">count </span><span class="pun">/</span><span class="pln"> total</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI</span><span class="pun">;</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
    </span><span class="com">// center=100,100, radius=100</span><span class="pln">
    </span><span class="com">// من الزاوية الحالية، باتجاه عقارب الساعة بحذاء زاوية الشريحة</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">arc</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln">
           currentAngle</span><span class="pun">,</span><span class="pln"> currentAngle </span><span class="pun">+</span><span class="pln"> sliceAngle</span><span class="pun">);</span><span class="pln">
    currentAngle </span><span class="pun">+=</span><span class="pln"> sliceAngle</span><span class="pun">;</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">);</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">color</span><span class="pun">;</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">fill</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	لا يخبرنا المخطط ماذا تعني تلك الشرائح، لذا سنحتاج إلى طريقة نرسم بها نصوصًا على اللوحة.
</p>

<h2>
	النصوص
</h2>

<p>
	يوفر سياق رسم اللوحة ثنائي الأبعاد التابعَين <code>fillText</code> و<code>strokeText</code>، حيث يُستخدَم الأخير في تحديد الأحرف، لكن الذي نحتاج إليه هو <code>fillText</code> عادةً، إذ سيملأ حد النص المعطى بتنسيق <code>fillStyle</code> الحالي.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_33" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">font </span><span class="pun">=</span><span class="pln"> </span><span class="str">"28px Georgia"</span><span class="pun">;</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> </span><span class="str">"fuchsia"</span><span class="pun">;</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">fillText</span><span class="pun">(</span><span class="str">"I can draw text, too!"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تستطيع تحديد حجم النص وتنسيقه ونوع خطه أيضًا باستخدام الخاصية <code>font</code>، ولا يعطينا هذا المثال إلا حجم الخط واسم عائلته، كما من الممكن إضافة ميل الخط <code>italic</code> أو سماكته <code>bold</code> إلى بداية السلسلة النصية لاختيار تنسيق ما، في حين يوفر آخر وسيطين لكل من <code>fillText</code> و<code>strokeText</code> الموضع الذي سيُرسم فيه الخط، كما يشيران افتراضيًا إلى موضع بداية قاعدة النص الأبجدية التي تكوِّن السطر الذي تقف الحروف عليه، لكن لا تحسب الأجزاء المتدلية من الأحرف مثل حرف j أو p، ونستطيع تغيير الموضع الأفقي ذاك بضبط الخاصية <code>textAlign</code> لتكون <code>"end"</code> أو <code>"center"</code>، وتغيير الموضع الرأسي كذلك من خلال ضبط <code>textBaseline</code> لتكون <code>"top"</code> أو <code>"middle"</code> أو <code>"bottom"</code>.
</p>

<h2>
	الصور
</h2>

<p>
	يُفرَّق عادةً في رسوميات الحواسيب بين الرسوميات المتجهية vector graphics والرسوميات النقطية bitmap graphics، فالأولى هي التي شرحناها في بداية هذا المقال والتي تصف الصورة وصفًا منطقيًا لشكلها؛ أما الرسوميات النقطية فلا تصف الأشكال الحقيقية، بل تعمل مع بيانات البكسلات الخاصة بالصورةk والتي هي مربعات من النقاط الملونة على الشاشة.
</p>

<p>
	يسمح لنا التابع <code>drawImage</code> برسم بيانات البكسلات على اللوحة، ويمكن استخراج تلك البيانات من عنصر <code>&lt;img&gt;</code> أو من لوحة أخرى، كما ينشئ المثال التالي عنصر <code>&lt;img&gt;</code> منفصل ويحمِّل ملف الصورة إليه، لكنه لا يستطيع البدء بالرسم مباشرةً من تلك الصورة بما أنّ المتصفح قد لا يكون حمَّلها بعد، ولحل هذا فإننا نسجل معالج الحدث <code>"load"</code> لتنفيذ الرسم بعد تحميل الصورة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_35" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  let img </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">);</span><span class="pln">
  img</span><span class="pun">.</span><span class="pln">src </span><span class="pun">=</span><span class="pln"> </span><span class="str">"img/hat.png"</span><span class="pun">;</span><span class="pln">
  img</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"load"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10</span><span class="pun">;</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">200</span><span class="pun">;</span><span class="pln"> x </span><span class="pun">+=</span><span class="pln"> </span><span class="lit">30</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      cx</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">img</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> </span><span class="lit">10</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	سيرسم التابع <code>drawImage</code> الصورة بحجمها الحقيقي افتراضيًا، لكن يمكن إعطاؤه وسيطَين إضافيين لتحديد عرض وطول مختلفَين، فإذا أعطي <code>drawImage</code> تسعة وسائط، فيمكن استخدامه لرسم جزء من الصورة، ويوضِّح الوسيط الثاني حتى الخامس -أي x وy والعرض والطول- المستطيل الذي في الصورة المصدرية والتي يجب نسخها؛ أما الوسيط السادس حتى التاسع فتعطينا المستطيل على اللوحة الذي سيُنسخ، كما يمكن استخدام هذا لتحزيم عدة شرائح أو عناصر من صورة (تسمى sprites أي عفاريت) في ملف صورة واحد، ثم رسم الجزء الذي نحتاج إليه فقط، فلدينا مثلًا هذه الصورة التي تحتوي على شخصية لعبة في عدة وضعيات:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81265" href="https://academy.hsoub.com/uploads/monthly_2021_10/player_big.png.656b1fa95e24cdf0d075d92ca962ef60.png" rel=""><img alt="player_big.png" class="ipsImage ipsImage_thumbnailed" data-fileid="81265" data-unique="hc6phczqc" src="https://academy.hsoub.com/uploads/monthly_2021_10/player_big.png.656b1fa95e24cdf0d075d92ca962ef60.png"></a>
</p>

<p>
	إذا غيرنا الوضع الذي نرسمه فسنستطيع عرض تحريك يبدو مثل شخصية تمشي، في حين نستخدِم التابع <code>clearRect</code> لتحريك صورة على اللوحة، وهو يمثِّل <code>fillRect</code>، لكنه يجعل المستطيل شفافًا بدلًا من تلوينه حاذفًا البكسلات المرسومة سابقًا، ونحن نعرف أنّ كل عفريت وكل صورة فرعية يكون عرضها 24 بكسل وارتفاعها 30 بكسل، وعلى ذلك تحمِّل الشيفرة التالية الصورة ثم تضبط فترةً زمنيةً متكررةً لرسم الإطار التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_37" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  let img </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">);</span><span class="pln">
  img</span><span class="pun">.</span><span class="pln">src </span><span class="pun">=</span><span class="pln"> </span><span class="str">"img/player.png"</span><span class="pun">;</span><span class="pln">
  let spriteW </span><span class="pun">=</span><span class="pln"> </span><span class="lit">24</span><span class="pun">,</span><span class="pln"> spriteH </span><span class="pun">=</span><span class="pln"> </span><span class="lit">30</span><span class="pun">;</span><span class="pln">
  img</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"load"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let cycle </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    setInterval</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      cx</span><span class="pun">.</span><span class="pln">clearRect</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> spriteW</span><span class="pun">,</span><span class="pln"> spriteH</span><span class="pun">);</span><span class="pln">
      cx</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">img</span><span class="pun">,</span><span class="pln">
                   </span><span class="com">// المستطيل المصدر</span><span class="pln">
                   cycle </span><span class="pun">*</span><span class="pln"> spriteW</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> spriteW</span><span class="pun">,</span><span class="pln"> spriteH</span><span class="pun">,</span><span class="pln">
                   </span><span class="com">// مستطيل الوجهة</span><span class="pln">
                   </span><span class="lit">0</span><span class="pun">,</span><span class="pln">               </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> spriteW</span><span class="pun">,</span><span class="pln"> spriteH</span><span class="pun">);</span><span class="pln">
      cycle </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">cycle </span><span class="pun">+</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="lit">8</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">},</span><span class="pln"> </span><span class="lit">120</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تتعقب رابطة <code>cycle</code> موضعنا في هذا التحريك وتتزايد مرةً لكل إطار، ثم تقفز عائدةً إلى المجال 0 إلى 7 باستخدام عامِل الباقي، بعدها تُستخدَم هذه الرابطة بعد ذلك لحساب الإحداثي x الذي يحتوي عليه العفريت الذي في الوضع الحالي في الصورة.
</p>

<h2>
	التحول
</h2>

<p>
	ماذا لو أردنا جعل الشخصية تمشي إلى اليسار بدلًا من اليمين؟ لا شك أننا نستطيع رسم مجموعة أخرى من عناصر العفاريت، لكننا نستطيع توجيه اللوحة أيضًا لترسم الصورة بعكس الطريقة التي ترسمها بها، كما سيزيد استدعاء التابع <code>scale</code> حجم أيّ شيء يُرسم بعده، وهو يأخذ معاملَين أحدهما لضبط المقياس الأفقي والآخر للعمودي.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_39" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">scale</span><span class="pun">(</span><span class="lit">3</span><span class="pun">,</span><span class="pln"> </span><span class="pun">.</span><span class="lit">5</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">arc</span><span class="pun">(</span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">,</span><span class="pln"> </span><span class="lit">40</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">7</span><span class="pun">);</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">lineWidth </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">stroke</span><span class="pun">();</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	سيتسبب تغيير حجم الصورة في تمديد كل شيء فيها أو ضغطه بما فيها عرض الخط، وإذا غيّرنا المقياس ليكون بقيمة سالبة، فستنقلب الصورة معكوسة، حيث يحدث الانعكاس حول النقطة (0,0) التي تعني أننا سنقلب أيضًا اتجاه نظام الإحداثيات، فحين نطبِّق مقياسًا أفقيًا مقداره ‎<code>-1</code>‎، فسيكون الشكل المرسوم عند الموضع 100 على إحداثي x في الموضع الذي كان ‎‎<code>‎‎-100</code>‎ من قبل، لذا لا نستطيع إضافة ‎<code>‎‎cx.scale(-1, 1)‎‎</code>‎ من أجل عكس الصورة وحسب قبل استدعاء <code>drawImage</code>، لأنه سيجعل الصورة تتحرك خارج اللوحة بحيث تكون غير مرئية، ونعدّل الإحداثيات المعطاة إلى <code>drawImage</code> من أجل ضبط هذا برسم الصورة في الموضع ‎<code>-50</code>‎ على الإحداثي x بدلًا من 0.
</p>

<p>
	هناك حل آخر لا يحتاج إلى الشيفرة التي تنفذ الرسم كي يدرك تغير المقياس، وهو تعديل المحور الذي يحدث تغيير الحجم حوله، كما يمكن استخدام عدة توابع أخرى غير <code>scale</code> للتأثير في نظام إحداثيات اللوحة، حيث تستطيع تدوير الأشكال المرسومة تاليًا باستخدام التابع <code>rotate</code> ونقلها باستخدام <code>translate</code>، لكن المثير في الأمر والمحير أيضًا هو أنّ تلك التحويلات تُكدَّس، بمعنى أنّ كل واحد يُحدِث نسبةً إلى ما قبله من تحولات، وبناءً عليه فإذا استخدمنا <code>translate</code> لتحريك 10 بكسلات مرتين أفقيًأ، فسيُرسم كل شيء مزاحًا إلى اليمين بمقدار 20 بكسل؛ أما إذا أزحنا مركز نظام الإحداثيات أولًا إلى (50,50) ثم دوّرنا بزاوية 20 درجة -أي 0.1π راديان-، فسيَحدث التدوير حول النقطة (50,50).
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81267" href="https://academy.hsoub.com/uploads/monthly_2021_10/transform.png.ff59cdb27a5d6daca0e89cfccbe0cf57.png" rel=""><img alt="transform.png" class="ipsImage ipsImage_thumbnailed" data-fileid="81267" data-unique="x1ugqsaos" src="https://academy.hsoub.com/uploads/monthly_2021_10/transform.png.ff59cdb27a5d6daca0e89cfccbe0cf57.png"></a>
</p>

<p>
	لكن إذا نفذّنا التدوير بمقدار عشرين درجة أولًا ثم أزحنا بمقدار (50,50)، فسيحدث الإزاحة عند نظام الإحداثيات المدوَّر، وعليه سيعطينا اتجاهًا مختلفًا، ونستنتج من هذا أنّ ترتيب تطبيق التحويلات مهم. وتعكس الشيفرة التالية الصورة حول الخط العمودي عند الموضع x:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_41" style="">
<span class="kwd">function</span><span class="pln"> flipHorizontally</span><span class="pun">(</span><span class="pln">context</span><span class="pun">,</span><span class="pln"> around</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  context</span><span class="pun">.</span><span class="pln">translate</span><span class="pun">(</span><span class="pln">around</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  context</span><span class="pun">.</span><span class="pln">scale</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">
  context</span><span class="pun">.</span><span class="pln">translate</span><span class="pun">(-</span><span class="pln">around</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ننقل المحور y إلى حيث نريد لمرآتنا أن تكون ونطبِّق المرآة، ثم نعيد المحور مرةً أخرى إلى موضعه المناسب في العالم المعكوس، وتوضِّح الصورة التالية ذلك:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81264" href="https://academy.hsoub.com/uploads/monthly_2021_10/mirror.png.97c72def251be4ff6b9c57d174f24903.png" rel=""><img alt="mirror.png" class="ipsImage ipsImage_thumbnailed" data-fileid="81264" data-unique="o4g6fa44u" src="https://academy.hsoub.com/uploads/monthly_2021_10/mirror.png.97c72def251be4ff6b9c57d174f24903.png"></a>
</p>

<p>
	توضِّح الصورة نظام الإحداثيات قبل وبعد الانعكاس على طول الخط المركزي وتُرقَّم المثلثات لتوضيح كل خطوة، فإذا رسمنا مثلثًا عند الموضع x الموجب، فسيكون حيث يكون المثلث 1، إذ نستدعي <code>flipHorizontally</code> لينفِّذ الإزاحة إلى اليمين أولًا لنصل إلى المثلث 2، ثم يغيِّر الحجم ويعكس المثلث إلى الموضع 3، غير أنه لا يُفترض أن يكون هناك إذا عُكِس في الخط المعطى، فيأتي استدعاء <code>translate</code> الثاني ليصلح ذلك، بحيث يلغي الإزاحة الأولى ويُظهِر المثلث 4 في الموضع الذي يُفترض أن يكون فيه تمامًا، ونستطيع الآن رسم الشخصية المعكوسة في الموضع (100,0) من خلال عكس العالم حول المركز العمودي للشخصية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_43" style="">
<span class="pun">&lt;</span><span class="pln">canvas</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  let img </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">);</span><span class="pln">
  img</span><span class="pun">.</span><span class="pln">src </span><span class="pun">=</span><span class="pln"> </span><span class="str">"img/player.png"</span><span class="pun">;</span><span class="pln">
  let spriteW </span><span class="pun">=</span><span class="pln"> </span><span class="lit">24</span><span class="pun">,</span><span class="pln"> spriteH </span><span class="pun">=</span><span class="pln"> </span><span class="lit">30</span><span class="pun">;</span><span class="pln">
  img</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"load"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    flipHorizontally</span><span class="pun">(</span><span class="pln">cx</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> spriteW </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">img</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> spriteW</span><span class="pun">,</span><span class="pln"> spriteH</span><span class="pun">,</span><span class="pln">
                 </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> spriteW</span><span class="pun">,</span><span class="pln"> spriteH</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	تخزين التحويلات ومحوها
</h2>

<p>
	تظل التحويلات قائمةً حتى بعد رسمنا لتلك الشخصية المعكوسة، إذ سيكون كل شيء نرسمه بعد ذلك معكوسًا أيضًا وقد لا نريد هذا، حيث من الممكن هنا حفظ التحويل الحالي ثم إجراء بعض الرسم والتحويل، وبعدها استعادة التحويل القديم مرةً أخرى، فغالبًا يُعَدّ ما سبق الإجراء الأفضل لإجراء وظيفة دالة ما تحتاج إلى تحويل نظام الإحداثيات لفترة مؤقتة، حيث سنحفظ أيّ تحويل استخدمته الشيفرة التي استدعت الدالة، ثم تضيف الدالة ما تشاء من التحويلات فوق التحويل الحالي، وبعد ذلك نرجع إلى التحول الذي بدأنا به مرةً أخرى.
</p>

<p>
	يدير عملية التحول تلك التابعَين <code>save</code> و<code>restore</code> على سياق اللوحة ثنائية الأبعاد لأنهما يحتفظان بمكدس من حالات التحول، وحين نستدعي <code>save</code>، فستُدفَع الحالة الراهنة إلى المكدس، ثم تؤخَذ الحالة التي على قمة المكدس مرةً أخرى عند استدعاء <code>restore</code> وتُستخدم على أنها التحول الحالي للسياق، كما نستطيع كذلك استدعاء <code>resetTransform</code> من أجل إعادة ضبط التحول بالكامل.
</p>

<p>
	توضِّح الدالة <code>branch</code> في المثال التالي ما يمكن فعله بدالة تغير التحول، ثم تستدعي دالةً -هي نفسها في هذه الحالة- تتابع الرسم بالتحول المعطى، حيث ترسم تلك الدالة شكلًا يشبه الشجرة من خلال رسم خط ثم نقل مركز نظام الإحداثيات إلى نهاية ذلك الخط، ثم استدعاء نفسها مرتين، مرةً مدارةً إلى اليسار، ثم مرةً أخرى مدارة إلى اليمين، إذ يقلل كل استدعاء من طول الفرع المرسوم ثم يتوقف التعاود حين يقل الطول عن 8.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_45" style="">
<span class="pun">&lt;</span><span class="pln">canvas width</span><span class="pun">=</span><span class="str">"600"</span><span class="pln"> height</span><span class="pun">=</span><span class="str">"300"</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> branch</span><span class="pun">(</span><span class="pln">length</span><span class="pun">,</span><span class="pln"> angle</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">fillRect</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> length</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">length </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">8</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">translate</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> length</span><span class="pun">);</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(-</span><span class="pln">angle</span><span class="pun">);</span><span class="pln">
    branch</span><span class="pun">(</span><span class="pln">length </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln"> angle</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">rotate</span><span class="pun">(</span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> angle</span><span class="pun">);</span><span class="pln">
    branch</span><span class="pun">(</span><span class="pln">length </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln"> angle</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">restore</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  cx</span><span class="pun">.</span><span class="pln">translate</span><span class="pun">(</span><span class="lit">300</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  branch</span><span class="pun">(</span><span class="lit">60</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.8</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	إذا لم توجد استدعاءات إلى <code>save</code> و<code>restore</code> فسيكون موضع ودران الاستدعاء الذاتي للمرة الثانية للدالة <code>branch</code> هما اللذان أُنشئا بالاستدعاء الأول، إذ لن تتصل بالفرع الحالي وإنما بالفرع الداخلي الأقصى إلى اليمين والمرسوم بالاستدعاء الأول، وعلى ذلك يكون الشكل الناتج ليس شجرةً أبدًا.
</p>

<h2>
	عودة إلى اللعبة
</h2>

<p>
	عرفنا الآن ما يكفي عن الرسم على اللوحة وسنعمل الآن على نظام عرض مبني على لوحة من أجل اللعبة التي من المقال السابق، إذ لم تعُد الشاشة الجديدة تعرض صناديق ملونة فحسب، وإنما سنستخدِم <code>drawImage</code> من أجل رسم صور تمثِّل عناصر اللعبة، كما سنعرِّف كائن عرض جديد يدعى <code>CanvasDisplay</code> ويدعم الواجهة نفسها مثل <code>DOMDisplay</code> من المقال السابق خاصةً التابعَين <code>syncState</code> و<code>clear</code>، حيث سيحتفظ هذا الكائن بمعلومات أكثر قليلًا من <code>DOMDisplay</code>، فبدلًا من استخدام موضع التمرير لعنصر DOM الخاص به، فهو يتتبع نافذة رؤيته التي تخبرنا بالجزء الذي ننظر إليه في المستوى، ثم يحتفظ بالخاصية <code>flipPlayer</code> التي تمكّن اللاعب من مواجهة الاتجاه الذي كان يتحرك فيه حتى لو كان واقفًا لا يتحرك.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_47" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">CanvasDisplay</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">parent</span><span class="pun">,</span><span class="pln"> level</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="lit">600</span><span class="pun">,</span><span class="pln"> level</span><span class="pun">.</span><span class="pln">width </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">height </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="lit">450</span><span class="pun">,</span><span class="pln"> level</span><span class="pun">.</span><span class="pln">height </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
    parent</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">flipPlayer </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">

    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">viewport </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
      top</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
      width</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">width </span><span class="pun">/</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln">
      height</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">height </span><span class="pun">/</span><span class="pln"> scale
    </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  clear</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يحسب التابع <code>syncState</code> أولًا نافذة رؤية جديدة ثم يرسم مشهد اللعبة عند الموضع المناسب.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_49" style="">
<span class="typ">CanvasDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">syncState </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">updateViewport</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">clearDisplay</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">drawBackground</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">drawActors</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">actors</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	سيكون على هذا التنسيق من العرض إعادة رسم الخلفية عند كل تحديث على عكس <code>DOMDisplay</code>، ولأن الأشكال التي على اللوحة ما هي إلا بكسلات، فليس لدينا طريقةً لتحريكها أو حتى حذفها بعد رسمها، والطريقة الوحيدة لدينا لتحديث شاشة اللوحة هي مسحها ثم إعادة رسم المشهد.، وقد يحدث أن نكون قد مرّرنا نافذة الرؤية من الشاشة، وهذا يتطلب أن تكون الخلفية في موضع مختلف.
</p>

<p>
	يتحقق التابع <code>updateViewport</code> إذا كان اللاعب قريبًا للغاية من حافة الشاشة أم لا، وإذا كان كذلك فسينقل نافذة الرؤية، وهو في هذا يشبه التابع <code>scrollPlayerIntoView</code> الخاص بـ <code>DOMDisplay</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_51" style="">
<span class="typ">CanvasDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">updateViewport </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let view </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">viewport</span><span class="pun">,</span><span class="pln"> margin </span><span class="pun">=</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">width </span><span class="pun">/</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln">
  let player </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">player</span><span class="pun">;</span><span class="pln">
  let center </span><span class="pun">=</span><span class="pln"> player</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="pln">player</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">times</span><span class="pun">(</span><span class="lit">0.5</span><span class="pun">));</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&lt;</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">left </span><span class="pun">+</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    view</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&gt;</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">left </span><span class="pun">+</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">width </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    view</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> margin </span><span class="pun">-</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">width</span><span class="pun">,</span><span class="pln">
                         state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">width </span><span class="pun">-</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">width</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&lt;</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">top </span><span class="pun">+</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    view</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&gt;</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">top </span><span class="pun">+</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">height </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    view</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> margin </span><span class="pun">-</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">height</span><span class="pun">,</span><span class="pln">
                        state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">height </span><span class="pun">-</span><span class="pln"> view</span><span class="pun">.</span><span class="pln">height</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تتأكد الاستدعاءات إلى <code>Math.max</code> و<code>Math.min</code> من أنّ نافذة الرؤية لا تعرض مساحةً خارج المستوى، حيث تضمن ‎<code>Math.max(x, 0)</code>‎ أنّ العدد الناتج ليس أقل من صفر، كما تضمن <code>Math.min</code> بقاء القيمة تحت الحد المعطى، وسنستخدم عند مسح الشاشة لونًا مختلفًا وفقًا لحالة اللعب إذا فازت أو خسرت، بحيث يكون لونًا فاتحًا في حالة الفوز، وداكنًا في الخسارة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_53" style="">
<span class="typ">CanvasDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">clearDisplay </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">status</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="str">"won"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> </span><span class="str">"rgb(68, 191, 255)"</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="str">"lost"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> </span><span class="str">"rgb(44, 136, 214)"</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> </span><span class="str">"rgb(52, 166, 251)"</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">fillRect</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln">
                   </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">width</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">canvas</span><span class="pun">.</span><span class="pln">height</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	نمر على المربعات المرئية في نافذة الرؤية الحالية من أجل رسم الخلفية باستخدام الطريقة نفسها التي اتبعناها في التابع <code>touches</code> في المقال السابق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_55" style="">
<span class="pln">let otherSprites </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">);</span><span class="pln">
otherSprites</span><span class="pun">.</span><span class="pln">src </span><span class="pun">=</span><span class="pln"> </span><span class="str">"img/sprites.png"</span><span class="pun">;</span><span class="pln">

</span><span class="typ">CanvasDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">drawBackground </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">level</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let </span><span class="pun">{</span><span class="pln">left</span><span class="pun">,</span><span class="pln"> top</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">viewport</span><span class="pun">;</span><span class="pln">
  let xStart </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">(</span><span class="pln">left</span><span class="pun">);</span><span class="pln">
  let xEnd </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">ceil</span><span class="pun">(</span><span class="pln">left </span><span class="pun">+</span><span class="pln"> width</span><span class="pun">);</span><span class="pln">
  let yStart </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">(</span><span class="pln">top</span><span class="pun">);</span><span class="pln">
  let yEnd </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">ceil</span><span class="pun">(</span><span class="pln">top </span><span class="pun">+</span><span class="pln"> height</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let y </span><span class="pun">=</span><span class="pln"> yStart</span><span class="pun">;</span><span class="pln"> y </span><span class="pun">&lt;</span><span class="pln"> yEnd</span><span class="pun">;</span><span class="pln"> y</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let x </span><span class="pun">=</span><span class="pln"> xStart</span><span class="pun">;</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> xEnd</span><span class="pun">;</span><span class="pln"> x</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let tile </span><span class="pun">=</span><span class="pln"> level</span><span class="pun">.</span><span class="pln">rows</span><span class="pun">[</span><span class="pln">y</span><span class="pun">][</span><span class="pln">x</span><span class="pun">];</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">tile </span><span class="pun">==</span><span class="pln"> </span><span class="str">"empty"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln">
      let screenX </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> left</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
      let screenY </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> top</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
      let tileX </span><span class="pun">=</span><span class="pln"> tile </span><span class="pun">==</span><span class="pln"> </span><span class="str">"lava"</span><span class="pln"> </span><span class="pun">?</span><span class="pln"> scale </span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">otherSprites</span><span class="pun">,</span><span class="pln">
                        tileX</span><span class="pun">,</span><span class="pln">         </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln">
                        screenX</span><span class="pun">,</span><span class="pln"> screenY</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">,</span><span class="pln"> scale</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	ستُرسم المربعات غير الفارغة باستخدام <code>drawImage</code>، وتحتوي الصورة <code>otherSprites</code> على الصور المستخدَمة للعناصر سوى اللاعب، فهي تحتوي من اليسار إلى اليمين على مربع الحائط ومربع الحمم البركانية وعفريت للعملة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81266" href="https://academy.hsoub.com/uploads/monthly_2021_10/sprites_big.png.0ecb40d16890d89b9596e04f2610112d.png" rel=""><img alt="sprites_big.png" class="ipsImage ipsImage_thumbnailed" data-fileid="81266" data-unique="4djvnskl5" src="https://academy.hsoub.com/uploads/monthly_2021_10/sprites_big.png.0ecb40d16890d89b9596e04f2610112d.png"></a>
</p>

<p>
	تكون مربعات الخلفية بقياس 20*20 بكسل بما أننا سنستخدم نفس المقياس الذي استخدمناه في <code>DOMDisplay</code>، وعلى ذلك ستكون إزاحة مربعات الحمم البركانية هي 20 -وهي قيمة رابطة <code>scale</code>-؛ أما إزاحة الجدران فستكون صفرًا، كما لن ننتظر تحميل عفريت الصورة لأنّ استدعاء <code>drawImage</code> بصورة لم تحمَّل بعد فلن يُحدث شيئًا، وبالتالي فقد نفشل في رسم اللعبة في أول بضعة إطارات أثناء تحميل تلك الصورة لكنها ليست تلك بالمشكلة الكبيرة، وسيظهر المشهد الصحيح بمجرد انتهاء التحميل بما أننا نظل نحدث الشاشة.
</p>

<p>
	ستُستخدم الشخصية الماشية التي رأيناها سابقًا لتمثل اللاعب، ويجب على الشيفرة التي ترسمها اختيار العفريت الصحيح والاتجاه الصحيح كذلك لحركة اللاعب الآنية، إذ تحتوي أول ثمانية عفاريت على تأثير المشي، كما نكرر تلك العناصر عند مشي اللاعب على الأرضية وفقًا للزمن الحالي، وبما أننا نبدِّل الإطارات كل 60 ميلي ثانية، فسنقسم الوقت على 60 أولًا؛ أما حين يقف اللاعب ساكنًا فسنرسم العفريت التاسع، ونستخدِم العنصر العاشر من أقصى اليمين من أجل رسم تأثير القفز الذي نعرفه من خلال كون السرعة العمودية لا تساوي صفرًا.
</p>

<p>
	يجب على التابع تعديل إحداثيات x والعرض بمقدار معطى (<code>playerXOverlap</code>) لمعادلة عرض العفاريت بما أنها أعرض من كائن اللاعب -24 بكسل بدلًا من 16- لتسمح ببعض المساحة للأذرع والأقدام.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_57" style="">
<span class="pln">let playerSprites </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">);</span><span class="pln">
playerSprites</span><span class="pun">.</span><span class="pln">src </span><span class="pun">=</span><span class="pln"> </span><span class="str">"img/player.png"</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> playerXOverlap </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pun">;</span><span class="pln">

</span><span class="typ">CanvasDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">drawPlayer </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">player</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln">
                                              width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">){</span><span class="pln">
  width </span><span class="pun">+=</span><span class="pln"> playerXOverlap </span><span class="pun">*</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
  x </span><span class="pun">-=</span><span class="pln"> playerXOverlap</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">player</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">.</span><span class="pln">x </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">flipPlayer </span><span class="pun">=</span><span class="pln"> player</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  let tile </span><span class="pun">=</span><span class="pln"> </span><span class="lit">8</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">player</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">.</span><span class="pln">y </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    tile </span><span class="pun">=</span><span class="pln"> </span><span class="lit">9</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">player</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">.</span><span class="pln">x </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    tile </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">(</span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">60</span><span class="pun">)</span><span class="pln"> </span><span class="pun">%</span><span class="pln"> </span><span class="lit">8</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">save</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">flipPlayer</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    flipHorizontally</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">,</span><span class="pln"> x </span><span class="pun">+</span><span class="pln"> width </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  let tileX </span><span class="pun">=</span><span class="pln"> tile </span><span class="pun">*</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">playerSprites</span><span class="pun">,</span><span class="pln"> tileX</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln">
                                   x</span><span class="pun">,</span><span class="pln">     y</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">restore</span><span class="pun">();</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يُستدعى التابع <code>drawPlayer</code> بواسطة <code>drawActors</code> التي تكون مسؤولةً عن رسم جميع الكائنات الفاعلة في اللعبة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_59" style="">
<span class="typ">CanvasDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">drawActors </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">actors</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let actor of actors</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let width </span><span class="pun">=</span><span class="pln"> actor</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
    let height </span><span class="pun">=</span><span class="pln"> actor</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">y </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
    let x </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">actor</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">viewport</span><span class="pun">.</span><span class="pln">left</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
    let y </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">actor</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">viewport</span><span class="pun">.</span><span class="pln">top</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">actor</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"player"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">drawPlayer</span><span class="pun">(</span><span class="pln">actor</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let tileX </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">actor</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"coin"</span><span class="pln"> </span><span class="pun">?</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">cx</span><span class="pun">.</span><span class="pln">drawImage</span><span class="pun">(</span><span class="pln">otherSprites</span><span class="pun">,</span><span class="pln">
                        tileX</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">,</span><span class="pln">
                        x</span><span class="pun">,</span><span class="pln">     y</span><span class="pun">,</span><span class="pln"> width</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	عند رسم أيّ شيء غير اللاعب فإننا ننظر أولًا في نوعه لنعرف إزاحة العفريت الصحيح، فمربع الحمم إزاحته 20، اما عفريت العملة فإزاحته 40، أي أنه ضعف مقدار <code>scale</code>، كما يجب طرح موضع نافذة الرؤية على لوحتنا لتتوافق مع أعلى يسار نافذة الرؤية وليس أعلى يسار المستوى، كما يمكن استخدام <code>translate</code> كذلك، فكلاهما يصلح.
</p>

<p>
	يركِّب المستند التالي الشاشة الجديدة بـ <code>runGame</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_61" style="">
<span class="pun">&lt;</span><span class="pln">body</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
    runGame</span><span class="pun">(</span><span class="pln">GAME_LEVELS</span><span class="pun">,</span><span class="pln"> </span><span class="typ">CanvasDisplay</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">body</span><span class="pun">&gt;</span></pre>

<h2>
	اختيار واجهة الرسوميات
</h2>

<p>
	لدينا عدة خيارات يمكن استخدامها لتوليد الرسوميات في المتصفح من HTML إلى SVG إلى اللوحة، وليس هناك واحد يفضُلها جميعًا في كل حالة، فكل واحد له نقاط قوته وضعفه، فلغة HTML مثلًا تمتاز بالبساطة، كما تتكامل جيدًا مع النصوص، بينما تسمح لك كل من SVG واللوحة برسم النصوص، لكنها لن تمكّنك من موضعة تلك النصوص أو تغليفها إذا أخذت أكثر من سطر واحد؛ أما في الصور المبنية على HTML فسيكون من السهل إدراج كتل نصية فيها.
</p>

<p>
	يمكن استخدام SVG من ناحية أخرى لإنتاج رسوميات واضحة مهما كان مستوى التكبير، فهي مصممة للرسم على عكس HTML، وعليه فهي ملائمة أكثر لهذا الغرض، كذلك تستطيع كل من SVG وHTML بناء هيكل بيانات مثل شجرة DOM تمثل صورتنا، وهذا يمكِّننا من تعديل العناصر بعد رسمها، لذا سيكون من الصعب استخدام اللوحة من أجل تغيير جزء صغير في صورة كبيرة كل حين للاستجابة لأفعال المستخدِم أو بسبب تحريك ما، كما يسمح DOM لنا بتسجيل معالجات أحداث الفأرة على كل عنصر في الصورة حتى الأشكال المرسومة باستخدام SVG؛ أما اللوحة فلا يمكن فعل ذلك فيها.
</p>

<p>
	غير أنه يمكن استخدام نهج المنظور البكسلي pixel-oriented للوحة عند رسم أعداد كبيرة جدًا من عناصر صغيرة، إذ تكون تكلفة الشكل الواحد فيها تافهةً بما أنها لا تبني هياكل بيانات وإنما تكرِّر الرسم على مساحة البكسل نفسها، كما يمكن تنفيذ تأثيرات مثل إخراج مشهد بسرعة بكسل واحد في كل مرة -باستخدام متعقب أشعة ray tracer مثلًا-، أو معالجة لاحقة لصورة باستخدام جافاسكربت مثل تأثير الضباب أو التشويش، وذلك لا يمكن معالجته بواقعية إلا من المنظور البكسلي.
</p>

<p>
	قد نرغب أحيانًا في جمع بعض تلك التقنيات معًا، فقد نرسم مخططًا باستخدام SVG أو اللوحة، لكن نُظهر المعلومات النصية عن طريق وضع عنصر HTML فوق الصورة؛ أما بالنسبة للتطبيقات التي لا تتطلب موارد كثيرة، فليس من المهم أيّ واجهة نختارها، إذ يمكن استخدام العرض الذي بنيناه في هذا المقال من أجل لعبتنا، كان يمكن تنفيذه باستخدام أي من التقنيات الرسومية الثلاثة بما أنه لا يحتاج إلى رسم نصوص أو معالجة تفاعلات للفأرة أو التعامل مع عدد ضخم من العناصر.
</p>

<h2>
	خاتمة
</h2>

<p>
	لقد ناقشنا في هذا المقال تقنيات رسم التصاميم المرئية والرسوميات في المتصفح مع تناول عنصر <code>&lt;canvas&gt;</code> بالتفصيل، كما عرفنا أنّ عقدة اللوحة تمثِّل مساحةً في مستند قد يرسم برنامجنا عليها، وينفَّذ هذا الرسم من خلال رسم كائن سياقي ينشئه التابع <code>getContext</code>، كما تسمح لنا واجهة الرسم ثنائية الأبعاد بملء وتخطيط أشكال كثيرة، وتحدد خاصية السياق <code>fillStyle</code> كيفية ملء الأشكال، في حين تتحكم الخاصيتان <code>strokeStyle</code> و<code>lineWidth</code> في طريقة رسم الخطوط.
</p>

<p>
	تُرسم المستطيلات وأجزاء النصوص باستدعاء تابع واحد، حيث يرسم التابعان <code>fillRect</code> و<code>strokeRect</code> مستطيلات، بينما يرسم كل من <code>fillText</code> و<code>strokeText</code> نصوصًا؛ أما إذا أردنا إنشاء أشكال فيجب علينا بناء مسار أولًا، كما ينشئ استدعاء <code>beginPath</code> مسارًا جديدًا، كما يمكن إضافة خطوط ومنحنيات إلى المسار الحالي باستخدام عدة توابع أخرى، حيث يضيف التابع <code>lineTo</code> مثلًا خطًا مستقيمًا، وإذا انتهى المسار، فيمكن استخدام التابع <code>fill</code> لملئه أو التابع <code>stroke</code> لتحديده.
</p>

<p>
	تُنقل البكسلات من صورة أو لوحة أخرى إلى لوحتنا باستخدام التابع <code>drawImage</code>، حيث يرسم هذا التابع الصورة المصدرية كلها افتراضيًا، لكن يمكن إعطاؤه معاملات إضافية من أجل نَسخ جزء محدد من الصورة، وقد استخدَمنا ذلك في لعبتنا بنسخ أوضاع شخصية اللاعب المختلفة من صورة تحتوي على عدة أوضاع.
</p>

<p>
	تسمح لنا التحولات برسم شكل في اتجاهات مختلفة، فسياق الرسم ثنائي الأبعاد به تحول راهن يمكن تغييره باستخدام التوابع <code>translate</code> و<code>scale</code> و<code>rotate</code>، إذ ستؤثِّر على جميع عمليات الرسم اللاحقة، كما يمكن حفظ حالة التحول باستخدام التابع <code>save</code> واستعادتها باستخدام التابع <code>restore</code>، وأخيرًا يُستخدَم التابع <code>clearRect</code> عند عرض تحريك على اللوحة من أجل مسح جزء من اللوحة قبل إعادة رسمه.
</p>

<h2>
	تدريبات
</h2>

<h3>
	الأشكال
</h3>

<p>
	اكتب برنامجًا يرسم الأشكال التالية على لوحة:
</p>

<ol>
<li>
		شبه منحرف وهو مستطيل أحد جوانبه المتوازية أطول من الآخر.
	</li>
	<li>
		ماسة حمراء وهي مستطيل مُدار بزاوية 45 درجة مئوية، أو <code>¼π</code> راديان.
	</li>
	<li>
		خط متعرِّج Zigzag.
	</li>
	<li>
		شكل حلزوني من 100 جزء من خطوط مستقيمة.
	</li>
	<li>
		نجمة صفراء.
	</li>
</ol>
<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81263" href="https://academy.hsoub.com/uploads/monthly_2021_10/exercise_shapes.png.e621c7e65de94b502573b681f070ec7d.png" rel=""><img alt="exercise_shapes.png" class="ipsImage ipsImage_thumbnailed" data-fileid="81263" data-unique="n39l3gk8g" src="https://academy.hsoub.com/uploads/monthly_2021_10/exercise_shapes.png.e621c7e65de94b502573b681f070ec7d.png"></a>
</p>

<p>
	ربما تود الرجوع إلى شرح كل من <code>Math.cos</code> و<code>Math.sin</code> في مقال <a href="https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%86%D8%AF-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1322/" rel="">نموذج كائن المستند في جافاسكريبت</a> عند رسم آخر شكلين لتعرف كيف تحصل على إحداثيات على دائرة باستخدام هاتين الدالتين، كما ننصحك بإنشاء دالة لكل شكل وتمرير الموضع إليها و-إذا أردت- مرر إليها الخصائص الأخرى مثل الحجم وعدد النقاط على أساس معاملات، كما يوجد حل بديل بأن تكتب أرقامًا ثابتةً في شيفرتك مما سيجعل الشيفرة أصعب في القراءة والتعديل.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_63" style="">
<span class="pun">&lt;</span><span class="pln">canvas width</span><span class="pun">=</span><span class="str">"600"</span><span class="pln"> height</span><span class="pun">=</span><span class="str">"200"</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// شيفرتك هنا.</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<p>
	يمكن رسم شبه المنحرف ببساطة باستخدام مسار، لذا اختر إحداثيات مركزية مناسبة وأضف كل ركن من الأركان الأربعة حول المركز؛ أما الماسة فيمكن رسمها بطريقة بسيطة باستخدام مسار أو باستخدام تحول <code>rotate</code>، وإذا أردت تجربة التحول فسيكون عليك تطبيق طريقة تشبه ما فعلنا في دالة <code>flipHorizontally</code>، كما يجب أن تنتقل <code>translate</code> إلى مركز المستطيل بدلًا من الإحداثي الصفري بما أنك ستدور حول مركزه، ثم تدور ثم تُعاد إلى مكانها مرةً أخرى، وتأكد من إعادة ضبط التحول بعد رسم أيّ شكل ينشئ تحولًا.
</p>

<p>
	أما بالنسبة للخط المتعرِّج فليس من المنطقي كتابة استدعاء جديد إلى <code>lineTo</code> في كل جزء من أجزاء الخط، وإنما يجب استخدام حلقة تكرارية، بحيث تجعل كل تكرار فيها يرسم جزأين -اليمين ثم اليسار مرةً أخرى- أو جزءًا واحدًا من أجل تحديد اتجاه الذهاب إلى اليمين أم اليسار والذي يكون بالاعتماد على عامل حالة من فهرس الحلقة <code>i</code> (مثل إذا كان <code>i % 2 == 0</code> اذهب لليسار وإلا، لليمين)، كما ستحتاج إلى حلقة تكرارية من أجل الشكل الحلزوني، فإذا رسمت سلسلةً من النقاط تتحرك فيها كل نقطة إلى الخارج على دائرة حول مركز الشكل فستحصل على دائرة؛ أما إذا استخدمت الحلقة التكرارية وغيرت نصف قطر الدائرة التي تضع النقطة الحالية عليها ونفّذت عدة حركات فستحصل على شكل حلزوني.
</p>

<p>
	تُبنى النجمة المصورة من خطوط <code>quadraticCurveTo</code>، لكن يمكن رسمها باستخدام الخطوط المستقيمة أيضًا من خلال تقسيم دائرة إلى ثمانية أجزاء إذا أردت رسم نجمة بثمانية نقاط، أو إلى أي عدد من الأجزاء تريد، ثم ارسم خطوطًا بين تلك النقاط لتجعلها تنحني نحو مركز النجمة؛ أما إذا استخدمت <code>quadraticCurveTo</code>، فستستطيع جعل المركز على أساس نقطة تحكم.
</p>

<h3>
	المخطط الدائري
</h3>

<p>
	رأينا في هذا المقال مثالًا لبرنامج يرسم مخططًا دائريًا، عدِّل هذا البرنامج ليظهر اسم كل تصنيف بجانب الشريحة التي تمثله، وحاول إيجاد طريقة مناسبة مرئيًا لموضعة ذلك النص تلقائيًا بحيث تصلح لأنواع البيانات الأخرى أيضًا، كما تستطيع الافتراض أنّ التصانيف كبيرة بحيث تترك مساحةً كافيةً لعناوينها، وقد تحتاج إلى دالتي <code>Math.sin</code> و<code>Math.cos</code> مرةً أخرى من مقال نموذج كائن المستند في جافاسكريبت.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_65" style="">
<span class="pun">&lt;</span><span class="pln">canvas width</span><span class="pun">=</span><span class="str">"600"</span><span class="pln"> height</span><span class="pun">=</span><span class="str">"300"</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">
  let total </span><span class="pun">=</span><span class="pln"> results
    </span><span class="pun">.</span><span class="pln">reduce</span><span class="pun">((</span><span class="pln">sum</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">count</span><span class="pun">})</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> sum </span><span class="pun">+</span><span class="pln"> count</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">);</span><span class="pln">
  let currentAngle </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">0.5</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI</span><span class="pun">;</span><span class="pln">
  let centerX </span><span class="pun">=</span><span class="pln"> </span><span class="lit">300</span><span class="pun">,</span><span class="pln"> centerY </span><span class="pun">=</span><span class="pln"> </span><span class="lit">150</span><span class="pun">;</span><span class="pln">

  </span><span class="com">// Add code to draw the slice labels in this loop.</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let result of results</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let sliceAngle </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">result</span><span class="pun">.</span><span class="pln">count </span><span class="pun">/</span><span class="pln"> total</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">2</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI</span><span class="pun">;</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">beginPath</span><span class="pun">();</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">arc</span><span class="pun">(</span><span class="pln">centerX</span><span class="pun">,</span><span class="pln"> centerY</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln">
           currentAngle</span><span class="pun">,</span><span class="pln"> currentAngle </span><span class="pun">+</span><span class="pln"> sliceAngle</span><span class="pun">);</span><span class="pln">
    currentAngle </span><span class="pun">+=</span><span class="pln"> sliceAngle</span><span class="pun">;</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">lineTo</span><span class="pun">(</span><span class="pln">centerX</span><span class="pun">,</span><span class="pln"> centerY</span><span class="pun">);</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">fillStyle </span><span class="pun">=</span><span class="pln"> result</span><span class="pun">.</span><span class="pln">color</span><span class="pun">;</span><span class="pln">
    cx</span><span class="pun">.</span><span class="pln">fill</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<p>
	ستحتاج إلى استدعاء <code>fillText</code> وضبط خصائص السياق <code>textAlign</code> و<code>textBaseline</code> بطريقة تجعل النص يكون حيث تريد، وستكون الطريقة المناسبة لموضعة العناوين هي وضع النصوص على الخطوط الذاهبة من مركز المخطط إلى منتصف الشريحة، كما لا تريد وضع النصوص مباشرةً على جانب المخطط على بعد مقدار ما من البكسلات، وتكون زاوية هذا الخط هي <code>currentAngle + 0.5 * sliceAngle</code>، كما تبحث الشيفرة التالية عن موضع عليه بحيث يكون على بعد 120 بكسل من المركز:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_67" style="">
<span class="pln">let middleAngle </span><span class="pun">=</span><span class="pln"> currentAngle </span><span class="pun">+</span><span class="pln"> </span><span class="lit">0.5</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> sliceAngle</span><span class="pun">;</span><span class="pln">
let textX </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">cos</span><span class="pun">(</span><span class="pln">middleAngle</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">120</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> centerX</span><span class="pun">;</span><span class="pln">
let textY </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">sin</span><span class="pun">(</span><span class="pln">middleAngle</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">120</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> centerY</span><span class="pun">;</span></pre>

<p>
	أما بالنسبة لـ <code>textBaseline</code>، فإنّ القيمة <code>"middle"</code> مناسبة عند استخدام ذلك المنظور، إذ يعتمد ما تستخدِمه لـ <code>textAlign</code> على الجانب الذي تكون فيه من الدائرة، فإذا كنت على اليسار، فيجب أن تكون <code>"right"</code> والعكس بالعكس، وذلك كي يكون موضع النص بعيدًا عن الدائرة.
</p>

<p>
	إذا لم تعرف كيف تجد الجانب الذي عليه زاوية ما من الدائرة، فانظر في شرح <code>Math.cos</code> في نموذج كائن المستند في جافاسكريبت، إذ يخبرك جيب التمام cosine لتلك الدالة بالإحداثي x الموافق لها، والذي يخبرنا بدوره على أي جانب من الدائرة نحن.
</p>

<h3>
	الكرة المرتدة
</h3>

<p>
	استخدام تقنية <code>requestAnimationFrame</code> التي رأيناها في مقال نموذج كائن المستند في جافاسكريبت والمقال السابق من أجل رسم صندوق فيه كرة مرتدة تتحرك بسرعة ثابتة وترتد عن جوانب الصندوق عندما تصطدم بها.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8250_69" style="">
<span class="pun">&lt;</span><span class="pln">canvas width</span><span class="pun">=</span><span class="str">"400"</span><span class="pln"> height</span><span class="pun">=</span><span class="str">"400"</span><span class="pun">&gt;&lt;/</span><span class="pln">canvas</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let cx </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"canvas"</span><span class="pun">).</span><span class="pln">getContext</span><span class="pun">(</span><span class="str">"2d"</span><span class="pun">);</span><span class="pln">

  let lastTime </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> frame</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lastTime </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      updateAnimation</span><span class="pun">(</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="lit">100</span><span class="pun">,</span><span class="pln"> time </span><span class="pun">-</span><span class="pln"> lastTime</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    lastTime </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">;</span><span class="pln">
    requestAnimationFrame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  requestAnimationFrame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> updateAnimation</span><span class="pun">(</span><span class="pln">step</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// شيفرتك هنا.</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<p>
	يسهل رسم الصندوق باستخدام <code>strokeRect</code>، لذا عرِّف رابطةً تحمل حجمه أو عرِّف رابطتَين إذا كان عرض الصندوق يختلف عن طوله؛ أما لإنشاء كرة مستديرة، فابدأ مسارًا واستدعي ‎<code>arc(x, y, radius, 0, 7)</code>‎ الذي ينشئ قوسًا من الصفر إلى أكثر من دائرة كاملة ثم املأ المسار.
</p>

<p>
	استخدِم الصنف <code>Vec</code> من <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D9%84%D8%B9%D8%A8%D8%A9-%D9%85%D9%86%D8%B5%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1355/" rel="">المقال السابق</a> ليصف نموذج سرعة الكرة وموضعها، وأعطه سرعةً ابتدائيةً يفضَّل أن تكون عموديةً فقط أو أفقيةً فقط، ثم اضرب تلك السرعة بمقدار الوقت المنقضي لكل إطار، وحين تقترب الكرة جدًا من حائط عمودي، اعكس المكوِّن x في سرعتها، وبالمثل اعكس المكوِّن y إذا اصطدمت بحائط أفقي، وبعد إيجاد موضع الكرة وسرعتها الجديدين، استخدم <code>clearRect</code> لحذف المشهد وإعادة رسمه باستخدام الموضع الجديد.
</p>

<h3>
	الانعكاس المحسوب مسبقا
</h3>

<p>
	إنّ أحد عيوب التحولات أنها تبطئ عملية الرسومات النقطية bitmaps، إذ يجب تحويل موضع وحجم كل بكسل، ويتسبب ذلك في زيادة كبيرة في وقت الرسم في المتصفحات، كما لا تمثِّل تلك مشكلةً في لعبتنا التي نرسم فيها عفريتًا واحدًا؛ أما رسم مئات الشخصيات أو آلاف الجزيئات التي تدور في الهواء نتيجة انفجار مثلًا، فستكون تلك معضلةً حقيقيةً.
</p>

<p>
	فكِّر في طريقة تسمح برسم شخصية معكوسة دون تحميل ملفات صور إضافية، ودون الحاجة إلى إنشاء استدعاءات <code>drawImage</code> متحولة لكل إطار.
</p>

<h4>
	إرشادات الحل
</h4>

<p>
	تستطيع استخدام عنصر اللوحة على أساس صورة مصدرية عند استخدام <code>drawImage</code>، حيث يمكن إنشاء عنصر <code>&lt;canvas&gt;</code> آخر دون إضافته إلى المستند وسحب عناصر العفاريت المعكوسة إليه مرةً واحدةً، وعند رسم إطار حقيقي ننسخ تلك المعكوسة إلى اللوحة الأساسية، لكن يجب توخي الحذر لأن الصور لا تحمَّل فورًا، فنحن ننفِّذ الرسم المعكوس مرةً واحدةً فقط، وإذا نفَّذناه قبل تحميل الصورة، فلن ترسم أي شيء، كما يمكن استخدام معالج <code>"load"</code> الذي على الصورة على أساس مصدر رسم مباشرةً، وسيكون فارغًا إلى أن نرسم الشخصية عليه.
</p>

<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/17_canvas.html" rel="external nofollow">للفصل السابع عشر من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/advance/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-r1305/" rel="">مشروع بناء لغة برمجة خاصة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%8A-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D8%B1%D8%AC%D9%84-%D8%A2%D9%84%D9%8A-%D8%B1%D9%88%D8%A8%D9%88%D8%AA-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1244/" rel="">مشروع تطبيقي لبناء رجل آلي (روبوت) عبر جافاسكريبت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1364</guid><pubDate>Sun, 17 Oct 2021 15:00:00 +0000</pubDate></item><item><title>&#x62A;&#x62E;&#x632;&#x64A;&#x646; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x645;&#x62D;&#x644;&#x64A;&#x627; &#x641;&#x64A; &#x645;&#x62A;&#x635;&#x641;&#x62D; &#x627;&#x644;&#x648;&#x64A;&#x628; &#x639;&#x628;&#x631; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%85%D8%AD%D9%84%D9%8A%D8%A7-%D9%81%D9%8A-%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1338/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_10/615fdf9560cff_--localStorage--sessionStorage.png.baf57d3b6dfaf7de8d904ab4d20450e7.png" /></p>

<p>
	يسمح هذان الكائنان بتخزين الأزواج "مفتاح/قيمة" في المتصفح، لكن الميزة الهامة لهما هي بقاء البيانات المخزنة في الكائن <code>sessionStorage</code> بعد تحديث الصفحة وبقاء المعلومات المخزنة في <code>localStorage</code> بعد إعادة تشغيل <a data-ss1634999751="1" href="https://academy.hsoub.com/programming/javascript/%D8%A3%D9%81%D8%B9%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D8%A7%D9%81%D8%AA%D8%B1%D8%A7%D8%B6%D9%8A%D8%A9-%D9%84%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1143/" rel="">المتصفح</a>، لكن السؤال الذي يلفت النظر هو: ما دام لدينا <a data-ss1634999751="1" href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-javascript-r1330/" rel="">ملفات تعريف الارتباط cookies</a>، فلماذا سنستخدم كائنات إضافيةً؟ والجواب:
</p>

<ul>
<li>
		لا يُرسَل كائنا تخزين بيانات الويب هذان إلى الخادم مع كل طلب، وذلك خلافًا لملفات تعريف الارتباط، وبالتالي سنتمكن من تخزين بيانات أكثر، حيث تتيح أغلب المتصفحات حوالي 2 ميغابايت من البيانات -وأكثر-، ولها إعدادات لتهيئة حجم التخزين.
	</li>
	<li>
		لا يمكن للخادم التحكم بهذين الكائنين عبر ترويسات <a data-ss1634999751="1" href="https://academy.hsoub.com/programming/general/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-http-r73/" rel="">HTTP</a>، وسيُنجز كل شيء باستخدام <a data-ss1634999751="1" href="https://wiki.hsoub.com/JavaScript" rel="external">JavaScript</a>، خلافًا لملفات تعريف الارتباط.
	</li>
	<li>
		ترتبط ذاكرة التخزين بالأصل الذي ولّدها (نطاق/بروتوكول/منفذ)، مما يعني أن البروتوكولات أو النطاقات الفرعية المختلفة ستدل على كائنات تخزين مختلفة، ولا يمكن أن تصل إلى بيانات بعضها البعض.
	</li>
</ul>
<p>
	لكائني التخزين التوابع والخصائص نفسها، وهي:
</p>

<ul>
<li>
		<code>(setItem(key, value</code>: يخزّن الأزواج "مفتاح/قيمة".
	</li>
	<li>
		<code>(getItem(key</code>: يحصل على القيمة عن طريق المفتاح.
	</li>
	<li>
		<code>(removeItem(key</code>: يزيل المفتاح مع قيمته.
	</li>
	<li>
		<code>()clear</code>: يحذف كل شيء.
	</li>
	<li>
		<code>(key(index</code>: يعيد مفتاحًا ذا موقع محدد.
	</li>
	<li>
		<code>length</code>: يعطي عدد العناصر المخزّنة.
	</li>
</ul>
<p>
	تشبه هذه التوابع ما يقوم به الترابط Map، لكنه يسمح أيضًا بالوصول إلى المفاتيح من خلال مواقعها <code>(key(index</code>.
</p>

<h2>
	مثال نموذجي عن الكائن localStorage
</h2>

<p>
	ميزات هذا الكائن الرئيسية هي:
</p>

<ul>
<li>
		مشترك بين كل النوافذ التي تشترك بالأصل ذاته.
	</li>
	<li>
		ليس للبيانات فترة صلاحية، إذ تبقى بعد إعادة تشغيل المتصفح أو نظام التشغيل.
	</li>
</ul>
<p>
	فلو شغّلنا الشيفرة التالية مثلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_8" style="">
<span class="pln">localStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">'test'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span></pre>

<p>
	إذا أغلقنا المتصفح ثم أعدنا تشغيله، أو فتحنا الصفحة نفسها في أكثر من نافذة، فسنحصل على القيمة التي خزناها بالشكل التالي:
</p>

<pre class="ipsCode">
alert( localStorage.getItem('test') ); // 1
</pre>

<p>
	علينا فقط أن نكون ضمن صفحات تنتمي إلى الأصل ذاته (نطاق/منفذ/بروتوكول)، على الرغم من إمكانية اختلاف المسار، لأن الكائن <code>localStorage</code> مشترك بين كل النوافذ التي تنتمي إلى الأصل ذاته.
</p>

<h2>
	الوصول بأسلوب الكائنات
</h2>

<p>
	يمكن استخدام أسلوب <a data-ss1634999751="1" href="https://academy.hsoub.com/programming/javascript/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1234/" rel="">الكائن </a>البسيط للحصول على المفاتيح أو تغيير قيمها بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_10" style="">
<span class="com">// ضبط المفتاح</span><span class="pln">
localStorage</span><span class="pun">.</span><span class="pln">test </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">

</span><span class="com">// الحصول على قيمة المفتاح</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> localStorage</span><span class="pun">.</span><span class="pln">test </span><span class="pun">);</span><span class="pln"> </span><span class="com">// 2</span><span class="pln">

</span><span class="com">// إزالة المفتاح</span><span class="pln">
</span><span class="kwd">delete</span><span class="pln"> localStorage</span><span class="pun">.</span><span class="pln">test</span><span class="pun">;</span></pre>

<p>
	يُسمح بهذه الطريقة -التي ستعمل غالبًا- لأسباب تاريخية، لكن لا يُفضّل استعمالها للأسباب التالية:
</p>

<ol>
<li>
		لو ولّد المستخدم المفتاح، فقد يكون أي شيء مثل <code>length</code> أو <code>toString</code>، أي قد يتشابه مع توابع محجوزة تستخدَم مع <code>localStorage</code>، في هذه الحالة ستعمل التوابع <code>getItem/setItem</code>، لكن سيخفق الوصول بأسلوب الكائنات .
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_12" style="">
<span class="pln">let key </span><span class="pun">=</span><span class="pln"> </span><span class="str">'length'</span><span class="pun">;</span><span class="pln">
localStorage</span><span class="pun">[</span><span class="pln">key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pun">;</span><span class="pln"> </span><span class="com">// Error, can't assign length</span></pre>

<ol start="2">
<li>
		الحدث <code>storage</code> الذي يقع عند تعديل البيانات، وليس عند تطبيق أسلوب الكائنات، وسنرى ذلك لاحقًا في هذا المقال.
	</li>
</ol>
<h2>
	التنقل بين المفاتيح ضمن حلقات
</h2>

<p>
	تؤمّن التوابع السابقة وظائف الحصول على قيم <a data-ss1634999751="1" href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%81%D8%A7%D8%AA%D9%8A%D8%AD-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%88%D9%82%D9%8A%D9%85%D9%87%D8%A7-%D9%88%D9%85%D8%AF%D8%AE%D9%84%D8%A7%D8%AA%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r823/" rel="">المفاتيح </a>وضبطها وحذفها، لكن كيف سنخزّن جميع القيم أو المفاتيح؟ لسوء الحظ لا تقبل كائنات تخزين البيانات التكرار، لكن إحدى الطرق المتبعة هي التنقل في حلقة كما لو أننا نتعامل مع مصفوفة:
</p>

<pre class="ipsCode">
for(let i=0; i&lt;localStorage.length; i++) {
  let key = localStorage.key(i);
  alert(`${key}: ${localStorage.getItem(key)}`);
}
</pre>

<p>
	يمكن استخدام الحلقة <code>for key in localStorage</code> كما نفعل مع الكائنات النظامية، حيث تتكرر تعليمات الحلقة وفقًا للمفاتيح المخزّنة، لكنها ستعطي حقولًا مدمجةً غير مطلوبة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_14" style="">
<span class="com">// محاولة فاشلة</span><span class="pln">
</span><span class="kwd">for</span><span class="pun">(</span><span class="pln">let key in localStorage</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="pln">key</span><span class="pun">);</span><span class="pln"> </span><span class="com">// وغيرها من الحقول المدمجة getItem, setItem ستظهر </span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	فإمّا أن نرشح الحقول التي ستُعرض بالتحقق من الخاصية <code>hasOwnProperty</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_16" style="">
<span class="kwd">for</span><span class="pun">(</span><span class="pln">let key in localStorage</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">localStorage</span><span class="pun">.</span><span class="pln">hasOwnProperty</span><span class="pun">(</span><span class="pln">key</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">continue</span><span class="pun">;</span><span class="pln"> </span><span class="com">// "setItem", "getItem" تتجاهل مفاتيح مثل</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="pln">$</span><span class="pun">{</span><span class="pln">key</span><span class="pun">}:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">localStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="pln">key</span><span class="pun">)}`);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	أو نحصل على المفاتيح الخاصة بالكائن باستخدام الأمر <code>Object.keys</code>، ثم نطبق الحلقة عليها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_18" style="">
<span class="pln">let keys </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">(</span><span class="pln">localStorage</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">for</span><span class="pun">(</span><span class="pln">let key of keys</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="pln">$</span><span class="pun">{</span><span class="pln">key</span><span class="pun">}:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">localStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="pln">key</span><span class="pun">)}`);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ستنجح الطريقة الأخيرة لأنّ التابع <code>Object.keys</code> سيعيد المفاتيح التي ترتبط بالكائن فقط، ويتجاهل النموذج الأولي prototype.
</p>

<h2>
	قيم نصية فقط
</h2>

<p>
	يجب الانتباه إلى أنّ المفاتيح وقيمها كائنات نصية، وستحوّل أي أنواع أخرى - مثل الأرقام- إلى قيم نصية تلقائيًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_21" style="">
<span class="pln">sessionStorage</span><span class="pun">.</span><span class="pln">user </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"John"</span><span class="pun">};</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">sessionStorage</span><span class="pun">.</span><span class="pln">user</span><span class="pun">);</span><span class="pln"> </span><span class="com">// [object Object]</span></pre>

<p>
	يمكن استخدام <a data-ss1634999751="1" href="https://academy.hsoub.com/programming/javascript/%D8%B5%D9%8A%D8%BA%D8%A9-json-%D9%88%D8%AA%D9%88%D8%A7%D8%A8%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r826/" rel="">JSON </a>لتخزين الكائنات أيضًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_25" style="">
<span class="pln">sessionStorage</span><span class="pun">.</span><span class="pln">user </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">({</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"John"</span><span class="pun">});</span><span class="pln">

</span><span class="com">// لاحقًا</span><span class="pln">
let user </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln"> sessionStorage</span><span class="pun">.</span><span class="pln">user </span><span class="pun">);</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> user</span><span class="pun">.</span><span class="pln">name </span><span class="pun">);</span><span class="pln"> </span><span class="com">// John</span></pre>

<p>
	كما يمكن تحويل كائن التخزين بالكامل إلى نص، لأغراض التنقيح مثلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_27" style="">
<span class="com">// لتبدو النتيجة أفضل JSON.stringify يمكن إضافة خيارات تنسيق إلى</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">(</span><span class="pln">localStorage</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span></pre>

<h2>
	الكائن sessionStorage
</h2>

<p>
	يُستخدم في حالات أقل من الكائن localStorage، وله نفس التوابع والخصائص، لكنها أكثر محدوديةً.
</p>

<ul>
<li>
		يتواجد الكائن فقط ضمن النافذة الحالية المفتوحة ضمن المتصفح.
	</li>
	<li>
		سيكون لنافذة أخرى مفتوحة ضمن المتصفح كائن آخر خاص بها.
	</li>
	<li>
		تتشارك النوافذ الضمنية الموجودة في نافذة نفس الكائن، بفرض أنها مشتركة بالأصل.
	</li>
	<li>
		تبقى البيانات المخزنة بعد تحديث الصفحة، لكنها تُحذف عند إغلاق النافذة وإعادة فتحها.
	</li>
</ul>
<p>
	شغّل هذه الشيفرة لترى آلية عمل الكائن:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_29" style="">
<span class="pln">sessionStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">'test'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span></pre>

<p>
	ثم حدّث الصفحة. عندها ستلاحظ أن البيانات مازالت موجودة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_31" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> sessionStorage</span><span class="pun">.</span><span class="pln">getItem</span><span class="pun">(</span><span class="str">'test'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">);</span><span class="pln"> </span><span class="com">// after refresh: 1</span></pre>

<p>
	لكن لو فتحت الصفحة نفسها في نافذة أخرى، وحاولت تنفيذ الشيفرة السابقة مجددًا، فستعيد القيمة "null" أي أنها لم تجد شيئًا، لأنّ الكائن <code>sessionStorage</code> لا يتعلق فقط بالأصل المشترك بل بالنافذة المفتوحة في المتصفح، لذا يندر استخدامه.
</p>

<h2>
	أحداث التخزين
</h2>

<p>
	عندما تُحدّث البيانات ضمن كائني التخزين فستقع <a data-ss1634999751="1" href="https://www.w3.org/TR/webstorage/#the-storage-event" rel="external nofollow">أحداث التخزين</a> التالية:
</p>

<ul>
<li>
		<code>key</code>: المفتاح الذي تغيّر، وسيعيد <code>null</code> إذا استدعي التابع <code>()clear</code>.
	</li>
	<li>
		<code>oldValue</code>: القيمة القديمة، وستكون <code>null</code> إذا أضيف المفتاح حديثًا.
	</li>
	<li>
		<code>newValue</code>: القيمة الجديدة، وستكون <code>null</code> إذا حُذف المفتاح.
	</li>
	<li>
		<code>url</code>: عنوان الصفحة التي حدث فيها التغيير.
	</li>
	<li>
		<code>storageArea</code> أو أحد الكائنين <code>localStorage</code> أو <code>sessionStorage</code> حيث حدث التغيير.
	</li>
</ul>
<p>
	أمّا الأمر الهام فهو أنّ هذه الأحداث ستقع في كل الكائنات <code>window</code> التي يمكن فيها الوصول إلى كائن تخزين البيانات، عدا تلك التي سببت وقوع الحدث.
</p>

<p>
	تخيل وجود نافذتين في المتصفح تعرضان الصفحة نفسها، عندئذ ستتشارك النافذتان الكائن <code>localStorage</code> نفسه، وقد يكون عرض الصفحة في نافذتين مختلفتين مناسبًا لاختبار الشيفرة التي سنعرضها تاليًا، إذا استمعت كلتا النافذتين إلى الحدث <code>window.onstorage</code>، فستتفاعلان مع التحديثات التي تجري في كلٍّ منهما.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2225_33" style="">
<span class="com">// يقع عند تحديث كائن التخزين من قبل صفحة أخرى</span><span class="pln">
window</span><span class="pun">.</span><span class="pln">onstorage </span><span class="pun">=</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// same as window.addEventListener('storage', event =&gt; {</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">key </span><span class="pun">!=</span><span class="pln"> </span><span class="str">'now'</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  alert</span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">key </span><span class="pun">+</span><span class="pln"> </span><span class="str">':'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">newValue </span><span class="pun">+</span><span class="pln"> </span><span class="str">" at "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">url</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

localStorage</span><span class="pun">.</span><span class="pln">setItem</span><span class="pun">(</span><span class="str">'now'</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">());</span></pre>

<p>
	لاحظ أنّ الحدث سيتضمن أيضًا <code>event.url</code>، وهو عنوان الصفحة التي حصل فيها التغيير، كما يتضمن الحدث كائن التخزين (سيبقى الحدث نفسه للكائنين <code>sessionStorage</code> و<code>localStorage</code>)، لذا سيشير الحدث <code>event.storageArea</code> إلى الكائن الذي جرى تعديله منهما، وبما أننا نريد أن نعيد ضبط قيمة ما استجابةً للتغيير، فسيسمح ذلك للنوافذ ذات الأصل المشترك بتبادل الرسائل.
</p>

<p>
	تدعم المتصفحات الحديثة الواجهة البرمجية لقناة البث <a data-ss1634999751="1" href="https://developer.mozilla.org/en-US/docs/Web/api/Broadcast_Channel_API" rel="external nofollow">Broadcast channel <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>، وهي واجهة خاصة بتبادل الرسائل بين النوافذ التي لها أصل مشترك. لهذه الواجهة ميزات متكاملة أكثر لكنها أقل دعمًا، ومع ذلك فستجد الكثير من المكتبات التي توائم هذه الواجهة مع المتصفحات بالاستفادة من الكائن <code>localStorage</code> مما يجعلها متاحةً في أي مكان.
</p>

<h2>
	خلاصة
</h2>

<p>
	يسمح الكائنان <code>localStorage</code> و<code>sessionStorage</code> بتخزين الأزواج (مفتاح/قيمة) في المتصفح.
</p>

<ul>
<li>
		المفتاح <code>key</code> والقيمة <code>value</code> من النوع النصي.
	</li>
	<li>
		الحد الأقصى للتخزين بحدود 5 ميغابايت وذلك تبعًا للمتصفح.
	</li>
	<li>
		ليس لها فترة صلاحية.
	</li>
	<li>
		ترتبط البيانات بأصل الصفحة (نطاق/ منفذ/بروتوكول).
	</li>
</ul>
<style type="text/css">
table {
    width: 100%;
}

thead {
    vertical-align: middle;
    text-align: center;
} 

td, th {
    border: 1px solid #dddddd;
    text-align: right;
    padding: 8px;
    text-align: inherit;

}
tr:nth-child(even) {
    background-color: #dddddd;
}</style>
<table>
<thead><tr>
<th style="text-align:center">
				<code>localStorage</code>
			</th>
			<th style="text-align:center">
				<code>sessionStorage</code>
			</th>
		</tr></thead>
<tbody>
<tr>
<td style="text-align:center">
				مشترك بين النوافذ التي لها نفس الأصل
			</td>
			<td style="text-align:center">
				تُرى ضمن نافذة واحدة في المتصفح بما فيها النوافذ الضمنية التي لها نفس الأصل
			</td>
		</tr>
<tr>
<td style="text-align:center">
				تبقى بعد إعادة تشغيل المتصفح
			</td>
			<td style="text-align:center">
				تبقى بعد تحديث الصفحة لكنها تحذف عند إغلاق النافذة
			</td>
		</tr>
</tbody>
</table>
<p>
	الواجهة البرمجية:
</p>

<ul>
<li>
		<code>(setItem(key, value</code>: يخزّن أزواج (مفتاح/قيمة).
	</li>
	<li>
		<code>(getItem(key</code>: يحصل على القيمة عن طريق المفتاح.
	</li>
	<li>
		<code>(removeItem(key</code>: يزيل المفتاح مع قيمته.
	</li>
	<li>
		<code>()clear</code>: يحذف كل شيء.
	</li>
	<li>
		<code>(key(index</code>: يعيد مفتاحًا ذا موقع محدد.
	</li>
	<li>
		<code>length</code>: يعطي عدد العناصر المخزّنة.
	</li>
	<li>
		<code>Object.keys</code>: للحصول على جميع المفاتيح.
	</li>
	<li>
		يمكن الوصول إلى المفاتيح عبر خصائص الكائن، لكن لن يقع الحدث <code>storage</code> في هذه الحالة.
	</li>
</ul>
<p>
	أحداث التخزين:
</p>

<ul>
<li>
		تقع نتيجةً للاستدعاءات التالية <code>setItem</code> أو <code>removeItem</code> أو <code>clear</code>.
	</li>
	<li>
		تحتوي على كل البيانات المتعلقة بالعملية <code>key/oldValue/newValue</code>، وبالصفحة <code>url</code>، وبكائن التخزين <code>storageArea</code>.
	</li>
	<li>
		تقع في جميع النوافذ التي يمكنها الوصول إلى كائن التخزين، عدا تلك التي ولّدته، ضمن نافذة واحدة بالنسبة للكائن <code>sessionStorage</code>، ولكل النوافذ بالنسبة للكائن <code>localStorage</code>.
	</li>
</ul>
<h2>
	مهمات لإنجازها
</h2>

<h3>
	الحفظ التلقائي لحقل من حقول نموذج
</h3>

<p>
	أنشئ حقلًا نصيًا <code>textarea</code> يحفظ تلقائيًا القيمة التي يحتويها بعد كل تغيير فيها، وبالتالي عندما يُغلق المستخدم الصفحة عن طريق الخطأ، ثم يفتحها مجددًا، فسيجد النص الذي لم يكمله بعد في مكانه.
</p>

<p>
	<iframe __idm_frm__="63" class="code-result__iframe" data-ss1633600451="1" data-ss1634999751="1" data-trusted="1" src="https://en.js.cx/task/form-autosave/solution/" style="height:120px"></iframe>
</p>

<p>
	<a data-ss1634999751="1" href="https://plnkr.co/edit/cShtUwrQINhUFnnK?p=preview" rel="external nofollow">افتح المثال في بيئة تجريبية</a>. وإن أردت الحل، فهو في <a data-ss1634999751="1" href="https://plnkr.co/edit/IUtAagHWvtGEX3NT?p=preview" rel="external nofollow">هذه البيئة التجريبية</a>.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1634999751="1" href="https://javascript.info/localstorage" rel="external nofollow">localStorage, sessionStorage</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a data-ss1634999751="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%B9%D9%85%D9%84-%D9%85%D8%B9-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-indexeddb-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1337/" rel="">العمل مع قاعدة البيانات IndexedDB في جافاسكربت</a>
	</li>
	<li>
		<a data-ss1634999751="1" href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-javascript-r1330/" rel="">ملفات تعريف الارتباط وضبطها في JavaScript</a>
	</li>
	<li>
		<a data-ss1634999751="1" href="https://academy.hsoub.com/programming/general/%D8%B9%D9%84%D9%88%D9%85-%D8%A7%D9%84%D8%AD%D8%A7%D8%B3%D9%88%D8%A8/" rel="">المدخل الشامل لتعلم علوم الحاسوب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1338</guid><pubDate>Tue, 12 Oct 2021 15:06:00 +0000</pubDate></item><item><title>&#x645;&#x634;&#x631;&#x648;&#x639; &#x644;&#x639;&#x628;&#x629; &#x645;&#x646;&#x635;&#x629; &#x628;&#x627;&#x633;&#x62A;&#x62E;&#x62F;&#x627;&#x645; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D9%84%D8%B9%D8%A8%D8%A9-%D9%85%D9%86%D8%B5%D8%A9-%D8%A8%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1355/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_10/617da176afe33_-01-01.jpg.3180f7ceb05c96aca965ad4d8e902169.jpg" /></p>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		ما الواقع إلا لعبة كبيرة.
	</p>

	<p>
		ـــ إيان بانكس Iain Banks، لاعب الألعاب The Player of Games
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81065" href="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_16.jpg.0279820fa97816099387cb7e9e9807a3.jpg" rel="" data-fileext="jpg"><img alt="chapter_picture_16.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="81065" data-unique="zths7r6et" src="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_16.jpg.0279820fa97816099387cb7e9e9807a3.jpg"></a>
</p>

<p>
	بدأ ولع الكثير من العاملين في مجالات التقنية بالحواسيب منذ الصغر من خلال ألعاب الحاسوب، خاصةً تلك التي فيها عوالم افتراضية يستطيع اللاعب فيها التحكم في سير اللعبة وأحداث القصة التي فيها، ورغم تشابه مجال برمجة الألعاب مع مجال الموسيقى من حيث نسبة العرض إلى الطلب، إلا أن التناقض العجيب بين كمية المبرمجين الصغار الذي يرغبون في العمل فيها ومقدار الطلب الحقيقي على أولئك المبرمجين يخلق بيئة عمل في غاية السوء، لكن بأي حال فذاك لا يمنع أنّ كتابة الألعاب قد تكون مسليةً للبعض.
</p>

<p>
	سنتعلم هنا كيفية كتابة لعبة منصة صغيرة، وتُعَدّ لعب المنصات platform games -أو ألعاب "اقفز واركض"- ألعابًا تتوقع من اللاعب تحريك شخصية في عالم افتراضي يكون ثنائي الأبعاد غالبًا، كما يكون منظور اللعبة من الجانب مع القفز على عناصر وكائنات أو القفز خلالها.
</p>

<h2>
	اللعبة
</h2>

<p>
	ستبنى لعبتنا بصورة ما على <a href="http://www.lessmilk.com/game/dark-blue/" rel="external nofollow">Dark Blue</a> التي كتبها توماس بالِف Thomas Palef، وقد اخترنا هذه اللعبة لأنها مسلية وصغيرة الحجم في نفس الوقت، كما يمكن بناؤها دون كتابة الكثير من الشيفرات وستبدو في النهاية هكذا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81066" href="https://academy.hsoub.com/uploads/monthly_2021_10/darkblue.png.4c228b6996224dec17076b7151e3a022.png" rel="" data-fileext="png"><img alt="darkblue.png" class="ipsImage ipsImage_thumbnailed" data-fileid="81066" data-unique="23sjtvbmf" style="width: 600px; height: auto;" src="https://academy.hsoub.com/uploads/monthly_2021_10/darkblue.png.4c228b6996224dec17076b7151e3a022.png"></a>
</p>

<p>
	يمثَّل اللاعب بالصندوق الداكن الذي تكون مهمته جمع الصناديق الصفراء -أي العملات النقدية- مع تجنب الكائنات الحمراء -أي الحمم البركانية-، ويكتمل المستوى حين تُجمع كل العملات النقدية.
</p>

<p>
	يستطيع اللاعب التحرك في اللعبة باستخدام أسهم اليمين واليسار، كما يستطيع القفز باستخدام زر السهم الأعلى، ويكون للقفز هنا ميزةً ليست واقعيةً لكنها تعطي اللاعب إحساس التحكم بالشخصية التي على الشاشة، وهي أن الشخصية تستطيع القفز لمسافة أكبر بعدة مرات من طولها، كما تستطيع تغيير اتجاهها في الهواء.
</p>

<p>
	تتكون اللعبة من خلفية ثابتة توضع في هيئة شبكة خلفية، وتكون العناصر المتحركة فوق تلك الخلفية، كما يكون كل حقل في تلك الشبكة إما خاليًا أو مصمتًا أو يحوي حممًا؛ أما العناصر المتحركة فتكون اللاعب أو العملات أو أجزاء من الحمم، كما لا تتقيد مواضع تلك العناصر بالشبكة بل قد تكون إحداثياتها أعدادًا كسْريةً لتسمح لها بالحركة بنعومة.
</p>

<h2>
	التقنية
</h2>

<p>
	سنستخدم نموذج كائن المستند DOM الخاص بالمتصفح لعرض اللعبة، وسنقرأ مدخلات المستخدِم من خلال معالجة أحداث المفاتيح.
</p>

<p>
	تمثِّل الشيفرة المتعلقة بالشاشة ولوحة المفاتيح جزءًا صغيرًا من العمل الذي علينا تنفيذه لبناء هذه اللعبة، ولن يكون رسم اللعبة صعبًا بما أنها تتكون من صناديق ملونة في الغالب، حيث سننشئ عناصر DOM وسنستخدم التنسيقات لنعطيها لون خلفية وحجمًا وموضعًا أيضًا.
</p>

<p>
	تمثَّل الخلفية على أساس جدول بما أنها شبكة مربعات ثابتة؛ أما العناصر التي تتحرك بحرية فتمثَّل باستخدام عناصر مطلقة الموضع absolutely positioned، وبما أننا نريد تمثيل مدخلات اللاعب والتجاوب معها دون تأخير ملحوظ فستكون الكفاءة مهمة هنا، ورغم أن DOM لم يُصمم للرسوميات عالية الأداء، إلا أن أداءه أفضل مما هو متوقع، فقد رأينا بعض ذلك في مقال <a href="https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%86%D8%AF-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1322/" rel="">نموذج كائن المستند في جافاسكريبت</a>، وسيكون أداء مثل هذه اللعبة ممتازًا على حاسوب حديث حتى لو لم نحسِّن أداءها كثيرًا.
</p>

<p>
	لكن مع هذا فسننظر في تقنية أخرى للمتصفحات في المقال التالي، وهي وسم <code>&lt;canvas&gt;</code> الذي يوفر طريقةً تقليديةً أكثر لتصميم الرسوميات، إذ يتعامل مع الأشكال والبكسلات بدلًا من عناصر DOM.
</p>

<h2>
	المستويات
</h2>

<p>
	نريد طريقةً لتحديد مستويات اللعبة بحيث يسهل للمستخدِم قراءتها وتعديلها أيضًا، وبما أن كل شيء هنا يمكن إنشاؤه على شبكة، فسنستخدم سلاسل نصية طويلة يمثِّل كل محرف فيها عنصرًا، بحيث يكون إما جزءًا من شبكة الخلفية أو عنصرًا متحركًا، كما سيبدو السطح الذي يمثل أحد المستويات الصغيرة كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_9" style=""><span class="pln">let simpleLevelPlan </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">
</span><span class="pun">......................</span><span class="pln">
</span><span class="pun">..#................#..</span><span class="pln">
</span><span class="pun">..#..............=.#..</span><span class="pln">
</span><span class="pun">..#.........</span><span class="pln">o</span><span class="pun">.</span><span class="pln">o</span><span class="pun">....#..</span><span class="pln">
</span><span class="pun">..#.@......#####...#..</span><span class="pln">
</span><span class="pun">..#####............#..</span><span class="pln">
</span><span class="pun">......#++++++++++++#..</span><span class="pln">
</span><span class="pun">......##############..</span><span class="pln">
</span><span class="pun">......................`;</span></pre>

<p>
	تمثِّل النقاط المساحات الفارغة، وتمثِّل محارف الشباك <code>#</code> الحوائط؛ أما علامات الجمع فتمثِّل الحمم البركانية، ويكون موضع بدء اللاعب عند العلامة <code>@</code>، كما يمثِّل كل محرف <code>O</code> في المستوى هنا عملة نقدية، وتمثل علامة <code>=</code> كتلةً من الحمم تتحرك جيئة وذهابًا بصورة أفقية.
</p>

<p>
	سنضيف نوعين آخرين من الحمم المتحركة، حيث سيمثِّل الأول محرف الأنبوب <code>|</code> للحمم المتحركة رأسيًا، ومحرف <code>v</code> للحمم المتساقطة، وهي حمم متحركة رأسيًا لا تتردد بين نقطتين، وإنما تتحرك للأسفل فقط قافزةً إلى نقطة بدايتها حين تصل إلى القاع.
</p>

<p>
	تتكون اللعبة كلها من مستويات عدة يجب على اللاعب إكمالها، ويكتمل المستوى حين تُجمع كل العملات كما ذكرنا؛ أما إذا لمس اللاعب حممًا بركانيةً فسيعود المستوى الحالي إلى نقطة البداية ليحاول اللاعب مرةً أخرى.
</p>

<h2>
	قراءة المستوى
</h2>

<p>
	يخزن الصنف التالي كائن المستوى، وسيكون وسيطه هو السلسلة النصية التي تعرِّف المستوى.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_11" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Level</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">plan</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let rows </span><span class="pun">=</span><span class="pln"> plan</span><span class="pun">.</span><span class="pln">trim</span><span class="pun">().</span><span class="pln">split</span><span class="pun">(</span><span class="str">"\n"</span><span class="pun">).</span><span class="pln">map</span><span class="pun">(</span><span class="pln">l </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">[...</span><span class="pln">l</span><span class="pun">]);</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">height </span><span class="pun">=</span><span class="pln"> rows</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> rows</span><span class="pun">[</span><span class="lit">0</span><span class="pun">].</span><span class="pln">length</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startActors </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[];</span><span class="pln">

    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">rows </span><span class="pun">=</span><span class="pln"> rows</span><span class="pun">.</span><span class="pln">map</span><span class="pun">((</span><span class="pln">row</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> row</span><span class="pun">.</span><span class="pln">map</span><span class="pun">((</span><span class="pln">ch</span><span class="pun">,</span><span class="pln"> x</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        let type </span><span class="pun">=</span><span class="pln"> levelChars</span><span class="pun">[</span><span class="pln">ch</span><span class="pun">];</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">typeof</span><span class="pln"> type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"string"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> type</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startActors</span><span class="pun">.</span><span class="pln">push</span><span class="pun">(</span><span class="pln">
          type</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">),</span><span class="pln"> ch</span><span class="pun">));</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"empty"</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُستخدَم التابع <code>trim</code> لحذف المسافات الفارغة في بداية ونهاية السلسلة النصية لسطح المستوى level plan، وهذا يسمح للسطح في مثالنا أن يبدأ بسطر جديد كي تكون جميع الأسطر تحت بعضها مباشرةً، ثم تقسَّم السلسلة الباقية بمحارف أسطر جديدة، وينتشر كل سطر في مصفوفة لتكون عندنا مصفوفات من المحارف، وبناءً عليه تحمل <code>rows</code> مصفوفةً من مصفوفات المحارف تكون هي صفوف سطح المستوى، كما نستطيع أخذ عرض المستوى وطوله منها، لكن لا زال علينا فصل العناصر المتحركة من شبكة الخلفية.
</p>

<p>
	سنسمي العناصر المتحركة باسم الكائنات الفاعلة أو actors، والتي ستخزَّن في مصفوفة من الكائنات؛ أما الخلفية فستكون مصفوفةً من مصفوفات سلاسل نصية تحمل أنواعًا من الحقول مثل <code>"empty"</code> أو <code>"wall"</code> أو <code>"lava"</code>.
</p>

<p>
	سنمر على الصفوف ثم على محتوياتها من أجل إنشاء تلك المصفوفات، وتذكَّر أنّ <code>map</code> تمرِّر فهرس المصفوفة على أنه الوسيط الثاني إلى دالة الربط mapping function التي تخبرنا إحداثيات x وy لأي عنصر، كما ستخزَّن المواضع في اللعبة على أساس أزواج من الإحداثيات بحيث يكون الزوج الأعلى إلى اليسار هو 0,0، ثم يكون عرض وارتفاع كل مربع في الخلفية هو وحدة واحدة.
</p>

<p>
	يستخدِم الباني <code>level</code> كائن <code>levelChars</code> لاعتراض الكائنات في سطح المستوى، وهو يربط عناصر الخلفية بالسلاسل، ويربط المحارف الفاعلة actor characters بالأصناف. وحين يكون <code>type</code> صنفَ كائن فاعل actor، فسيُستخدم التابع الساكن <code>create</code> الخاص به لإنشاء كائن يضاف إلى <code>startActors</code>، ثم تعيد دالة الربط <code>"empty"</code> لمربع الخلفية ذاك.
</p>

<p>
	يخزَّن موضع الكائن الفاعل على أساس كائن <code>Vec</code> الذي هو متجه ثنائي الأبعاد، أي كائن له خصائص <code>x</code> و<code>y</code> كما رأينا في مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AD%D9%8A%D8%A7%D8%A9-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%A9-%D9%84%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1243/" rel="">الحياة السرية للكائنات في جافاسكريبت</a>.
</p>

<p>
	ستتغير مواضع الكائنات الفاعلة مع تشغيل اللعبة لتكون في أماكن مختلفة أو حتى تختفي تمامًا كما في حال العملات عند جمعها، ولهذا سنستخدم الصنف <code>State</code> لتتبّع حالة اللعبة أثناء عملها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_13" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">State</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> actors</span><span class="pun">,</span><span class="pln"> status</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">level </span><span class="pun">=</span><span class="pln"> level</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actors </span><span class="pun">=</span><span class="pln"> actors</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">status </span><span class="pun">=</span><span class="pln"> status</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">static</span><span class="pln"> start</span><span class="pun">(</span><span class="pln">level</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">State</span><span class="pun">(</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> level</span><span class="pun">.</span><span class="pln">startActors</span><span class="pun">,</span><span class="pln"> </span><span class="str">"playing"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">get</span><span class="pln"> player</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actors</span><span class="pun">.</span><span class="pln">find</span><span class="pun">(</span><span class="pln">a </span><span class="pun">=&gt;</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"player"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ستتغير الخاصية <code>status</code> لتكون <code>"lost"</code> أو <code>"won"</code> عند نهاية اللعبة، ونكون هنا مرةً أخرى أمام هيكل بيانات ثابت، إذ ينشئ تحديث حالة اللعبة حالةً جديدةً ويترك القديمة كما هي.
</p>

<h2>
	الكائنات الفاعلة Actors
</h2>

<p>
	تمثل الكائنات الفاعلة الموضع والحالة الحاليَين لعنصر معطى في اللعبة، وتعمل كلها بالواجهة نفسها، كما تحمل الخاصية <code>pos</code> الخاصة بها إحداثيات الركن العلوي الأيسر للعنصر، في حين تحمل خاصية <code>size</code> حجمه.
</p>

<p>
	ثم إن لديها التابع <code>update</code> الذي يُستخدَم لحساب حالتها الجديدة وموضعها كذلك بعد خطوة زمنية معطاة، ويحاكي الإجراء الذي يأخذه الكائن الفاعل -الاستجابة لأزرار الأسهم والتحرك وفقها بالنسبة للاعب، والقفز للأمام أو الخلف بالنسبة للحمم-، ويعيد كائن فاعل <code>actor</code> جديدًا ومحدَّثًا.
</p>

<p>
	تحتوي الخاصية <code>type</code> على سلسلة نصية تعرف نوع الكائن الفاعل سواءً كان <code>"player"</code> أو <code>"coin"</code> أو <code>"lava"</code>، وهذا مفيد عند رسم اللعبة، حيث سيعتمد مظهر المستطيل المرسوم من أجل كائن فاعل على نوعه.
</p>

<p>
	تحتوي أصناف الكائنات الفاعلة على التابع الساكن <code>create</code> الذي يستخدمه الباني <code>Level</code> لإنشاء كائن فاعل من شخصية في مستوى السطح، كما يعطى إحداثيات الشخصية والشخصية نفسها، إذ هي مطلوبة لأن صنف <code>Lava</code> يعالج عدة شخصيات مختلفة.
</p>

<p>
	لدينا فيما يلي الصنف <code>Vec</code> الذي سنستخدمه من أجل قيمنا ثنائية البعد مثل موضع الكائنات الفاعلة وحجمها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_15" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Vec</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">x </span><span class="pun">=</span><span class="pln"> x</span><span class="pun">;</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">y </span><span class="pun">=</span><span class="pln"> y</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  plus</span><span class="pun">(</span><span class="pln">other</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> other</span><span class="pun">.</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> other</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  times</span><span class="pun">(</span><span class="pln">factor</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">x </span><span class="pun">*</span><span class="pln"> factor</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">y </span><span class="pun">*</span><span class="pln"> factor</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يغيِّر التابع <code>times</code> حجم المتجه بعدد معيّن، والذي سيفيدنا حين نحتاج إلى زيادة متجه السرعة بضربه في مدة زمنية لنحصل على المسافة المقطوعة خلال تلك المدة.
</p>

<p>
	تحصل الأنواع المختلفة من الكائنات الفاعلة على أصنافها الخاصة بما أنّ سلوكها مختلف. دعنا نعرِّف تلك الأصناف وسننظر لاحقًا في توابع <code>update</code> الخاصة بها. سيكون لصنف اللاعب الخاصية <code>speed</code> التي تخزن السرعة الحالية لتحاكي قوة الدفع والجاذبية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_17" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Player</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> speed</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pos </span><span class="pun">=</span><span class="pln"> pos</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">speed </span><span class="pun">=</span><span class="pln"> speed</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">get</span><span class="pln"> type</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"player"</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">static</span><span class="pln"> create</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Player</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">0.5</span><span class="pun">)),</span><span class="pln">
                      </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="typ">Player</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">size </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0.8</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1.5</span><span class="pun">);</span></pre>

<p>
	يكون الموضع الابتدائي للاعب فوق الموضع الذي يظهر فيه محرف @ بنصف مربع، وذلك لأن طول اللاعب يساوي مربعًا ونصف، وتكون بهذه الطريقة قاعدته بمحاذاة قاعدة المربع الذي يظهر فيه.
</p>

<p>
	كذلك تكون الخاصية <code>size</code> هي نفسها لجميع نُسخ <code>Player</code>، لذا نخزنها في النموذج الأولي بدلًا من النُسخ نفسها، وقد كان بإمكاننا استخدام جالبة مثل <code>type</code>، ولكنه سينشئ ويعيد ذلك كائن <code>Vec</code> جديد في كل مرة تُقرأ فيها الخاصية، وهو هدر لا حاجة له، إذ لا نحتاج إلى إعادة إنشاء السلاسل النصية في كل مرة نقيِّمها بما أنها غير قابلة للتغير immutable.
</p>

<p>
	عند إنشاء الكائن الفاعل <code>Lava</code> سنحتاج إلى تهيئته initialization تهيئةً مختلفةً وفقًا للمحرف المبني عليه، فالحمم الديناميكية تتحرك بسرعتها الحالية إلى أن تصطدم بعائق، فإذا كانت لديها الخاصية <code>reset</code> فستقفز إلى موضع البداية -أي تقطير dripping-؛ أما إذا لم تكن لديها، فستعكس سرعتها وتكمل في الاتجاه المعاكس -أي ارتداد bouncing-.
</p>

<p>
	ينظر التابع <code>create</code> في المحرف الذي يمرره الباني <code>Level</code> وينشئ كائن الحمم الفاعل المناسب.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_19" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Lava</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> speed</span><span class="pun">,</span><span class="pln"> reset</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pos </span><span class="pun">=</span><span class="pln"> pos</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">speed </span><span class="pun">=</span><span class="pln"> speed</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">reset </span><span class="pun">=</span><span class="pln"> reset</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">get</span><span class="pln"> type</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"lava"</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">static</span><span class="pln"> create</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> ch</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ch </span><span class="pun">==</span><span class="pln"> </span><span class="str">"="</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">));</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ch </span><span class="pun">==</span><span class="pln"> </span><span class="str">"|"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">));</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ch </span><span class="pun">==</span><span class="pln"> </span><span class="str">"v"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">3</span><span class="pun">),</span><span class="pln"> pos</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="typ">Lava</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">size </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">1</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span></pre>

<p>
	أما كائنات <code>Coin</code> الفاعلة فهي بسيطة نسبيًا، إذ تظل في مكانها لا تتحرك، لكن سنعطيها تأثيرًا متمايلًا بحيث تتحرك رأسيًا جيئةً وذهابًا، كما يخزن كائن العملة موضعًا أساسيًا وخاصية <code>wobble</code> تتتَّبع مرحلة حركة الارتداد من أجل تتبعها، ويحددان معًا الموضع الحقيقي للعملة ويُخزَّن في الخاصية <code>pos</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_21" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">Coin</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> basePos</span><span class="pun">,</span><span class="pln"> wobble</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pos </span><span class="pun">=</span><span class="pln"> pos</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">basePos </span><span class="pun">=</span><span class="pln"> basePos</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">wobble </span><span class="pun">=</span><span class="pln"> wobble</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">get</span><span class="pln"> type</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"coin"</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">static</span><span class="pln"> create</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let basePos </span><span class="pun">=</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0.2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.1</span><span class="pun">));</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Coin</span><span class="pun">(</span><span class="pln">basePos</span><span class="pun">,</span><span class="pln"> basePos</span><span class="pun">,</span><span class="pln">
                    </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">random</span><span class="pun">()</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI </span><span class="pun">*</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="typ">Coin</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">size </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0.6</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0.6</span><span class="pun">);</span></pre>

<p>
	رأينا في نموذج كائن المستند في جافاسكريبت أنّ <code>Math.sin</code> تعطينا إحداثية y لنقطة ما في دائرة، وتتذبذب تلك الإحداثية في حركة موجية ناعمة أثناء الحركة على الدائرة، مما يعطينا دالةً جيبيةً نستفيد منها في نمذجة الحركة الموجية.
</p>

<p>
	سنجعل مرحلة بدء كل عملة عشوائية، وذلك لكي نتفادى صنع حركة متزامنة للعملات، ويكون عرض الموجة التي تنتجها <code>Math.sin</code> هو 2π وهي مدة الموجة، ثم نضرب القيمة المعادة بـ <code>Math.random</code> بذلك العدد لتعطي العملة موضع بدء عشوائي على الموجة.
</p>

<p>
	نستطيع الآن تعريف كائن <code>levelChars</code> الذي يربط محارف السطح لأنواع شبكة الخلفية أو لأصناف الكائنات الفاعلة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_23" style=""><span class="kwd">const</span><span class="pln"> levelChars </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="str">"."</span><span class="pun">:</span><span class="pln"> </span><span class="str">"empty"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"#"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"wall"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"+"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"lava"</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"@"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Player</span><span class="pun">,</span><span class="pln"> </span><span class="str">"o"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Coin</span><span class="pun">,</span><span class="pln">
  </span><span class="str">"="</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">,</span><span class="pln"> </span><span class="str">"|"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">,</span><span class="pln"> </span><span class="str">"v"</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Lava</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يعطينا ذلك جميع الأجزاء التي نحتاجها لإنشاء نسخة <code>Level</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_25" style=""><span class="pln">let simpleLevel </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Level</span><span class="pun">(</span><span class="pln">simpleLevelPlan</span><span class="pun">);</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="pln">$</span><span class="pun">{</span><span class="pln">simpleLevel</span><span class="pun">.</span><span class="pln">width</span><span class="pun">}</span><span class="pln"> by $</span><span class="pun">{</span><span class="pln">simpleLevel</span><span class="pun">.</span><span class="pln">height</span><span class="pun">}`);</span><span class="pln">
</span><span class="com">// → 22 by 9</span></pre>

<p>
	ستكون المهمة التالية هي عرض تلك المستويات على الشاشة وضبط الوقت والحركة فيها.
</p>

<h2>
	مشكلة التغليف
</h2>

<p>
	لا يؤثر التغليف encapsulation على أغلب الشيفرات الموجودة في هذا المقل لسببين، أولهما أنه يأخذ جهدًا إضافيًا فيجعل البرامج أكبر ويتطلب مفاهيم وواجهات إضافية، وبما أن تلك شيفرات كثيرة على القارئ، فقد اجتهدنا في تصغير البرنامج وتبسيطه؛ أما الثاني أن ثمة عناصر عديدة في اللعبة ترتبط ببعضها، بحيث إذا تغير سلوك أحدها، فمن الصعب بقاء غيرها كما هو، وسيجعل ذلك الواجهات تطرح الكثير من الافتراضات عن طريقة عمل اللعبة، مما يقلل من فائدتها، فإذا غيرنا جزءًا من النظام، فسيكون علينا مراقبة تأثير ذلك في الأجزاء الأخرى بما أنّ واجهاتها لن تغطي الموقف الجديد.
</p>

<p>
	بعض النقاط الفاصلة تقبل الانفصال من خلال واجهات صارمة، لكن هذا ليس حال كل النقاط لدينا، كما سنهدر طاقةً كبيرةً في محاولة تغليف شيء لا يكون حدًا مناسبًا، وسنلاحظ إذا ارتكبنا ذلك الخطأ أنّ واجهاتنا صارت كبيرةً وكثيرة التفاصيل، كما ستحتاج إلى التغيير كل حين كلما تغير البرنامج.
</p>

<p>
	لكن ثمة شيء سنغلفه هو النظام الفرعي للرسم، وذلك لأننا سنعرض اللعبة نفسها بطريقة مختلفة في المقال التالي، فإذا وضعنا الرسم خلف واجهة، فسنتمكن من تحميل برنامج اللعبة نفسه، ونلحقه بوحدة العرض الجديدة.
</p>

<h2>
	الرسم
</h2>

<p>
	تُغلَّف شيفرة الرسم من خلال تعريف كائن عرض display object يعرض مستوى ما وحالته، وسيكون اسم نوع العرض الذي نعرّفه في هذا المقال هو <code>DOMDisplay</code> بما أنه يستخدم عناصر DOM لعرض المستوى، كما سنستخدم ورقة أنماط style sheet لضبط الألوان الفعلية والخصائص الثابتة الأخرى للعناصر التي ستشكِّل اللعبة، كذلك يمكن تعيين الخاصية <code>style</code> للعناصر مباشرةً حين ننشئها لكن سينتج ذلك برامجًا أكثر إسهابًا.
</p>

<p>
	توفر الدالة المساعدة التالية طريقةً موجزةً لإنشاء عنصر وتعطيه بعض السمات والعقد الفرعية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_27" style=""><span class="kwd">function</span><span class="pln"> elt</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> attrs</span><span class="pun">,</span><span class="pln"> </span><span class="pun">...</span><span class="pln">children</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let dom </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="pln">name</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let attr of </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">keys</span><span class="pun">(</span><span class="pln">attrs</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    dom</span><span class="pun">.</span><span class="pln">setAttribute</span><span class="pun">(</span><span class="pln">attr</span><span class="pun">,</span><span class="pln"> attrs</span><span class="pun">[</span><span class="pln">attr</span><span class="pun">]);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let child of children</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    dom</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">child</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> dom</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يُنشأ العرض بإعطائه كائن مستوى وعنصرًا أبًا parent element ليلحِق نفسه به.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_29" style=""><span class="kwd">class</span><span class="pln"> </span><span class="typ">DOMDisplay</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  constructor</span><span class="pun">(</span><span class="pln">parent</span><span class="pun">,</span><span class="pln"> level</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="kwd">class</span><span class="pun">:</span><span class="pln"> </span><span class="str">"game"</span><span class="pun">},</span><span class="pln"> drawGrid</span><span class="pun">(</span><span class="pln">level</span><span class="pun">));</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actorLayer </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">
    parent</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  clear</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">();</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُرسم شبكة الخلفية للمستوى مرةً واحدةً بما أنها لن تتغير، ويعاد رسم الكائنات الفاعلة في كل مرة يُحدّث فيها العرض بحالة ما، كما سنستخدم الخاصية <code>actorLayer</code> لتتبّع العنصر الذي يحمل الكائنات الفاعلة، بحيث يمكن حذفها واستبدالها بسهولة؛ كما ستُتتبّع إحداثياتنا وأحجامنا بوحدات الشبكة، حيث يشير الحجم أو المسافة التي تساوي 1 إلى كتلة شبكة واحدة، ويجب أن نزيد حجم الإحداثيات حين نضبط أحجام البكسلات، إذ سيبدو كل شيء صغيرًا في اللعبة إذا جعلنا كل بكسل يقابل مربعًا واحدًا، وسيعطينا الثابت <code>scale</code> عدد البكسلات التي تأخذها واحدةً واحدة single unit على الشاشة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_31" style=""><span class="kwd">const</span><span class="pln"> scale </span><span class="pun">=</span><span class="pln"> </span><span class="lit">20</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> drawGrid</span><span class="pun">(</span><span class="pln">level</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"table"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">class</span><span class="pun">:</span><span class="pln"> </span><span class="str">"background"</span><span class="pun">,</span><span class="pln">
    style</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">level</span><span class="pun">.</span><span class="pln">width </span><span class="pun">*</span><span class="pln"> scale</span><span class="pun">}</span><span class="pln">px</span><span class="pun">`</span><span class="pln">
  </span><span class="pun">},</span><span class="pln"> </span><span class="pun">...</span><span class="pln">level</span><span class="pun">.</span><span class="pln">rows</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">row </span><span class="pun">=&gt;</span><span class="pln">
    elt</span><span class="pun">(</span><span class="str">"tr"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">style</span><span class="pun">:</span><span class="pln"> </span><span class="pun">`</span><span class="pln">height</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">scale</span><span class="pun">}</span><span class="pln">px</span><span class="pun">`},</span><span class="pln">
        </span><span class="pun">...</span><span class="pln">row</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">type </span><span class="pun">=&gt;</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"td"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="kwd">class</span><span class="pun">:</span><span class="pln"> type</span><span class="pun">})))</span><span class="pln">
  </span><span class="pun">));</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُرسم الخلفية على أساس عنصر <code>&lt;table&gt;</code>، ويتوافق ذلك بسلاسة مع هيكل الخاصية <code>rows</code> للمستوى، فقد حُول كل صف في الشبكة إلى صف جدول -أي عنصر <code>&lt;tr&gt;</code>-؛ أما السلاسل النصية في الشبكة فتُستخدم على أساس أسماء أصناف لخلية الجدول -أي <code>&lt;td&gt;</code>-، كما يُستخدم عامل النشر spread operator -أي النقطة الثلاثية- لتمرير مصفوفات من العقد الفرعية إلى <code>elt</code> لفصل الوسائط.
</p>

<p>
	يوضح المثال التالي كيف نجعل الجدول يبدو كما نريد من خلال شيفرة CSS:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_33" style=""><span class="pun">.</span><span class="pln">background    </span><span class="pun">{</span><span class="pln"> background</span><span class="pun">:</span><span class="pln"> rgb</span><span class="pun">(</span><span class="lit">52</span><span class="pun">,</span><span class="pln"> </span><span class="lit">166</span><span class="pun">,</span><span class="pln"> </span><span class="lit">251</span><span class="pun">);</span><span class="pln">
                 table</span><span class="pun">-</span><span class="pln">layout</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">fixed</span><span class="pun">;</span><span class="pln">
                 border</span><span class="pun">-</span><span class="pln">spacing</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">              </span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln">background td </span><span class="pun">{</span><span class="pln"> padding</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">                     </span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln">lava          </span><span class="pun">{</span><span class="pln"> background</span><span class="pun">:</span><span class="pln"> rgb</span><span class="pun">(</span><span class="lit">255</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln">wall          </span><span class="pun">{</span><span class="pln"> background</span><span class="pun">:</span><span class="pln"> white</span><span class="pun">;</span><span class="pln">              </span><span class="pun">}</span></pre>

<p>
	تُستخدم بعض الوسوم مثل <code>table-layout</code> و<code>border-spacing</code> و<code>padding</code>، لمنع السلوك الافتراضي غير المرغوب فيه، فلا نريد لتخطيط الجدول أن يعتمد على محتويات خلاياه، كما لا نريد مسافات بين خلايا الجدول أو تبطينًا padding داخلها.
</p>

<p>
	تضبط قاعدة <code>background</code> لون الخلفية، إذ تسمح CSS بتحديد الألوان على أساس كلمات -مثل <code>white</code>- أو بصيغة مثل ‎<code>rgb(R, G, B)‎</code>‎‎، حيث تُفصل مكونات اللون الحمراء والخضراء والزرقاء إلى ثلاثة أعداد من 0 إلى 255. ففي اللون ‎<code>rgb(52, 166, 251)</code>‎ مثلًا، سيكون مقدار المكون الأحمر 52 والأخضر 166 والأزرق 251، وبما أن مقدار الأزرق هو الأكبر، فسيكون اللون الناتج مائلًا للزرقة، ويمكن رؤية ذلك في القاعدة <code>‎.lava</code>، إذ أنّ أول عدد فيها -أي الأحمر- هو الأكبر.
</p>

<p>
	سنرسم كل كائن فاعل بإنشاء عنصر DOM له وضبط موضع وحجم ذلك العنصر بناءً على خصائص الكائن الفاعل، ويجب ضرب القيم في <code>scale</code> لتُحوَّل من وحدات اللعبة إلى بكسلات.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_39" style=""><span class="kwd">function</span><span class="pln"> drawActors</span><span class="pun">(</span><span class="pln">actors</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{},</span><span class="pln"> </span><span class="pun">...</span><span class="pln">actors</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">actor </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">let</span><span class="pln"> rect </span><span class="pun">=</span><span class="pln"> elt</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="kwd">class</span><span class="pun">:</span><span class="pln"> </span><span class="str">`actor ${actor.type}`</span><span class="pun">});</span><span class="pln">
    rect</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> </span><span class="str">`${actor.size.x * scale}px`</span><span class="pun">;</span><span class="pln">
    rect</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">height </span><span class="pun">=</span><span class="pln"> </span><span class="str">`${actor.size.y * scale}px`</span><span class="pun">;</span><span class="pln">
    rect</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="str">`${actor.pos.x * scale}px`</span><span class="pun">;</span><span class="pln">
    rect</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> </span><span class="str">`${actor.pos.y * scale}px`</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> rect</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}));</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تُفصل أسماء الأصناف بمسافات كي نعطي العنصر الواحد أكثر من صنف، ففي شيفرة CSS أدناه سنرى أن الصنف <code>actor</code> يعطي الكائنات الفاعلة موضعها المطلق، كما يُستخدم اسم نوعها على أساس صنف إضافي ليعطيها لون، ولا نريد تعريف صنف <code>lava</code> مرةً أخرى بما أننا سنعيد استخدامه من أجل مربعات شبكة الحمم التي عرّفناها سابقًا.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_37" style=""><span class="pun">.</span><span class="pln">actor  </span><span class="pun">{</span><span class="pln"> position</span><span class="pun">:</span><span class="pln"> absolute</span><span class="pun">;</span><span class="pln">            </span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln">coin   </span><span class="pun">{</span><span class="pln"> background</span><span class="pun">:</span><span class="pln"> rgb</span><span class="pun">(</span><span class="lit">241</span><span class="pun">,</span><span class="pln"> </span><span class="lit">229</span><span class="pun">,</span><span class="pln"> </span><span class="lit">89</span><span class="pun">);</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln">player </span><span class="pun">{</span><span class="pln"> background</span><span class="pun">:</span><span class="pln"> rgb</span><span class="pun">(</span><span class="lit">64</span><span class="pun">,</span><span class="pln"> </span><span class="lit">64</span><span class="pun">,</span><span class="pln"> </span><span class="lit">64</span><span class="pun">);</span><span class="pln">   </span><span class="pun">}</span></pre>

<p>
	يُستخدَم التابع <code>syncState</code> لجعل العرض يظهر حالةً ما، وهو يحذف رسوميات الكائن الفاعل القديم أولًا إذا وُجدت، ثم يعيد رسم الكائنات الفاعلة في مواضعها الجديدة.
</p>

<p>
	قد يكون من المغري استخدام عناصر DOM للكائنات الفاعلة، لكننا سنحتاج إلى الكثير من الحسابات الإضافية إذا أردنا إنجاح ذلك من أجل ربطها مع عناصر DOM، ولضمان حذف العناصر حين تختفي كائناتها الفاعلة، وعلى أيّ حال فليست لدينا كائنات فاعلة كثيرة في اللعبة، وبالتالي لن تكون إعادة رسمها مكلفةً.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_43" style=""><span class="typ">DOMDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">syncState </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actorLayer</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actorLayer</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actorLayer </span><span class="pun">=</span><span class="pln"> drawActors</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">actors</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actorLayer</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">className </span><span class="pun">=</span><span class="pln"> </span><span class="str">`game ${state.status}`</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">scrollPlayerIntoView</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	نستطيع تخصيص الكائن الفاعل للاعب تخصيصًا مختلفًا حين تفوز اللعبة أو تخسر بإضافة حالة المستوى الحالية، مثل اسم صنف إلى المغلِّف، وذلك من خلال إضافة قاعدة CSS لا تأخذ ذلك التأثير إلا عندما يكون للاعب عنصر سلف بصنف ما.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_45" style=""><span class="pun">.</span><span class="pln">lost </span><span class="pun">.</span><span class="pln">player </span><span class="pun">{</span><span class="pln">
  background</span><span class="pun">:</span><span class="pln"> rgb</span><span class="pun">(</span><span class="lit">160</span><span class="pun">,</span><span class="pln"> </span><span class="lit">64</span><span class="pun">,</span><span class="pln"> </span><span class="lit">64</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">.</span><span class="pln">won </span><span class="pun">.</span><span class="pln">player </span><span class="pun">{</span><span class="pln">
  box</span><span class="pun">-</span><span class="pln">shadow</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">4px</span><span class="pln"> </span><span class="pun">-</span><span class="lit">7px</span><span class="pln"> </span><span class="lit">8px</span><span class="pln"> white</span><span class="pun">,</span><span class="pln"> </span><span class="lit">4px</span><span class="pln"> </span><span class="pun">-</span><span class="lit">7px</span><span class="pln"> </span><span class="lit">8px</span><span class="pln"> white</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يتغير لون اللاعب إلى الأحمر الداكن بعد لمس الحمم ليشير إلى الحرق، كما نضيف إليه هالةً بيضاء حوله إذا جمع كل العملات، وذلك بإضافة ظلَّين أبيضين ضبابيين، بحيث يكون واحدًا أعلى يساره والثاني أعلى يمينه.
</p>

<p>
	لا يمكن افتراض تلاؤم المستوى مع نافذة الرؤية على الدوام، ونافذة الرؤية هي العنصر الذي سنرسم اللعبة داخله، لذا نحتاج إلى استدعاء <code>scrollPlayerIntoView</code> الذي يضمن أننا سنمرر scroll نافذة الرؤية إذا كان المستوى سيخرج عنها إلى أن يصير اللاعب قريبًا من مركزها.
</p>

<p>
	تعطي شيفرة CSS التالية قيمة حجم عظمى لعنصر DOM المغلِّف الخاص باللعبة، ويضمن ألا يُرى شيء خارج صندوق العنصر، كما سنعطيه موضعًا نسبيًا كي تكون مواضع الكائنات الفاعلة داخلها منسوبةً إلى الركن العلوي الأيسر من المستوى.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_47" style=""><span class="pun">.</span><span class="pln">game </span><span class="pun">{</span><span class="pln">
  overflow</span><span class="pun">:</span><span class="pln"> hidden</span><span class="pun">;</span><span class="pln">
  max</span><span class="pun">-</span><span class="pln">width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">600px</span><span class="pun">;</span><span class="pln">
  max</span><span class="pun">-</span><span class="pln">height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">450px</span><span class="pun">;</span><span class="pln">
  position</span><span class="pun">:</span><span class="pln"> relative</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	نبحث عن موضع اللاعب في التابع <code>scrollPlayerIntoView</code> ونحدِّث موضع التمرير للعنصر المغلِّف، كما نغير موضع التمرير بتعديل الخصائص <code>scrollLeft</code> و<code>scrollTop</code> الخاصة بالعنصر حين يقترب اللاعب من الحافة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_49" style=""><span class="typ">DOMDisplay</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">scrollPlayerIntoView </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let width </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">clientWidth</span><span class="pun">;</span><span class="pln">
  let height </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">clientHeight</span><span class="pun">;</span><span class="pln">
  let margin </span><span class="pun">=</span><span class="pln"> width </span><span class="pun">/</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln">

  </span><span class="com">// The viewport</span><span class="pln">
  let left </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">scrollLeft</span><span class="pun">,</span><span class="pln"> right </span><span class="pun">=</span><span class="pln"> left </span><span class="pun">+</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
  let top </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">scrollTop</span><span class="pun">,</span><span class="pln"> bottom </span><span class="pun">=</span><span class="pln"> top </span><span class="pun">+</span><span class="pln"> height</span><span class="pun">;</span><span class="pln">

  let player </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">player</span><span class="pun">;</span><span class="pln">
  let center </span><span class="pun">=</span><span class="pln"> player</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="pln">player</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">times</span><span class="pun">(</span><span class="lit">0.5</span><span class="pun">))</span><span class="pln">
                         </span><span class="pun">.</span><span class="pln">times</span><span class="pun">(</span><span class="pln">scale</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&lt;</span><span class="pln"> left </span><span class="pun">+</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">scrollLeft </span><span class="pun">=</span><span class="pln"> center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&gt;</span><span class="pln"> right </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">scrollLeft </span><span class="pun">=</span><span class="pln"> center</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> margin </span><span class="pun">-</span><span class="pln"> width</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&lt;</span><span class="pln"> top </span><span class="pun">+</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">scrollTop </span><span class="pun">=</span><span class="pln"> center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&gt;</span><span class="pln"> bottom </span><span class="pun">-</span><span class="pln"> margin</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">dom</span><span class="pun">.</span><span class="pln">scrollTop </span><span class="pun">=</span><span class="pln"> center</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> margin </span><span class="pun">-</span><span class="pln"> height</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تُظهِر الطريقة التي نحدد بها مركز اللاعب كيفية سماح التوابع التي على نوع <code>Vec</code> بكتابة حسابات الكائنات بطريقة قابلة للقراءة نوعًا ما، ونفعل ذلك بإضافة موضع الكائن الفاعل -وهنا ركنه العلوي الأيسر- ونصف حجمه، ويكون هذا هو المركز في إحداثيات المستوى، لكننا سنحتاج إليه في إحدائيات البكسل أيضًا، لذا نضرب المتجه الناتج بمقياس العرض.
</p>

<p>
	تبدأ بعد ذلك سلسلة من التحقُّقات للتأكد من أن موضع اللاعب داخل المجال المسموح به، وقد يؤدي ذلك أحيانًا إلى تعيين إحداثيات تمرير غير منطقية، كأن تكون قيمًا سالبة أو أكبر من مساحة العنصر القابلة للتمرير، ولا بأس في هذا، إذ ستقيد عناصر DOM تلك القيم لتكون في نطاق مسموح به، فإذا ضُبطت <code>srollLeft</code> لتكون <code>‎-10</code> مثلًا، فإنها ستتغير لتصير <code>0</code>.
</p>

<p>
	قد يقال أن الأسهل يكون بجعل اللاعب يُمرَّر دائمًا ليكون في منتصف الشاشة، غير أنّ هذا -وإذا كان أسهل- سيُحدِث أثرًا عكسيًا من حيث تجربة استخدام للعبة، فكلما قفز اللاعب، ستتغير نافذة الرؤية للأعلى والأسفل معه، ولهذا من الأفضل حينها إبقاء منطقة محايدة ثابتة في منتصف الشاشة كي نتحرك دون التسبب في أي تمرير.
</p>

<p>
	نستطيع الآن عرض المستوى الصغير الذي أنشأناه.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_51" style=""><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"css/game.css"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let simpleLevel </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Level</span><span class="pun">(</span><span class="pln">simpleLevelPlan</span><span class="pun">);</span><span class="pln">
  let display </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">DOMDisplay</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> simpleLevel</span><span class="pun">);</span><span class="pln">
  display</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="typ">State</span><span class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span class="pln">simpleLevel</span><span class="pun">));</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	يُستخدَم الوسم <code>&lt;link&gt;</code> مع ‎<code>‎rel="stylesheet"</code>‎ لتحميل ملف CSS إلى الصفحة، ويحتوي ملف <code>game.css</code> على الأنماط الضرورية للعبتنا.
</p>

<h2>
	الحركة والتصادم
</h2>

<p>
	نحن الآن في مرحلة نستطيع فيها إضافة الحركة، وهي لا شك بأنها أكثر جزء مثير في اللعبة، والطريقة التي تتبعها أغلب الألعاب التي تشبه لعبتنا هي تقسيم الوقت إلى خطوات صغيرة ونحرك الكائنات الفاعلة بمسافة تتوافق مع سرعتها ومضروبة في حجم الخطوة الزمنية، كما سنحسب الزمن بالثواني، وعليه سيعبَّر عن السرعات بوحدات لكل ثانية.
</p>

<p>
	يُعَدّ تحريك الكائنات أمرًا يسيرًا؛ أما التعامل مع التفاعلات التي يجب حدوثها بين العناصر فهو الأمر الصعب، فإذا اصطدم اللاعب بجدار أو أرضية، فلا يجب المرور من خلالها، بل تنتبه اللعبة إذا تسببت حركة ما في ارتطام كائن بآخر، ثم تتصرف وفق الحالة، فتتوقف الحركة مثلًا بالنسبة للجدران؛ أما إذا اصطدم بعملة ما فيجب جمعها، وإذا لمس حممًا بركانية فيجب خسارة اللعبة.
</p>

<p>
	لا شك أن هذه عملية معقدة إذا أردنا ضبط قوانينها، لذا ستجد مكتبات يُطلق عليها عادةً محركات فيزيائية physics engines تحاكي التفاعل بين الكائنات الفيزيائية في بعدين أو ثلاثة أبعاد، ولكن بأي حال سنلقي نظرةً عليها في هذا المقال لنتعرف إليها، حيث سنعالج التصادمات بين الكائنات المستطيلة فقط على أساس تدريب عملي عليها.
</p>

<p>
	ننظر أولًا قبل تحريك اللاعب أو كتلة الحمم هل تأخذه الحركة داخل جدار أم لا، فإذا أخذته؛ فإننا نلغي الحركة بالكلية. تعتمد الاستجابة لمثل ذلك التصادم على نوع الكائن الفاعل نفسه، فاللاعب مثلًا سيقف، بينما ترتد كتلة الحمم في الاتجاه المعاكس.
</p>

<p>
	يتطلب هذا الأسلوب أن تكون خطواتنا الزمنية صغيرةً إلى حد ما بما أنها ستوقف الحركة قبل أن تتلامس الكائنات، وإلا سنجد اللاعب يحوم لمسافة ملحوظة فوق الأرض إذا كانت الخطوات الزمنية كبيرةً -وخطوات الحركة بناءً عليها-، والأفضل هنا هو أن نجد نقطة التصادم بالتحديد وننتقل إليها، لكن هذا أعقد، لذا سنأخذ الأسلوب البسيط ونتلافى مشاكله بالتأكد من أن الحركة مستمرة في خطوات صغيرة.
</p>

<p>
	يخبرنا التابع التالي هل يلمس المستطيل -المحدَّد بموضع وحجم- عنصر شبكة من نوع ما أم لا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_53" style=""><span class="typ">Level</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">touches </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> size</span><span class="pun">,</span><span class="pln"> type</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let xStart </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x</span><span class="pun">);</span><span class="pln">
  let xEnd </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">ceil</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> size</span><span class="pun">.</span><span class="pln">x</span><span class="pun">);</span><span class="pln">
  let yStart </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">floor</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span><span class="pln">
  let yEnd </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">ceil</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> size</span><span class="pun">.</span><span class="pln">y</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let y </span><span class="pun">=</span><span class="pln"> yStart</span><span class="pun">;</span><span class="pln"> y </span><span class="pun">&lt;</span><span class="pln"> yEnd</span><span class="pun">;</span><span class="pln"> y</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let x </span><span class="pun">=</span><span class="pln"> xStart</span><span class="pun">;</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> xEnd</span><span class="pun">;</span><span class="pln"> x</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let isOutside </span><span class="pun">=</span><span class="pln"> x </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> x </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">width </span><span class="pun">||</span><span class="pln">
                      y </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0</span><span class="pln"> </span><span class="pun">||</span><span class="pln"> y </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">height</span><span class="pun">;</span><span class="pln">
      let here </span><span class="pun">=</span><span class="pln"> isOutside </span><span class="pun">?</span><span class="pln"> </span><span class="str">"wall"</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">rows</span><span class="pun">[</span><span class="pln">y</span><span class="pun">][</span><span class="pln">x</span><span class="pun">];</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">here </span><span class="pun">==</span><span class="pln"> type</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يحسب التابع مجموعة مربعات الشبكة التي يتداخل الجسم معها من خلال استخدام <code>Math.floor</code> و<code>Math.ceil</code> على إحداثياته، وتذكَّر أنّ مربعات الشبكة حجمها 1 * 1 وحدة، فإذا قربنا جوانب الصندوق لأعلى وأسفل سنحصل على مجال مربعات الخلفية التي يلمسها الصندوق.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81067" href="https://academy.hsoub.com/uploads/monthly_2021_10/game-grid.png.2103f37549c375ba0f63cf87355369a6.png" rel="" data-fileext="png"><img alt="game-grid.png" class="ipsImage ipsImage_thumbnailed" data-fileid="81067" data-unique="xt2df47u6" src="https://academy.hsoub.com/uploads/monthly_2021_10/game-grid.png.2103f37549c375ba0f63cf87355369a6.png"></a>
</p>

<p>
	سنمر حلقيًا على كتلة مربعات الشبكة التي خرجنا بها من الإحداثيات السابقة، ونعيد القيمة <code>true</code> إذا وجدنا مربعًا مطابِقًا. تُعامَل المربعات التي خارج المستوى على أساس جدار <code>"wall"</code> لضمان عدم خروج اللاعب من العالم الافتراضي، وأننا لن نقرأ أي شيء خارج حدود مصفوفتنا <code>rows</code> بالخطأ.
</p>

<p>
	تابع الحالة <code>update</code> يستخدِم <code>touches</code> لمعرفة هل لمس اللاعب حممًا بركانية أم لا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_59" style=""><span class="typ">State</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">update </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> keys</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let actors </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">actors
    </span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">actor </span><span class="pun">=&gt;</span><span class="pln"> actor</span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">,</span><span class="pln"> keys</span><span class="pun">));</span><span class="pln">
  let newState </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">State</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> actors</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">newState</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"playing"</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> newState</span><span class="pun">;</span><span class="pln">

  let player </span><span class="pun">=</span><span class="pln"> newState</span><span class="pun">.</span><span class="pln">player</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">(</span><span class="pln">player</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> player</span><span class="pun">.</span><span class="pln">size</span><span class="pun">,</span><span class="pln"> </span><span class="str">"lava"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">State</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> actors</span><span class="pun">,</span><span class="pln"> </span><span class="str">"lost"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let actor of actors</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">actor </span><span class="pun">!=</span><span class="pln"> player </span><span class="pun">&amp;&amp;</span><span class="pln"> overlap</span><span class="pun">(</span><span class="pln">actor</span><span class="pun">,</span><span class="pln"> player</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      newState </span><span class="pun">=</span><span class="pln"> actor</span><span class="pun">.</span><span class="pln">collide</span><span class="pun">(</span><span class="pln">newState</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> newState</span><span class="pun">;</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تُمرَّر كل من الخطوة الزمنية وهيكل البيانات إلى التابع، حيث يخبرنا هيكل البيانات بالمفاتيح المضغوط عليها حاليًا، ويستدعي التابعَ <code>update</code> على جميع الكائنات الفاعلة ليُنتج لنا مصفوفةً من نسخها المحدثة، كما تحصل الكائنات الفاعلة على الخطوة الزمنية والمفاتيح والحالة أيضًا لتتمكن من بناء تحديثها عليها، لكن تلك المفاتيح لا يقرؤها إلا اللاعب نفسه بما أنه هو الكائن الفاعل الوحيد الذي تتحكم به لوحة المفاتيح.
</p>

<p>
	إذا انتهت اللعبة، فلا يكون قد بقي شيء من المعالجة لفعله، أي أن اللعبة يستحيل أن تفوز بعد خسارتها أو العكس؛ أما إذا لم تنتهي، فسيختبر التابع هل لمس اللاعب حمم الخلفية أم لا، فإذا لمسها تخسر اللعبة وتنتهي.
</p>

<p>
	أخيرًا، إذا كانت اللعبة لا تزال قائمةً فسيرى هل تداخلت كائنات فاعلة أخرى مع اللاعب أم لا، ويُكتشف التداخل بين الكائنات الفاعلة باستخدام الدالة <code>overlap</code> التي تأخذ كائنين فاعلين وتعيد true إذا تلامسا فقط، وهي الحالة التي يتداخلا فيها على محوري x وy معًا.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_61" style=""><span class="kwd">function</span><span class="pln"> overlap</span><span class="pun">(</span><span class="pln">actor1</span><span class="pun">,</span><span class="pln"> actor2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> actor1</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> actor1</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&gt;</span><span class="pln"> actor2</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&amp;&amp;</span><span class="pln">
         actor1</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&lt;</span><span class="pln"> actor2</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> actor2</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">x </span><span class="pun">&amp;&amp;</span><span class="pln">
         actor1</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> actor1</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&gt;</span><span class="pln"> actor2</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&amp;&amp;</span><span class="pln">
         actor1</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">&lt;</span><span class="pln"> actor2</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> actor2</span><span class="pun">.</span><span class="pln">size</span><span class="pun">.</span><span class="pln">y</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	فإذا تداخل كائن فاعل، فسيحصل التابع <code>collide</code> الخاص به على فرصة لتحديث حالته، ويضبط لمس كائن الحمم حالة اللعبة إلى <code>"lost"</code>؛ أما العملات فستختفي حين نلمسها، وعند لمس العملة الأخيرة تُضبَط حالة اللعبة إلى <code>"won"</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_63" style=""><span class="typ">Lava</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">collide </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">State</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">actors</span><span class="pun">,</span><span class="pln"> </span><span class="str">"lost"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="typ">Coin</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">collide </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let filtered </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">actors</span><span class="pun">.</span><span class="pln">filter</span><span class="pun">(</span><span class="pln">a </span><span class="pun">=&gt;</span><span class="pln"> a </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">);</span><span class="pln">
  let status </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">status</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">filtered</span><span class="pun">.</span><span class="pln">some</span><span class="pun">(</span><span class="pln">a </span><span class="pun">=&gt;</span><span class="pln"> a</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"coin"</span><span class="pun">))</span><span class="pln"> status </span><span class="pun">=</span><span class="pln"> </span><span class="str">"won"</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">State</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> filtered</span><span class="pun">,</span><span class="pln"> status</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<h2>
	تحديثات الكائنات الفاعلة
</h2>

<p>
	تأخذ توابع <code>update</code> الخاصة بالكائنات الفاعلة الخطوة الزمنية وكائن الحالة وكائن <code>keys</code> على أساس وسائط لها، مع استثناء تابع الكائن الفاعل <code>Lava</code>، إذ يتجاهل كائن <code>keys</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_65" style=""><span class="typ">Lava</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">update </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let newPos </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">.</span><span class="pln">times</span><span class="pun">(</span><span class="pln">time</span><span class="pun">));</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">size</span><span class="pun">,</span><span class="pln"> </span><span class="str">"wall"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">(</span><span class="pln">newPos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">reset</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">reset</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">reset</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">reset</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Lava</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">.</span><span class="pln">times</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يحسب التابع <code>update</code> موضعًا جديدًا بإضافة ناتج الخطوة الزمنية والسرعة الحالية إلى الموضع القديم، فإذا لم تحجب ذلك الموضع الجديد أية عوائق فسينتقل إليه؛ أما إذا وُجد عائق فسيعتمد السلوك حينها على نوع كتلة الحمم، فالحمم المتساقطة لديها الموضع <code>reset</code> الذي تقفز إليه حين تصطدم بشيء ما؛ أما الحمم المرتدة، فتعكس سرعتها وتضربها في <code>‎-1</code> كي تبدأ بالحركة في الاتجاه المعاكس.
</p>

<p>
	تستخدِم العملات كذلك التابع <code>update</code> من أجل تأثير التمايل، فتتجاهل التصادمات مع الشبكة بما أنها تتمايل في المربع نفسه.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_67" style=""><span class="kwd">const</span><span class="pln"> wobbleSpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">8</span><span class="pun">,</span><span class="pln"> wobbleDist </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0.07</span><span class="pun">;</span><span class="pln">

</span><span class="typ">Coin</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">update </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let wobble </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">wobble </span><span class="pun">+</span><span class="pln"> time </span><span class="pun">*</span><span class="pln"> wobbleSpeed</span><span class="pun">;</span><span class="pln">
  let wobblePos </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">sin</span><span class="pun">(</span><span class="pln">wobble</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> wobbleDist</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Coin</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">basePos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> wobblePos</span><span class="pun">)),</span><span class="pln">
                  </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">basePos</span><span class="pun">,</span><span class="pln"> wobble</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تتزايد الخاصية <code>wobble</code> لتراقب الوقت، ثم تُستخدَم على أساس وسيط لـ <code>Math.sin</code> لإيجاد الموضع الجديد على الموجة، ثم يُحسب موضع العملة الحالي من موضعها الأساسي وإزاحة مبنية على هذه الموجة.
</p>

<p>
	يتبقى لنا اللاعب نفسه، إذ تُعالَج حركة اللاعب معالجةً مستقلةً لكل محور، ذلك أنه ينبغي على اصطدامه بالأرض ألا يمثِّل مشكلةً وألا يمنع الحركة الأفقية، كما أن الاصطدام بحائط لا يجب ألا يوقف حركة السقوط أو القفز.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_69" style=""><span class="kwd">const</span><span class="pln"> playerXSpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">7</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> gravity </span><span class="pun">=</span><span class="pln"> </span><span class="lit">30</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> jumpSpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">17</span><span class="pun">;</span><span class="pln">

</span><span class="typ">Player</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">update </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">,</span><span class="pln"> keys</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let xSpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">keys</span><span class="pun">.</span><span class="typ">ArrowLeft</span><span class="pun">)</span><span class="pln"> xSpeed </span><span class="pun">-=</span><span class="pln"> playerXSpeed</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">keys</span><span class="pun">.</span><span class="typ">ArrowRight</span><span class="pun">)</span><span class="pln"> xSpeed </span><span class="pun">+=</span><span class="pln"> playerXSpeed</span><span class="pun">;</span><span class="pln">
  let pos </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">pos</span><span class="pun">;</span><span class="pln">
  let movedX </span><span class="pun">=</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="pln">xSpeed </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">,</span><span class="pln"> </span><span class="lit">0</span><span class="pun">));</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">(</span><span class="pln">movedX</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">size</span><span class="pun">,</span><span class="pln"> </span><span class="str">"wall"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    pos </span><span class="pun">=</span><span class="pln"> movedX</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  let ySpeed </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">speed</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> time </span><span class="pun">*</span><span class="pln"> gravity</span><span class="pun">;</span><span class="pln">
  let movedY </span><span class="pun">=</span><span class="pln"> pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> ySpeed </span><span class="pun">*</span><span class="pln"> time</span><span class="pun">));</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">state</span><span class="pun">.</span><span class="pln">level</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">(</span><span class="pln">movedY</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">size</span><span class="pun">,</span><span class="pln"> </span><span class="str">"wall"</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    pos </span><span class="pun">=</span><span class="pln"> movedY</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">keys</span><span class="pun">.</span><span class="typ">ArrowUp</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> ySpeed </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ySpeed </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="pln">jumpSpeed</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    ySpeed </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Player</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="pln">xSpeed</span><span class="pun">,</span><span class="pln"> ySpeed</span><span class="pun">));</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تُحسب الحركة الأفقية وفقًا لحالة مفاتيح الأسهم اليمين واليسار، فإذا لم يكن هناك حائط يحجب الموضع الجديد الذي سينشأ بسبب تلك الحركة، فسيُستخدَم؛ وإلا نظل على الموضع القديم.
</p>

<p>
	كذلك الأمر بالنسبة للحركة الرأسية، لكن يجب محاكاة القفز والجاذبية، فتتزايد سرعة اللاعب الرأسية <code>ySpeed</code> أولًا لتعادل الجاذبية (تراجع)، ثم نتحقق من الحوائط مرةً أخرى، فإذا لم نصطدم بحائط فسنستخدم الموضع الجديد؛ أما إذا اصطدمنا بحائط فلدينا احتمالان، وهما إما أن يُضغط زر السهم الأعلى ونحن نتحرك إلى الأسفل، أي أن ما اصطدمنا به كان تحتنا، فتُضبط السرعة على قيمة كبيرة سالبة، وهذا يجعل اللاعب يقفز لأعلى، ويُعَد ما سوى ذلك اصطدام اللاعب بشيء في طريقه، وهنا تتغير السرعة إلى صفر؛ أما قوة الجاذبية وسرعة القفز وغيرها من الثوابت في تلك اللعبة، فتُضبط بالتجربة والخطأ، حيث اختبرنا القيم حتى وصلنا إلى قيم متوافقة مع بعضها بعضًا ومناسبة.
</p>

<h2>
	مفاتيح التعقب
</h2>

<p>
	لا نريد للمفاتيح أن تُحدِث تأثيرًا واحدًا لكل نقرة عليها، بل نريد أن يظل تأثيرها عاملًا طالما كان المفتاح مضغوطًا، وذلك مفيد في شأن لعبة مثل التي نكتبها من أجل إجراء ما مثل تحريك اللاعب.
</p>

<p>
	سنُعِدّ معالِج مفتاح يخزن الحالة الراهنة لمفاتيح الأسهم الأربعة، كما سنستدعي <code>preventDefault</code> لتلك المفاتيح كي لا تتسبب في تمرير الصفحة.
</p>

<p>
	تعيد الدالة في المثال أدناه كائنًا عند إعطائها مصفوفةً من أسماء المفاتيح، حيث يتعقب الموضع الحالي لتلك المفاتيح ويسجل معالجات أحداث للأحداث <code>"keydown"</code> و<code>"keyup"</code>، وإذا كانت شيفرة المفتاح التي في الحدث موجودةً في مجموعة الشيفرات التي تتعقبها، فستحدِّث الكائن.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_71" style=""><span class="kwd">function</span><span class="pln"> trackKeys</span><span class="pun">(</span><span class="pln">keys</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let down </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> track</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">keys</span><span class="pun">.</span><span class="pln">includes</span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">key</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      down</span><span class="pun">[</span><span class="pln">event</span><span class="pun">.</span><span class="pln">key</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">type </span><span class="pun">==</span><span class="pln"> </span><span class="str">"keydown"</span><span class="pun">;</span><span class="pln">
      event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"keydown"</span><span class="pun">,</span><span class="pln"> track</span><span class="pun">);</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"keyup"</span><span class="pun">,</span><span class="pln"> track</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> down</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> arrowKeys </span><span class="pun">=</span><span class="pln">
  trackKeys</span><span class="pun">([</span><span class="str">"ArrowLeft"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"ArrowRight"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"ArrowUp"</span><span class="pun">]);</span></pre>

<p>
	تُستخدَم الدالة المعالج نفسها لنوعي الأحداث، إذ تنظر في الخاصية <code>type</code> لكائن الحدث لتحدِّد هل يجب تحديث حالة المفتاح إلى القيمة true -أي <code>"keydown"</code>- أو القيمة false -أي <code>"keyup"</code>-.
</p>

<h2>
	تشغيل اللعبة
</h2>

<p>
	توفِّر الدالة <code>requestAnimationFrame</code> التي رأيناها في مقال نموذج كائن المستند في جافاسكريبت طريقةً جيدةً لتحريك اللعبة، لكن واجهتها بدائية للغاية، إذ نحتاج إلى تعقب الوقت الذي استدعيت فيه دالتنا في آخر مرة، ثم نستدعي <code>requestAnimationFrame</code> مرةً أخرى بعد كل إطار ولهذا سنعرِّف دالةً مساعدةً تغلّف هذه الأجزاء المملة في واجهة مريحة وعملية، وتسمح لنا باستدعاء <code>runAnimation</code> ببساطة لتعطيها دالةً تتوقع فرق الوقت على أساس وسيط وترسم إطارًا واحدًا، كما تتوقف الحركة إذا أعادت دالة الإطار القيمة <code>false</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_73" style=""><span class="kwd">function</span><span class="pln"> runAnimation</span><span class="pun">(</span><span class="pln">frameFunc</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let lastTime </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> frame</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lastTime </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let timeStep </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">min</span><span class="pun">(</span><span class="pln">time </span><span class="pun">-</span><span class="pln"> lastTime</span><span class="pun">,</span><span class="pln"> </span><span class="lit">100</span><span class="pun">)</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">;</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">frameFunc</span><span class="pun">(</span><span class="pln">timeStep</span><span class="pun">)</span><span class="pln"> </span><span class="pun">===</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    lastTime </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">;</span><span class="pln">
    requestAnimationFrame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  requestAnimationFrame</span><span class="pun">(</span><span class="pln">frame</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ضبطنا القيمة العظمى لخطوة الإطار لتكون مساويةً لـ 100 ميلي ثانية -أي عُشر ثانية-، فإذا أُخفيت نافذة المتصفح أو التبويب الذي فيه صفحتنا، فستتوقف استدعاءات <code>requestAnimationFrame</code> إلى أن يُعرَض التبويب أو النافذة مرةً أخرى، ويكون الفرق في هذه الحالة بين <code>lastTime</code> و<code>time</code> هو الوقت الكلي الذي أُخفيت فيه الصفحة.
</p>

<p>
	لا شك في أن تقدُّم اللعبة بذلك المقدار في خطوة واحدة سيكون سخيفًا وقد يسبب آثارًا جانبيةً غريبةً، كأن يسقط اللاعب داخل الأرضية.
</p>

<p>
	تحوِّل الدالة كذلك الخطوات الزمنية إلى ثواني، وهي أسهل في النظر إليها على أساس كمية عنها إذا كانت بالمللي ثانية، وتأخذ الدالة <code>runLevel</code> الكائن <code>Level</code> وتعرض بانيًا وتُعيد وعدًا، كما تعرض المستوى -في <code>document.body</code>-، وتسمح للمستخدِم باللعب من خلاله، وإذا انتهى المستوى بالفوز أو الخسارة، فستنتظر <code>runLevel</code> زمنًا قدره ثانيةً واحدةً إضافيةً ليتمكن المستخدِم من رؤية ما حدث، ثم تمحو العرض وتوقف التحريك، وتحل الوعد لحالة اللعبة النهائية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_75" style=""><span class="kwd">function</span><span class="pln"> runLevel</span><span class="pun">(</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Display</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let display </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Display</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> level</span><span class="pun">);</span><span class="pln">
  let state </span><span class="pun">=</span><span class="pln"> </span><span class="typ">State</span><span class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span class="pln">level</span><span class="pun">);</span><span class="pln">
  let ending </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    runAnimation</span><span class="pun">(</span><span class="pln">time </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      state </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> arrowKeys</span><span class="pun">);</span><span class="pln">
      display</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="str">"playing"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ending </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        ending </span><span class="pun">-=</span><span class="pln"> time</span><span class="pun">;</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        display</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
        resolve</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تتكون اللعبة من سلسلة من المستويات، بحيث يعاد المستوى الحالي إذا مات اللاعب، وإذا اكتمل مستوى، فسننتقل إلى المستوى التالي، ويمكن التعبير عن ذلك بالدالة التالية التي تأخذ مصفوفةً من أسطح المستويات (سلاسل نصية) وتعرض بانيًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_7180_77" style=""><span class="pln">async </span><span class="kwd">function</span><span class="pln"> runGame</span><span class="pun">(</span><span class="pln">plans</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Display</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let level </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> level </span><span class="pun">&lt;</span><span class="pln"> plans</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let status </span><span class="pun">=</span><span class="pln"> await runLevel</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Level</span><span class="pun">(</span><span class="pln">plans</span><span class="pun">[</span><span class="pln">level</span><span class="pun">]),</span><span class="pln">
                                </span><span class="typ">Display</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="str">"won"</span><span class="pun">)</span><span class="pln"> level</span><span class="pun">++;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"You've won!"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لأننا جعلنا <code>runLevel</code> تعيد وعدًا، فيمكن كتابة <code>runGame</code> باستخدام دالة <code>async</code> كما هو موضح في الحادي عشر، وهي تُعيد وعدًا آخرًا يُحَل عندما يُنهي اللاعب اللعبة.
</p>

<p>
	ستجد مجموعةً من أسطح المستويات متاحةً في رابطة <code>GAME_LEVELS</code> في <a href="https://eloquentjavascript.net/code#16" rel="external nofollow">صندوق الاختبارات الخاص بهذا المقال</a>، وتغذي تلك الصفحة المستويات إلى <code>runGame</code> لتبدأ اللعبة الحقيقية.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_79" style=""><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"css/game.css"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">
  </span><span class="tag">&lt;script&gt;</span><span class="pln">
    runGame</span><span class="pun">(</span><span class="pln">GAME_LEVELS</span><span class="pun">,</span><span class="pln"> </span><span class="typ">DOMDisplay</span><span class="pun">);</span><span class="pln">
  </span><span class="tag">&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span></pre>

<p>
	جرب بنفسك لترى ما إذا كنت تستطيع تجاوز هذه المستويات.
</p>

<h2>
	تدريبات
</h2>

<h3>
	انتهاء اللعبة
</h3>

<p>
	من المتعارف عليه في ألعاب الحاسوب أنّ اللاعب يبدأ بعدد محدود من فرص الحياة التي تنقص بمقدار حياة واحدة كلما مات في اللعبة، وإذا انتهت الفرص المتاحة، فستعيد اللعبة التشغيل من البداية.
</p>

<p>
	عدِّل <code>runGame</code> لتضع فيها خاصية الحيوات، واجعل اللاعب يبدأ بثلاثة حيوات، ثم أخرج عدد الحيوات الحالي باستخدام <code>console.log</code> في كل مرة يبدأ فيها مستوى.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_81" style=""><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"css/game.css"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
   </span><span class="com">// دالة runGame القديمة، عدّلها ...</span><span class="pln">
  async </span><span class="kwd">function</span><span class="pln"> runGame</span><span class="pun">(</span><span class="pln">plans</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Display</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let level </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> level </span><span class="pun">&lt;</span><span class="pln"> plans</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let status </span><span class="pun">=</span><span class="pln"> await runLevel</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Level</span><span class="pun">(</span><span class="pln">plans</span><span class="pun">[</span><span class="pln">level</span><span class="pun">]),</span><span class="pln">
                                  </span><span class="typ">Display</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="str">"won"</span><span class="pun">)</span><span class="pln"> level</span><span class="pun">++;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"You've won!"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  runGame</span><span class="pun">(</span><span class="pln">GAME_LEVELS</span><span class="pun">,</span><span class="pln"> </span><span class="typ">DOMDisplay</span><span class="pun">);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span></pre>

<h3>
	الإيقاف المؤقت للعبة
</h3>

<p>
	أضف خاصية الإيقاف المؤقت للعبة والعودة إليها من خلال مفتاح Esc، ويمكن تنفيذ هذا بتغيير دالة <code>runLevel</code> لتستخدِم معالِج حدث لوحة مفاتيح آخر، وتعترض الحركة أو تستعيدها كلما ضغط اللاعب على زر Esc.
</p>

<p>
	قد لا تبدو واجهة <code>runAnimation</code> مناسبةً لهذه الخاصية، لكنها ستكون كذلك إذا أعدت ترتيب الطريقة التي تستدعيها <code>runLevel</code> بها.
</p>

<p>
	إذا تمكنت من تنفيذ ذلك فثمة شيء آخر قد تستطيع فعله، ذلك أنّ الطريقة التي نسجل بها معالِجات الأحداث تسبب لنا مشكلة، فالكائن <code>arrowKeys</code> حاليًا هو رابطة عامة global binding، وتظل معالجات أحداثه باقيةً حتى لو لم تكن هناك لعبة تعمل، فتستطيع القول أنها تتسرب من نظامنا. وسِّع <code>trackKeys</code> من أجل توفير طريقة لتسجيل معالجاتها عندما تبدأ ثم تلغي تسجيلها مرةً أخرى عند انتهائها.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_83" style=""><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"css/game.css"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  </span><span class="com">// The old runLevel function. Modify this...</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> runLevel</span><span class="pun">(</span><span class="pln">level</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Display</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let display </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Display</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> level</span><span class="pun">);</span><span class="pln">
    let state </span><span class="pun">=</span><span class="pln"> </span><span class="typ">State</span><span class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span class="pln">level</span><span class="pun">);</span><span class="pln">
    let ending </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      runAnimation</span><span class="pun">(</span><span class="pln">time </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        state </span><span class="pun">=</span><span class="pln"> state</span><span class="pun">.</span><span class="pln">update</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> arrowKeys</span><span class="pun">);</span><span class="pln">
        display</span><span class="pun">.</span><span class="pln">syncState</span><span class="pun">(</span><span class="pln">state</span><span class="pun">);</span><span class="pln">
        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="str">"playing"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ending </span><span class="pun">&gt;</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          ending </span><span class="pun">-=</span><span class="pln"> time</span><span class="pun">;</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          display</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
          resolve</span><span class="pun">(</span><span class="pln">state</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">
          </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">});</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  runGame</span><span class="pun">(</span><span class="pln">GAME_LEVELS</span><span class="pun">,</span><span class="pln"> </span><span class="typ">DOMDisplay</span><span class="pun">);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<p>
	يمكن اعتراض الحركة بإعادة <code>false</code> من الدالة المعطاة لـ <code>runAnimation</code>، ويمكن متابعتها باستدعاء <code>runAnimation</code> مرةً أخرى، وهكذا سنحتاج إلى إبلاغ الدالة المعطاة لـ <code>runAnimation</code> أننا سنوقف اللعبة مؤقتًا؛ ولفعل هذا، استخدِم رابطةً يستطيع كل من معالج الحدث والدالة الوصول إليها.
</p>

<p>
	عند البحث عن طريقة لإلغاء تسجيل المعالجات المسجَّلة بواسطة <code>trackKeys</code>، تذكر أنّ قيمة الدالة الممررة نفسها إلى <code>addEventListener</code> يجب تمريرها إلى <code>removeEventListener</code> من أجل حذف معالج بنجاح، وعليه يجب أن تكون قيمة الدالة <code>handler</code> المنشأة في <code>trackKeys</code> متاحةً في الشيفرة التي تلغي تسجيل المعالِجات.
</p>

<p>
	تستطيع إضافة خاصية إلى الكائن المعاد بواسطة <code>trackKeys</code> تحتوي على قيمة الدالة أو على تابع يعالج إلغاء التسجيل مباشرةً.
</p>

<h3>
	الوحش
</h3>

<p>
	من الشائع أيضًا في ألعاب المنصة أن تحتوي على أعداء تستطيع القفز فوقها لتتغلب عليها، ويطلب منك هذا التدريب إضافة مثل نوع الكائن الفاعل ذلك إلى اللعبة.
</p>

<p>
	سنطلق عليه اسم الوحش وتتحرك تلك الوحوش أفقيًا فقط، كما تستطيع جعلها تتحرك في اتجاه اللاعب وتقفز للأمام والخلف مثل الحمم الأفقية، أو يكون لها أي نمط حركة تختاره، ولا تحتاج إلى جعل الصنف يعالج السقوط، لكن يجب التأكد من أن الوحش لا يسير خلال الجدران.
</p>

<p>
	يتوقف التأثير الواقع على اللاعب إذا لمسه أحد الوحوش بكون اللاعب يقفز فوق الوحش أم لا، ويمكنك تقريب الأمر بالتحقق من قاعدة اللاعب هل هي قريبة من قمة الوحش أم لا، فإذا كانت قريبةً فسيختفي الوحش، وإذا كانت بعيدةً فستخسر اللعبة.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_7180_85" style=""><span class="tag">&lt;link</span><span class="pln"> </span><span class="atn">rel</span><span class="pun">=</span><span class="atv">"stylesheet"</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"css/game.css"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;style&gt;</span><span class="pun">.</span><span class="pln">monster </span><span class="pun">{</span><span class="pln"> background</span><span class="pun">:</span><span class="pln"> purple </span><span class="pun">}</span><span class="tag">&lt;/style&gt;</span><span class="pln">

</span><span class="tag">&lt;body&gt;</span><span class="pln">
  </span><span class="tag">&lt;script&gt;</span><span class="pln">
    </span><span class="com">// أكمل التوابع التالية: constructor وupdate وcollide</span><span class="pln">
    </span><span class="kwd">class</span><span class="pln"> </span><span class="typ">Monster</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      constructor</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">,</span><span class="pln"> </span><span class="com">/* ... */</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

      </span><span class="kwd">get</span><span class="pln"> type</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="kwd">return</span><span class="pln"> </span><span class="str">"monster"</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">

      </span><span class="kwd">static</span><span class="pln"> create</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Monster</span><span class="pun">(</span><span class="pln">pos</span><span class="pun">.</span><span class="pln">plus</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">)));</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

      update</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">

      collide</span><span class="pun">(</span><span class="pln">state</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    </span><span class="typ">Monster</span><span class="pun">.</span><span class="pln">prototype</span><span class="pun">.</span><span class="pln">size </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Vec</span><span class="pun">(</span><span class="lit">1.2</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span><span class="pln">

    levelChars</span><span class="pun">[</span><span class="str">"M"</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Monster</span><span class="pun">;</span><span class="pln">

    runLevel</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Level</span><span class="pun">(`</span><span class="pln">
</span><span class="pun">..................................</span><span class="pln">
</span><span class="pun">.################################.</span><span class="pln">
</span><span class="pun">.#..............................#.</span><span class="pln">
</span><span class="pun">.#..............................#.</span><span class="pln">
</span><span class="pun">.#..............................#.</span><span class="pln">
</span><span class="pun">.#...........................</span><span class="pln">o</span><span class="pun">..#.</span><span class="pln">
</span><span class="pun">.#..@...........................#.</span><span class="pln">
</span><span class="pun">.##########..............########.</span><span class="pln">
</span><span class="pun">..........#..</span><span class="pln">o</span><span class="pun">..</span><span class="pln">o</span><span class="pun">..</span><span class="pln">o</span><span class="pun">..</span><span class="pln">o</span><span class="pun">..#........</span><span class="pln">
</span><span class="pun">..........#...........</span><span class="pln">M</span><span class="pun">..#........</span><span class="pln">
</span><span class="pun">..........################........</span><span class="pln">
</span><span class="pun">..................................</span><span class="pln">
</span><span class="pun">`),</span><span class="pln"> </span><span class="typ">DOMDisplay</span><span class="pun">);</span><span class="pln">
  </span><span class="tag">&lt;/script&gt;</span><span class="pln">
</span><span class="tag">&lt;/body&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<p>
	إذا أردت تنفيذ نوع حركة حالي stateful مثل الارتداد، فتأكد من تخزين الحالة المطلوبة في الكائن الفاعل، بأن تضمِّنها على أساس وسيط باني وتضيفها على أساس خاصية.
</p>

<p>
	تذكر أنّ <code>update</code> تعيد كائنًا جديدًا بدلًا من تغيير الكائن القديم، وابحث عن اللاعب في <code>state.actors</code> عند معالجة اصطدام ووازن موضعه مع موضع الوحش.
</p>

<p>
	للحصول على قاعدة اللاعب يجب عليك إضافة حجمه الرأسي إلى موضعه الرأسي، وسيمثل إنشاء حالة محدثة إما التابع <code>collide</code> الخاص بـ <code>Coin</code>، وهو ما يعني حذف الكائن الفاعل، أو ذلك الخاص بـ <code>Lava</code>، والذي سيغير الحالة إلى <code>"lost"</code> وفقًا لموضع اللاعب.
</p>

<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/16_game.html" rel="external nofollow">للفصل السادس عشر من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1354/" rel="">معالجة الأحداث في جافسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/advance/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-r1305/" rel="">مشروع بناء لغة برمجة خاصة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/game-development/%D9%84%D8%BA%D8%A7%D8%AA-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D9%84%D8%B9%D8%A7%D8%A8/" rel="">تعرف على أشهر لغات برمجة الألعاب</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%8A-%D9%84%D8%A8%D9%86%D8%A7%D8%A1-%D8%B1%D8%AC%D9%84-%D8%A2%D9%84%D9%8A-%D8%B1%D9%88%D8%A8%D9%88%D8%AA-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1244/" rel="">مشروع تطبيقي لبناء رجل آلي (روبوت) عبر جافاسكريبت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1355</guid><pubDate>Sun, 10 Oct 2021 15:00:00 +0000</pubDate></item><item><title>&#x62A;&#x62E;&#x632;&#x64A;&#x646; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; &#x645;&#x62D;&#x644;&#x64A;&#x64B;&#x627; &#x641;&#x64A; &#x627;&#x644;&#x645;&#x62A;&#x635;&#x641;&#x62D; &#x639;&#x628;&#x631; &#x642;&#x627;&#x639;&#x62F;&#x629; &#x627;&#x644;&#x628;&#x64A;&#x627;&#x646;&#x627;&#x62A; IndexedDB</title><link>https://academy.hsoub.com/programming/javascript/%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%85%D8%AD%D9%84%D9%8A%D9%8B%D8%A7-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%B9%D8%A8%D8%B1-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-indexeddb-r1337/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_10/615fd91f0a7ad_----IndexedDB.png.da8ca62824ddc7a9214fdef191a4b9b8.png" /></p>

<p>
	IndexedDB هي قاعدة بيانات مدمجة مع المتصفح، ولها ميزات أقوى بكثير من <a data-ss1635000182="1" data-ss1635001042="1" href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%AE%D8%B2%D9%8A%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%85%D8%AD%D9%84%D9%8A%D9%8B%D8%A7-%D9%81%D9%8A-%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1338/" rel="">الكائن localStorage</a>، أهمها:
</p>

<ul>
<li>
		تخزن أي نوع من القيم تقريبًا من خلال المفاتيح ذات الأنواع المختلفة.
	</li>
	<li>
		تدعم الإجرائيات المترابطة transactions لوثوقية أعلى.
	</li>
	<li>
		تدعم الاستعلامات عن المفاتيح ضمن مجالات، كما تدعم الوصول إلى المفتاح بالفهرس index.
	</li>
	<li>
		يمكن أن تخزّن بيانات أحجام أكبر بكثير مما يخزنه الكائن <code>localStorage</code>.
	</li>
</ul>
<p>
	إنّ القدرة التي تؤمّنها قاعدة البيانات هذه تفوق المطلوب في تطبيقات (خادم-عميل) التقليدية، فهي مصممة للتطبيقات التي تعمل دون اتصال offline، وذلك لتشترك مع تقنية عمال الخدمات ServiceWorkers وغيرها من التقنيات. تشرح <a data-ss1635000182="1" data-ss1635001042="1" href="https://www.w3.org/TR/IndexedDB" rel="external nofollow">توصيفات قاعدة البيانات IndexedDB</a> الواجهة الأصلية للتعامل مع القاعدة، وتتميز بأنها مقادة بالأحداث، كما يمكن أيضًا استخدام آلية <code>async/await</code> بمساعدة مُغلّف wrapper يعتمد على الوعود promise مثل المُغلّف <a data-ss1635000182="1" data-ss1635001042="1" href="https://github.com/jakearchibald/idb" rel="external nofollow">idb</a>، وعلى الرغم من أن هذه آلية مريحة، إلا أنها ليست مثاليةً، إذ لن يتمكن المغلف من استبدال الأحداث في كل الحالات، لذلك سنبدأ أولًا بالتعرف على الأحداث، ثم نتفهم قاعدة البيانات IndexedDb، ثم سنعود لاستخدام المُغلِّف.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		لكن أين توجد البيانات؟ تُخزّن البيانات من الناحية التقنية في المجلد الأساسي home directory للزائر، مع بقية إعدادات المتصفح والموسِّعات extensions، ولكل متصفح أو مستخدم -على مستوى نظام التشغيل- مخزنه المستقل الخاص.
	</p>
</blockquote>

<h2>
	الاتصال مع قاعدة البيانات
</h2>

<p>
	نحتاج أولًا إلى تأسيس اتصال مع قاعدة البيانات IndexedDB باستعمال التابع <code>open</code> قبل البدء بالعمل معها، ولهذا التابع الصيغة التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_6" style="">
<span class="pln">let openRequest </span><span class="pun">=</span><span class="pln"> indexedDB</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> version</span><span class="pun">);</span></pre>

<ul>
<li>
		<code>name</code>: قيمة نصية تشير إلى اسم قاعدة البيانات.
	</li>
	<li>
		<code>version</code>: قيمة صحيحة موجبة لنسخة قاعدة البيانات، وهي 1 افتراضيًا.
	</li>
</ul>
<p>
	قد توجد قواعد بيانات عديدة بأسماء مختلفة، لكنها تعود جميعها إلى نفس الأصل (نطاق/بروتوكول/منفذ)، ولا يمكن لمواقع الويب المختلفة الوصول إلى قواعد بيانات المواقع الأخرى.
</p>

<p>
	يعيد الاستدعاء الكائن <code>openRequest</code>، وينبغي علينا الاستماع إلى الأحداث المتعلقة به، وهي:
</p>

<ul>
<li>
		<code>success</code>: يقع عندما تكون قاعدة البيانات جاهزة، أو لما يتواجد كائن قاعدة بيانات ضمن <code>openRequest.result</code>، والذي علينا استخدامه في الاستدعاءات اللاحقة.
	</li>
	<li>
		<code>error</code>: يقع عند الإخفاق في إنشاء الاتصال مع قاعدة البيانات
	</li>
	<li>
		<code>upgradeneeded</code>: قاعدة البيانات جاهزة، لكن نسختها قديمة.
	</li>
</ul>
<p>
	تتميز IndexedDB بوجود آلية مدمجة فيها لتحديد نسخة تخطيطها schema versioning، والتي لا نراها في قواعد البيانات الموجودة في جهة الخادم، فهي قاعدة بيانات تعمل من جهة العميل وتخزّن بياناتها ضمن المتصفح، كما لا يستطيع المطورون الوصول إليها في أي وقت، لذا عندما يزور المستخدم موقع الويب بعد إطلاق نسخة جديدة من التطبيق، فلا بدّ من تحديث قاعدة البيانات، فإذا كانت نسخة قاعدة البيانات أقل من تلك التي يحملها الأمر <code>open</code>، فسيقع الحدث <code>upgradeneeded</code> الذي يوازن بين النسختين ويُحدِّث هياكل البيانات بما يناسب، كما يقع هذا الحدث أيضًا عندما تكون قاعدة البيانات غير موجودة (أي تكون نسختها -تقنيًا- "0")، وهذا ما يجعلنا قادرين على إجراء عملية التهيئة.
</p>

<p>
	لنفترض أننا قد أصدرنا النسخة الأولى من تطبيقنا، حيث يمكننا عندها تأسيس الاتصال مع قاعدة بيانات نسختها "1"، وتهيئتها بالاستفادة من معالج الحدث <code>upgradeneeded</code> بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_8" style="">
<span class="pln">let openRequest </span><span class="pun">=</span><span class="pln"> indexedDB</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"store"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">);</span><span class="pln">

openRequest</span><span class="pun">.</span><span class="pln">onupgradeneeded </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// يقع عندما لا يمتلك العميل قاعدة بيانات</span><span class="pln">
  </span><span class="com">// ...إنجاز التهيئة...</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

openRequest</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="str">"Error"</span><span class="pun">,</span><span class="pln"> openRequest</span><span class="pun">.</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

openRequest</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let db </span><span class="pun">=</span><span class="pln"> openRequest</span><span class="pun">.</span><span class="pln">result</span><span class="pun">;</span><span class="pln">
  </span><span class="com">// متابعة العمل مع قاعدة البيانات</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	ثم أصدرنا لاحقًا النسخة الثانية، عندها سنتمكن من تأسيس الاتصال مع النسخة "2" والتحديث بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_10" style="">
<span class="pln">let openRequest </span><span class="pun">=</span><span class="pln"> indexedDB</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"store"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span><span class="pln">

openRequest</span><span class="pun">.</span><span class="pln">onupgradeneeded </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// قاعدة البيانات أقل من النسخة 2 أو غير موجودة</span><span class="pln">
  let db </span><span class="pun">=</span><span class="pln"> openRequest</span><span class="pun">.</span><span class="pln">result</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">switch</span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">oldVersion</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// النسخة الموجودة من القاعدة</span><span class="pln">
    </span><span class="kwd">case</span><span class="pln"> </span><span class="lit">0</span><span class="pun">:</span><span class="pln">
      </span><span class="com">// لا قاعدة بيانات</span><span class="pln">
      </span><span class="com">// تهيئة</span><span class="pln">
    </span><span class="kwd">case</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln">
      </span><span class="com">// يمتلك المستخدم النسخة 1 من القاعدة</span><span class="pln">
      </span><span class="com">// تحديث</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	لاحظ أنه عندما تكون نسختنا الحالية هي "2"، فسيتضمن معالج الحدث <code>onupgradeneeded</code> شيفرةً تعالج حالة النسخة "0"، وهذا الأمر ملائم للمستخدمين الذين يزورون الصفحة للمرة الأولى ولا يمتلكون قاعدة بيانات، كما يتضمن شيفرةً تعالج وجود النسخة "1" لتحديثها.
</p>

<p>
	سيقع الحدث <code>openRequest.onsuccess</code> عندما ينتهي معالج الحدث <code>onupgradeneeded</code> بنجاح، وينجح تأسيس الاتصال مع قاعدة البيانات.
</p>

<p>
	لحذف قاعدة البيانات:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_12" style="">
<span class="pln">let deleteRequest </span><span class="pun">=</span><span class="pln"> indexedDB</span><span class="pun">.</span><span class="pln">deleteDatabase</span><span class="pun">(</span><span class="pln">name</span><span class="pun">)</span><span class="pln">
</span><span class="com">//العملية deleteRequest.onsuccess/onerror يتتبع الحدثان</span></pre>

<p>
	لا يمكن تأسيس اتصال مع قاعدة بيانات بنسخة أقدم إذا كانت النسخة الحالية لقاعدة بيانات المستخدم أعلى من النسخة التي نمررها للاستدعاء، أي نسخة القاعدة "3" ونحاول تأسيس اتصال مع النسخة "2" مثلًا، فسيتولد خطأ وسيقع الحدث <code>openRequest.onerror</code>. وعلى الرغم من ندرة حدوث هذا الأمر، إلا أنه قد يحصل عندما يحاول الزائر تحميل شيفرة JavaScript قديمة، مثل أن تكون من الذاكرة المؤقتة لخادم وكيل مثلًا، حيث ستكون الشيفرة قديمةً والقاعدة حديثة. ولا بدّ من التحقق من نسخة قاعدة البيانات <code>db.version</code>، واقتراح إعادة تحميل الصفحة إذا أردنا الحماية من الأخطاء، كما نستخدم ترويسة HTTP ملائمةً للتعامل مع الذاكرة المؤقتة لتفادي تحميل شيفرة قديمة، وبالتالي لن نواجه مشاكل.
</p>

<h2>
	مشكلة التحديث المتوازي Parallel update
</h2>

<p>
	ما دمنا نتكلم عن تحديد نسخة التطبيق، فسنتطرق إلى مشكلة صغيرة مرتبطة بذلك، لنتأمل الحالة التالية:
</p>

<ol>
<li>
		فتح مستخدم موقعنا في نافذة متصفح، وكانت نسخة قاعدة البيانات هي "1".
	</li>
	<li>
		ثم حدّثنا الصفحة وأصبحت الشيفرة أحدث.
	</li>
	<li>
		ثم فتح المستخدم نفسه موقعنا في نافذة أخرى.
	</li>
</ol>
<p>
	أي ستكون هناك نافذة متصلة بقاعدة بيانات نسختها "1"، بينما تحاول النافذة الأخرى تحديثها إلى النسخة "2" عبر معالج الحدث <code>upgradeneeded</code>. تتلخص المشكلة بأن قاعدة البيانات مشتركة بين نافذتين، لأنهما تعودان لنفس الموقع ولهما الأصل ذاته، ولا يمكن أن تكونا من النسختين "1" و"2" في نفس الوقت، ولتنفيذ عملية الانتقال إلى النسخة "2"، ينبغي إغلاق كل قنوات الاتصال مع النسخة "1" بما فيها قناة اتصال النافذة الأولى، ولتنظيم ذلك سيقع الحدث <code>versionchange</code> ضمن كائن قاعدة البيانات "المنتهية الصلاحية"، لذا يفترض الاستماع لهذا الحدث، وإغلاق اتصال قاعدة البيانات القديمة. يمكن اقتراح إعادة تحميل الصفحة للحصول على الشيفرة الأحدث، فإذا لم نستمع إلى الحدث <code>versionchange</code> ولم نغلق قناة الاتصال، فلن يُنفَّذ الاتصال الثاني، وسيعطي الكائن <code>openRequest</code> الحدث <code>blocked</code> بدلًا من <code>success</code> ولن تعمل النافذة الثانية.
</p>

<p>
	إليك الشيفرة التي تتعامل بشكل صحيح مع التحديث المتوازي، إذ تثبّت معالج الحدث <code>onversionchange</code> الذي يقع عندما يصبح الاتصال الحالي مع قاعدة البيانات منتهي الصلاحية، أي عندما تُحدَّث نسخة قاعدة البيانات في مكان آخر، ويغلق الاتصال.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_14" style="">
<span class="pln">let openRequest </span><span class="pun">=</span><span class="pln"> indexedDB</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"store"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span><span class="pln">

openRequest</span><span class="pun">.</span><span class="pln">onupgradeneeded </span><span class="pun">=</span><span class="pln"> </span><span class="pun">...;</span><span class="pln">
openRequest</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="pun">...;</span><span class="pln">

openRequest</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let db </span><span class="pun">=</span><span class="pln"> openRequest</span><span class="pun">.</span><span class="pln">result</span><span class="pun">;</span><span class="pln">

  db</span><span class="pun">.</span><span class="pln">onversionchange </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
    alert</span><span class="pun">(</span><span class="str">"Database is outdated, please reload the page."</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">

  </span><span class="com">// ...قاعدة البيانات جاهزة! استخدمها...</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

openRequest</span><span class="pun">.</span><span class="pln">onblocked </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

</span><span class="pun">};</span></pre>

<p>
	إن ما نفعله هنا بعبارة أخرى هو:
</p>

<ol>
<li>
		يُعلمنا المستمِع إلى الحدث <code>db.onversionchange</code> عن محاولة التحديث المتوازي، عندما تصبح النسخة الحالية لقاعدة البيانات منتهية الصلاحية.
	</li>
	<li>
		يُعلمنا المستمِع إلى الحدث <code>openRequest.onblocked</code> عن الحالة المعاكسة، وهي وجود اتصال لم يُغلَق بعد مع نسخة منتهية الصلاحية في نافذة ما، وبالتالي لن يعمل الاتصال الجديد.
	</li>
</ol>
<p>
	يمكن أن نتعامل مع الموضوع بطريقة ألطف عند استخدام الحدث <code>db.onversionchange</code> الذي يبلغ المستخدم بوجوب حفظ بياناته قبل قطع الاتصال، يمكن أيضًا اعتماد مقاربة أخرى لا تتعلق بإغلاق الاتصال مع قاعدة البيانات في معالج الحدث <code>db.onversionchange</code>، بل باستخدام معالج الحدث <code>onblocked</code> -في نافذة متصفح أخرى- لتنبيه المستخدم بأن النسخة الجديدة لن تُحمَّل قبل إغلاق النافذة الأخرى.
</p>

<p>
	لا يحدث التعارض في التحديث إلا نادرًا، ومع ذلك لا بدّ من توقعه والتعامل معه، على الأقل باستخدام معالج الحدث <code>onblocked</code> لمنع انهيار السكربت.
</p>

<h2>
	مخزن الكائن
</h2>

<p>
	نحتاج إلى مخزن الكائنات Object Store لتخزين أي شيء في قاعدة البيانات IndexedDB، وهو مفهوم جوهري فيها، ويقابل مفهوم "الجداول tables" أو "المجموعات collections" في قواعد البيانات الأخرى، كما تُخزّن فيه البيانات. وقد تتكون القاعدة من عدة مخازن، يكون الأول فيها للمستخدمين، والثاني للبضائع مثلًا وغيرها.
</p>

<p>
	على الرغم من اسم "مخزن الكائن" إلا أنه يمكن تخزين القيم الأولية فيه بالإضافة إلى الكائنات، إذ يمكن تخزين أي قيمة بما فيها الكائنات المعقدة، وتستخدم قاعدة البيانات <a data-ss1635000182="1" data-ss1635001042="1" href="https://www.w3.org/TR/html53/infrastructure.html#section-structuredserializeforstorage" rel="external nofollow">خوارزمية التفكيك المعيارية standard serialization algorithm</a> لنسخ وتخزين الكائن، ويشابه ذلك استخدام الأمر <code>JSON.stringify</code> لكن مع إمكانيات أكثر، وقدرة على تخزين أنواع أكثر من البيانات.
</p>

<p>
	ومن الكائنات التي لا يمكن تخزينها في هذه المخازن، نجد الكائن ذو المراجع الحلقية التي تشكل حلقةً يدل آخرها على أولها، والتي لا يمكن أن تُفكك، وسيفشل الأمر <code>JSON.stringify</code> معها.
</p>

<p>
	يجب أن يكون لكل قيمة ستُخزّن في قاعدة البيانات مفتاح فريد <code>key</code>، كما يجب أن تكون هذه المفاتيح من أحد الأنواع التالية: رقم أو تاريخ أو نص أو قيمة ثنائية أو مصفوفة، وسنتمكن من البحث عن القيم أو إزالتها أو تحديثها بواسطة هذه المفاتيح الفريدة.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="79128" data-ss1635000182="1" data-ss1635001042="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/object_store_01.png.3525fd84f5d9aae77e72c6dd81fe7e82.png" rel=""><img alt="object_store_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79128" data-unique="iula19vwx" src="https://academy.hsoub.com/uploads/monthly_2021_10/object_store_01.png.3525fd84f5d9aae77e72c6dd81fe7e82.png"></a>
</p>

<p>
	سنرى قريبًا كيف سنحصل على مفتاح عند إضافة قيمة جديدة إلى المخزن، كما يحدث عند استخدام الكائن <code>localStorage</code>، لكن ستسمح لنا قاعدة البيانات IndexedDB عند تخزين كائن؛ بضبط وإعداد خصائصه مثل مفتاح، وهذه مقاربة أفضل بكثير، كما يمكننا توليد المفاتيح تلقائيًا، لكننا سنحتاج إلى إنشاء مخزن للكائن أولًا.
</p>

<p>
	إليك الصيغة المستخدمة في إنشاء مخزن لكائن:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_16" style="">
<span class="pln">db</span><span class="pun">.</span><span class="pln">createObjectStore</span><span class="pun">(</span><span class="pln">name</span><span class="pun">[,</span><span class="pln"> keyOptions</span><span class="pun">]);</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		<strong>ملاحظة</strong>: العملية متزامنة ولا تحتاج إلى التعليمة <code>await</code>.
	</p>
</blockquote>

<ul>
<li>
		<code>name</code>: وتمثل اسم المخزن.
	</li>
	<li>
		<code>keyOptions</code>: وتمثل كائنًا اختياريًا له خاصيتان، هما:
	</li>
	<li>
		<code>keyPath</code>: المسار إلى خاصية الكائن التي سنستخدمها مفتاحًا، مثل <code>id</code>.
	</li>
	<li>
		<code>autoIncrement</code>: إذا كانت قيمته <code>true</code> فسيتولد تلقائيًا مفتاح للكائن الجديد، مثل رقم يتزايد باستمرار.
	</li>
</ul>
<p>
	إذا لم نستخدم <code>keyOptions</code>، فلا بدّ حينها من التصريح عن المفتاح لاحقًا عند تخزين الكائن.
</p>

<p>
	يستخدم مخزن الكائن التالي الخاصية <code>id</code> مفتاحًا، وبشكل صريح:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_18" style="">
<span class="pln">db</span><span class="pun">.</span><span class="pln">createObjectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">keyPath</span><span class="pun">:</span><span class="pln"> </span><span class="str">'id'</span><span class="pun">});</span></pre>

<p>
	لا يمكن إنشاء أو تعديل مخزن كائن عند تحديث نسخة قاعدة البيانات إلا ضمن معالج الحدث <code>upgradneeded</code>، ويشكل هذا الأمر محدوديةً تقنيةً، إذ يمكن إضافة أو إزالة أو تحديث البيانات خارج المعالج، بينما لا يمكننا إنشاء أو حذف أو تغيير مخزن الكائن إلا خلال تحديث نسخة قاعدة البيانات. وتوجد مقاربتان لتنفيذ عملية تحديث نسخة قاعدة البيانات:
</p>

<ol>
<li>
		يمكن كتابة دوال خاصة لتحديث كل نسخة ممكنة إلى النسخة الجديدة، من "1" إلى "2"، ومن "2" إلى "3" وهكذا، ثم يمكن موازنة النسخ ضمن جسم دالة معالج الحدث <code>upgradeneeded</code> من القديمة 2 إلى الحديثة 4 مثلًا، وتنفيذ الدالة المناسبة لتنفيذ التحديث خطوةً بخطوة، أي من 2 إلى 3، ثم من 3 إلى 4.
	</li>
	<li>
		فحص قاعدة البيانات والحصول على قائمة بمخازن الكائنات الموجودة باستخدام التابع <code>db.objectStoreNames</code>، ويمثل الكائن المُعاد قائمةً من النوع <a data-ss1635000182="1" data-ss1635001042="1" href="https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#domstringlist" rel="external nofollow">DOMStringList</a>، والذي يزودنا بالتابع <code>(contains(name</code> الذي يتحقق من وجود مخزن باسم محدد، ثم سنتمكن من إجراء التحديث اعتمادًا على ما هو موجود وما هو غير موجود، وهذه المقاربة أبسط لقواعد البيانات الصغيرة، وإليك مثالًا عن استخدامها:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_20" style="">
<span class="pln">let openRequest </span><span class="pun">=</span><span class="pln"> indexedDB</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"db"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">2</span><span class="pun">);</span><span class="pln">

</span><span class="com">// تحديث وإضافة قواعد البيانات دون تحقق</span><span class="pln">
openRequest</span><span class="pun">.</span><span class="pln">onupgradeneeded </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let db </span><span class="pun">=</span><span class="pln"> openRequest</span><span class="pun">.</span><span class="pln">result</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">db</span><span class="pun">.</span><span class="pln">objectStoreNames</span><span class="pun">.</span><span class="pln">contains</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> 
      </span><span class="com">//  "books" إن لم يكن هناك مخزن باسم </span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">createObjectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">keyPath</span><span class="pun">:</span><span class="pln"> </span><span class="str">'id'</span><span class="pun">});</span><span class="pln"> </span><span class="com">// أنشئه</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	ولحذف مخزن كائن:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_22" style="">
<span class="pln">db</span><span class="pun">.</span><span class="pln">deleteObjectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">)</span></pre>

<h2>
	الإجرائيات المترابطة Transactions
</h2>

<p>
	يُعَد مصطلح "إجرائيات مترابطة" مصطلحًا عامًا، ويُستخدم في قواعد بيانات عديدة، كما يشير إلى مجموعة من العمليات التي ينبغي أن تُنفَّذ مترابطةً، بحيث تنجح معًا أو تخفق معًا، فعندما يشتري شخص شيئًا ما مثلًا، فعلينا:
</p>

<ol>
<li>
		سحب المبلغ من حساب المشتري.
	</li>
	<li>
		إضافة المشتريات إلى سلة مشترياته.
	</li>
</ol>
<p>
	سيبدو الأمر سيئًا بالطبع لو أُنجزت الخطوة الأولى بنجاح وفشلت الخطوة الثانية لسبب أو لآخر، فإما أن تنجح الخطوتان -أي يكتمل الشراء- أو تخفق الخطوتان، فعندها لن يخسر المشتري ماله ويستطيع إعادة المحاولة مجددًا.
</p>

<p>
	ينبغي أن تُنفَّذ جميع العمليات على البيانات ضمن إجرائيات مترابطة في القاعدة IndexedDB، وللبدء بإجرائية مترابطة يجب تنفيذ الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_24" style="">
<span class="pln">db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="pln">store</span><span class="pun">[,</span><span class="pln"> type</span><span class="pun">]);</span></pre>

<ul>
<li>
		<code>store</code>: اسم المخزن الذي ستُنفَّّذ عليه إجرائية مترابطة، المخزن "books" مثلًا، ويمكن أن يكون مصفوفةً من أسماء المخازن إذا كنا نريد الوصول إلى عدة مخازن.
	</li>
	<li>
		<code>type</code>: نوع الإجرائية المترابطة، وقد يكون:
	</li>
	<li>
		<code>readonly</code>: يُنفِّذ عمليات قراءة فقط، وهذا هو الخيار الافتراضي.
	</li>
	<li>
		<code>readwrite</code>: يُنفِّذ عمليات قراءة وكتابة فقط، ولا يستطيع إنشاء أو حذف أو تبديل الكائن.
	</li>
</ul>
<p>
	كما يمكن تنفيذ إجرائيات مترابطة من النوع <code>versionchange</code>، ويستطيع هذا النوع تنفيذ أي شيء، لكن لا يمكن إنشاؤه يدويًا، إذ تنشئ قاعدة البيانات الإجرائيات المترابطة من النوع <code>versionchange</code> عند تأسيس الاتصال مع قاعدة البيانات لتنفيذ معالج الحدث <code>updateneeded</code>، لذا فهو المكان الوحيد الذي نستطيع فيه تحديث هيكلية قاعدة البيانات أو إنشاء وحذف كائن المخزن.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		<strong>لماذا توجد ثلاثة أنواع مختلفة من الإجرائيات المترابطة؟</strong> يُحدد الأداء سبب وجود أنواع مختلفة من الإجرائيات المترابطة، إذ يمكن للعديد من الإجرائيات المترابطة من النوع <code>readonly</code> الوصول إلى نفس المخزن في نفس الوقت، لكن النوع <code>readwrite</code> غير قادر على ذلك، حيث يُقفل المخزن للكتابة ضمنه، ولا بدّ أن تنتظر الإجرائية المترابطة التالية حتى تنتهي الإجرائية السابقة.
	</p>
</blockquote>

<p>
	يمكن إضافة العناصر إلى المخزن بعد إنشاء الإجرائية المترابطة بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_26" style="">
<span class="pln">let transaction </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"readwrite"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// (1)</span><span class="pln">

</span><span class="com">// الحصول على كائن المخزن للتعامل معه</span><span class="pln">
let books </span><span class="pun">=</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// (2)</span><span class="pln">

let book </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  id</span><span class="pun">:</span><span class="pln"> </span><span class="str">'js'</span><span class="pun">,</span><span class="pln">
  price</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln">
  created</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

let request </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">book</span><span class="pun">);</span><span class="pln"> </span><span class="com">// (3)</span><span class="pln">

request</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// (4)</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Book added to the store"</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">result</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

request</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Error"</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تُنفَّذ العملية مبدئيًا وفق خطوات أربع هي:
</p>

<ol>
<li>
		إنشاء إجرائية مترابطة تشير إلى كل المخازن الذي سنصل إليها.
	</li>
	<li>
		الحصول على كائن المخزن باستخدام الأمر <code>(transaction.objectStore(name</code>.
	</li>
	<li>
		تنفيذ العمليات على كائن المخزن <code>(books.add(book</code>.
	</li>
	<li>
		التعامل مع حالة نجاح أو فشل الإجرائية المترابطة على كائن المخزن <code>(books.add(book</code>.
	</li>
</ol>
<p>
	تدعم كائنات المخازن تابعين لتخزين القيم، هما:
</p>

<ul>
<li>
		<code>([put(value,[key</code>: حيث يضيف القيمة <code>value</code> إلى المخزن، ويزوَّد التابع بالمعامل <code>key</code> فقط في الحالة التي لا يمتلك فيها المخزن أحد الخيارين <code>keyPath</code> أو <code>autoIncrement</code>، فإن وجدت قيمة لها نفس المفتاح فستُستبدَل.
	</li>
	<li>
		<code>([add(value,[key</code>: يشابه التابع السابق، لكن إن وجدت قيمة لها نفس المفتاح فسيخفق الطلب، وسيولِّد خطأً باسم <code>"ConstraintError"</code>.
	</li>
</ul>
<p>
	يمكن إرسال الطلب <code>(books.add(book</code> مثلًا بما يتناسب مع تأسيس اتصال مع قاعدة بيانات، ومن ثم ننتظر وقوع أحد الحدثين <code>success/error</code>.
</p>

<ul>
<li>
		سيكون مفتاح الكائن الجديد هو نتيجة الطلب <code>request.result</code> للتابع <code>add</code>.
	</li>
	<li>
		فإن وقع خطأ ما فسنجده في الكائن <code>request.error</code>.
	</li>
</ul>
<h2>
	اكتمال الإجراءات المترابطة
</h2>

<p>
	بدأنا الإجرائية المترابطة في المثال السابق بتنفيذ الطلب <code>add</code>، لكن -وكما أشرنا سابقًا- قد تتألف الإجرائية المترابطة من عدة طلبات ينبغي أن تنجح معًا أو تخفق معًا، فكيف سنميِّز إذًا انتهاء الإجرائية ولا توجد طلبات أخرى قيد التنفيذ؟ الجواب باختصار هو أننا لا نستطيع، ولا بدّ من وجود طريقة يدوية لإنهاء الإجرائيات المترابطة في النسخة التالية 3.0 من التوصيفات، لكن حاليًا في النسخة 2.0 لا يوجد شيء مشابه لهذا. وستكتمل الإجرائية تلقائيًا عندما تنتهي جميع الطلبات المتعلقة بإجرائية مترابطة، وسيصبح صف المهام المتناهية الصغر <a data-ss1635000182="1" data-ss1635001042="1" href="https://javascript.info/microtask-queue" rel="external nofollow">microtasks queue</a> فارغًا.
</p>

<p>
	توصف الإجرائية عادةً بأنها مكتملة عندما تكتمل كل طلباتها، وينتهي تنفيذ الشيفرة المتعلقة بها، لذا فلا حاجة في الشيفرة السابقة مثلًا إلى استدعاء خاص لإنهاء الإجرائية المترابطة. ويترافق مبدأ الاكتمال التلقائي للإجرائيات المترابطة بتأثير جانبي مهم، فلا يمكن تنفيذ عملية غير متزامنة مثل <code>fetch</code> أو <code>setTimeout</code> أثناء تنفيذ الإجرائية، كما لن تبقي قاعدة البيانات IndexedDB الإجرائية المترابطة في حالة انتظار حتى تُنجز هذه العملية.
</p>

<p>
	سيُخفق الطلب <code>request2</code> في السطر(*) من الشيفرة التالية لأن الإجرائية قد اكتملت بالفعل، ولن نتمكن من تنفيذ طلبات أخرى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_28" style="">
<span class="pln">let request1 </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">book</span><span class="pun">);</span><span class="pln">

request1</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  fetch</span><span class="pun">(</span><span class="str">'/'</span><span class="pun">).</span><span class="pln">then</span><span class="pun">(</span><span class="pln">response </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let request2 </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">anotherBook</span><span class="pun">);</span><span class="pln"> </span><span class="com">// (*)</span><span class="pln">
    request2</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">request2</span><span class="pun">.</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name</span><span class="pun">);</span><span class="pln"> </span><span class="com">// TransactionInactiveError</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	لأنّ <code>fetch</code> عملية غير متزامنة وتمثل مهمةً مستقلةً macrotask، وستُغلَق الإجرائية المترابطة قبل أن يبدأ المتصفح بتنفيذ مهمات مستقلة، كما سيرى محررو توصيفات قاعدة البيانات IndexedDB أنّ مدة إنجاز الإجرائيات المترابطة لا بدّ أن تكون قصيرة، وذلك لأسباب تتعلق بالأداء في الغالب.
</p>

<p>
	تقفِل الإجرائيات المترابطة من النوع <code>readwrite</code> المخزن عند الكتابة فيه، لذا إذا حاول جزء آخر من التطبيق تنفيذ عملية مشابهة على نفس المخزن، فعليه الانتظار. ستعلَّق الإجرائية المترابطة الجديدة حتى تنتهي الأولى، وهذا ما يسبب تأخيرًا غريبًا إذا استغرقت إجرائية مترابطة ما وقتًا طويلًا، فما العمل إذًا؟
</p>

<p>
	بإمكاننا في المثال السابق تنفيذ إجرائية مترابطة جديدة <code>db.transaction</code> تمامًا قبل الطلب الجديد في السطر "(*)"، لكن يفضَّل -إن أردنا إبقاء العمليات معًا في إجرائية مترابطة واحدة- أن نفصل الإجرائية المترابطة في قاعدة البيانات IndexedDB عن الأمور الأخرى غير المتزامنة.
</p>

<p>
	نفِّذ العملية <code>fetch</code> أولًا، ثم حضر البيانات إن تطلب الأمر ذلك، ثم أنشئ إجرائيةً مترابطةً، ونفّذ كل الطلبات وسينجح الأمر. استمع إلى الحدث <code>transaction.oncomplete</code> لمعرفة اللحظة التي تكتمل فيها الإجرائية المترابطة بنجاح.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_30" style="">
<span class="pln">let transaction </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"readwrite"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// ...perform operations...</span><span class="pln">

transaction</span><span class="pun">.</span><span class="pln">oncomplete </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Transaction is complete"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	تضمن الخاصية <code>complete</code> فقط اكتمال وحفظ الإجرائية بالكامل، وقد تنجح طلبات بمفردها، لكن قد تفشل بالمقابل عملية الكتابة النهائية، بسبب خطأ في منظومة الدخل/خرج مثلًا.
</p>

<p>
	استدعي التابع التالي لإيقاف الإجرائية المترابطة يدويًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_32" style="">
<span class="pln">transaction</span><span class="pun">.</span><span class="pln">abort</span><span class="pun">();</span></pre>

<p>
	سيلغي هذا الاستدعاء كل التغييرات التي نفذتها الطلبات، ويتسبب بوقوع الحدث <code>transaction.onabort</code>.
</p>

<h2>
	معالجة الأخطاء
</h2>

<p>
	قد تُخفق طلبات الكتابة، ولا بدّ من توقع هذا الأمر -لا نتيجةً للأخطاء المحتملة التي قد نرتكبها فقط- بل لأسباب تتعلق بالإجرائيات المترابطة بحد ذاتها، فقد يحدث تجاوز حجم المخزن المحدد على سبيل المثال، لذا لا بدّ أن نكون مستعدين للتعامل مع حالات كهذه.
</p>

<p>
	يوقف إخفاق الطلب الإجرائية المترابطة تلقائيًا ويلغي كل التغييرات التي حدثت، لكننا قد نحتاج إلى التعامل مع حالة إخفاق الطلب، لتجريب طلب آخر مثلًا، دون إلغاء التغييرات التي حدثت، ومن ثم متابعة الإجرائية المترابطة، وهذا أمر ممكن، إذ يمكن لمعالج الحدث <code>request.onerror</code> أن يمنع إلغاء الإجرائية المترابطة عن طريق استدعاء التابع <code>()event.preventDefault</code>.
</p>

<p>
	سنرى في المثال التالي كيف يُضاف كتاب جديد بمفتاح <code>id</code> موجود مسبقًا، وعندها سيولِّد التابع <code>store.add</code> الخطأ <code>"ConstraintError"</code>، الذي نتعامل معه دون إلغاء الإجرائية المترابطة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_35" style="">
<span class="pln">let transaction </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"readwrite"</span><span class="pun">);</span><span class="pln">

let book </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="str">'js'</span><span class="pun">,</span><span class="pln"> price</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pln"> </span><span class="pun">};</span><span class="pln">

let request </span><span class="pun">=</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">).</span><span class="pln">add</span><span class="pun">(</span><span class="pln">book</span><span class="pun">);</span><span class="pln">

request</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">//عند إضافة قيمة بمفتاح موجود مسبقًا ConstraintError يقع الخطأ</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">==</span><span class="pln"> </span><span class="str">"ConstraintError"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Book with such id already exists"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// التعامل مع الخطأ</span><span class="pln">
    event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln"> </span><span class="com">// لا تلغ الإجرائية المترابطة</span><span class="pln">
    </span><span class="com">// استخدم مفتاح جديد للكتاب؟</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// خطأ غير متوقع لايمكن التعامل معه</span><span class="pln">
    </span><span class="com">// ستلغى الإجرائية المترابطة</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

transaction</span><span class="pun">.</span><span class="pln">onabort </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Error"</span><span class="pun">,</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<h2>
	تفويض الأحداث
</h2>

<p>
	هل نحتاج إلى الحدثين <code>onsuccess/onerror</code> عند كل طلب؟ والجواب هو لا، ليس في كل مرة، إذ يمكننا أن نستعمل تفويضًا للحدث event delegation بدلًا من ذلك.
</p>

<p>
	تجري عملية انسياب Bubbling الحدث في قاعدة بيانات بالشكل التالي:<br>
	 
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_8038_39" style="">
<span class="pln">request </span><span class="pun">→</span><span class="pln"> transaction </span><span class="pun">→</span><span class="pln"> database
</span></pre>

<p>
	 والتي تقتضي التقاط العنصر الداخلي ضمن شجرة DOM للحدث، ثم تستمع إليه الأحداث الخارجية بالتتالي.
</p>

<p>
	قد تنساب الأحداث في الشجرة DOM للخارج bubbling أو للداخل capturing، لكن يُستخدم عادةً الانسياب نحو الخارج، ويمكن حينها التقاط الأخطاء عن طريق معالج الحدث <code>db.onerror</code> لإظهارها أو لأي أسباب أخرى.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_37" style="">
<span class="pln">db</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let request </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">;</span><span class="pln"> </span><span class="com">// الطلب الذي ولّد الخطأ</span><span class="pln">

  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Error"</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">error</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	لكن ماذا لو تمكنا من التعامل مع الخطأ كاملًا؟ عندها لن نحتاج لإظهار أي شيء، يمكننا إيقاف الانسياب الخارجي للأحداث، وبالتالي إيقاف الحدث <code>db.onerror</code>، باستخدام الأمر <code>()event.stopPropagation</code> ضمن دالة معالجة الحدث <code>request.onerror</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_41" style="">
<span class="pln">request</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">error</span><span class="pun">.</span><span class="pln">name </span><span class="pun">==</span><span class="pln"> </span><span class="str">"ConstraintError"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Book with such id already exists"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// معالجة الخطأ</span><span class="pln">
    event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln"> </span><span class="com">// لا توقف الإجرائية المترابطة</span><span class="pln">
    event</span><span class="pun">.</span><span class="pln">stopPropagation</span><span class="pun">();</span><span class="pln"> </span><span class="com">// لا تجعل الأحداث تنساب للخارج</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// لا تفعل شيئًا</span><span class="pln">
    </span><span class="com">// إيقاف الإجرائية المترابطة</span><span class="pln">
    </span><span class="com">// transaction.onabort يمكن التعامل مع الخطأ ضمن</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<h2>
	عمليات البحث
</h2>

<p>
	يوجد نوعان أساسيان للبحث في مخزن الكائن:
</p>

<ol>
<li>
		بقيمة المفتاح أو مجال المفتاح، ففي مثالنا عن المخزن "books"، سنتمكن من البحث عن قيمة أو مجال من القيم للمفتاح <code>book.id</code>.
	</li>
	<li>
		باستخدام حقل آخر من حقول الكائن، مثل البحث في مثالنا السابق اعتمادًا على الحقل <code>book.price</code>، ويتطلب هذا البحث هيكليةً إضافيةً للبيانات تُدعى الفهرس index.
	</li>
</ol>
<h3>
	البحث بالمفتاح
</h3>

<p>
	لنتعرف أولًا على النوع الأول، وهو البحث بالمفتاح، وتدعم طرق البحث حالة القيمة الدقيقة للمفتاح أو ما يسمى "مجالًا من القيم"، ويمثلها الكائن <a data-ss1635000182="1" data-ss1635001042="1" href="https://www.w3.org/TR/IndexedDB/#keyrange" rel="external nofollow">IDBKeyRange</a>، وهي كائنات تحدد مجالًا مقبولًا من قيم المفاتيح، وتتولد الكائنات <code>IDBKeyRange</code> نتيجةً لاستخدام الاستدعاءات التالية:
</p>

<ul>
<li>
		<code>([IDBKeyRange.lowerBound(lower, [open</code>: وتعني أن تكون القيم أكبر أو تساوي الحد الأدنى <code>lower</code>، أو أكبر تمامًا إذا كانت قيمة <code>open</code> هي "true".
	</li>
	<li>
		<code>([IDBKeyRange.upperBound(upper, [open</code>: تعني أن تكون القيم أصغر أو تساوي الحد الأعلى <code>upper</code>، أو أصغر تمامًا إذا كانت قيمة <code>open</code> هي "true".
	</li>
	<li>
		<code>([IDBKeyRange.bound(lower, upper, [lowerOpen], [upperOpen</code>: تعني أن تكون القيمة محصورةً بين الحد الأدنى <code>lower</code> والأعلى <code>upper</code>، ولن يتضمن المجال قيمتي الحدين الأعلى والأدنى إذا ضبطنا <code>open</code> على القيمة "true".
	</li>
	<li>
		<code>(IDBKeyRange.only(key</code>: وتمثل مجالًا يتكون من مفتاح واحد، وهو نادر الاستخدام.
	</li>
</ul>
<p>
	سنرى التطبيق العملي لهذه الاستدعاءات السابقة قريبًا.
</p>

<p>
	ولتنفيذ بحث حقيقي ستجد التوابع التالية التي تقبل الوسيط <code>query</code>، وقد يكون هذا الوسيط قيمةً دقيقةً للمفتاح أو مجالًا:
</p>

<ul>
<li>
		<code>(store.get(query</code>: يبحث عن أول قيمة من خلال مفتاح أو مجال.
	</li>
	<li>
		<code>([store.getAll([query], [count</code>: يبحث عن جميع القيم، ويكون عدد القيم محدودًا إذا أعطينا قيمةً للوسيط <code>count</code>.
	</li>
	<li>
		<code>(store.getKey(query</code>: يبحث عن أول مفتاح يحقق الاستعلام، وعادةً يكون مجالًا.
	</li>
	<li>
		<code>([store.getAllKeys([query], [count</code>: يبحث عن كل المفاتيح التي تحقق الاستعلام، ويكون عدد النتائج محدودًا إذا أعطينا <code>count</code> قيمةً.
	</li>
	<li>
		<code>[(store.count([query</code>: يعيد العدد الكلي للمفاتيح التي تحقق الاستعلام، وعادةً يكون مجالًا.
	</li>
</ul>
<p>
	قد يكون لدينا على سبيل المثال الكثير من الكتب في مخزننا، وما دام الحقل <code>id</code> هو المفتاح، فستتمكن تلك التوابع من البحث باستعماله، مثل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_43" style="">
<span class="com">// الحصول على كتاب واحد</span><span class="pln">
books</span><span class="pun">.</span><span class="kwd">get</span><span class="pun">(</span><span class="str">'js'</span><span class="pun">)</span><span class="pln">

</span><span class="com">//  'css' &lt;= id &lt;= 'html' الحصول على كتاب يكون </span><span class="pln">
books</span><span class="pun">.</span><span class="pln">getAll</span><span class="pun">(</span><span class="typ">IDBKeyRange</span><span class="pun">.</span><span class="pln">bound</span><span class="pun">(</span><span class="str">'css'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'html'</span><span class="pun">))</span><span class="pln">

</span><span class="com">//  id &lt; 'html' الحصول على كتاب بحيث </span><span class="pln">
books</span><span class="pun">.</span><span class="pln">getAll</span><span class="pun">(</span><span class="typ">IDBKeyRange</span><span class="pun">.</span><span class="pln">upperBound</span><span class="pun">(</span><span class="str">'html'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">))</span><span class="pln">

</span><span class="com">// الحصول على كل الكتب</span><span class="pln">
books</span><span class="pun">.</span><span class="pln">getAll</span><span class="pun">()</span><span class="pln">

</span><span class="com">//  id &gt; 'js' الحصول على كل الكتب التي تحقق</span><span class="pln">
books</span><span class="pun">.</span><span class="pln">getAllKeys</span><span class="pun">(</span><span class="typ">IDBKeyRange</span><span class="pun">.</span><span class="pln">lowerBound</span><span class="pun">(</span><span class="str">'js'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">))</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		تُصنَّف القيم في مخازن الكائنات داخليًا باستخدام المفتاح، وبالتالي ستكون نتائج الاستعلامات التي تعيد عدة قيم مصنّفةً وفقًا للمفتاح.
	</p>
</blockquote>

<h3>
	البحث بالحقول
</h3>

<p>
	نحتاج إلى إنشاء هيكلية إضافية للبيانات تُدعى الفهرس index، لتنفيذ استعلام باستخدام حقل آخر من حقول الكائن، ويمثِّل الفهرس إضافةً add-on إلى المخزن لتتبع حقل محدد من كائن، ويُخزّن الفهرس -ومن أجل كل قيمة للحقل المحدد- قائمةً من مفاتيح الكائنات التي تمتلك تلك القيمة، وسنوضح ذلك لاحقًا بالتفصيل.
</p>

<p>
	إليك صيغة إنشاء الفهرس:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_45" style="">
<span class="pln">objectStore</span><span class="pun">.</span><span class="pln">createIndex</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> keyPath</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">options</span><span class="pun">]);</span></pre>

<ul>
<li>
		<code>name</code>: اسم الفهرس.
	</li>
	<li>
		<code>keyPath</code>: المسار إلى حقل الكائن الذي سيتتبعه الفهرس، وسنبحث اعتمادًا على ذلك الحقل.
	</li>
	<li>
		<code>option</code>: كائن اختياري له الخصائص التالية:
	</li>
	<li>
		<code>unique</code>: إذا كانت قيمته "true"، فيجب أن يوجد كائن واحد في المخزن له القيمة المعطاة في المسار المحدد، وسيجبر الفهرس تنفيذ هذا الأمر بتوليد خطأ إذا حاولنا إضافة نسخة مكررة.
	</li>
	<li>
		<code>multiEntry</code>: ويستخدَم فقط إذا كانت القيمة في المسار المحدد <code>keyPath</code> مصفوفةً، وسيعامل الفهرس في هذه الحالة المصفوفة بأكملها مثل مفتاح افتراضيًا، لكن إذا كانت قيمة <code>multiEntry</code> هي "true"، فسيحتفظ الفهرس بقائمة من كائنات المخزن لكل قيمة في تلك المصفوفة، وهكذا ستصبح عناصر المصفوفة مفاتيح فهرسة.
	</li>
</ul>
<p>
	نخزّن في المثال التالي الكتب باستعمال المفتاح <code>id</code>، ولنقل أننا نريد البحث باستعمال الحقل <code>price</code>، سنحتاج أولًا إلى إنشاء فهرس، ويجب أن ننجز ذلك ضمن معالج الحدث <code>upgradeneeded</code> تمامًا مثل مخزن الكائن:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_47" style="">
<span class="pln">openRequest</span><span class="pun">.</span><span class="pln">onupgradeneeded </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">//لابد من إنشاء الفهرس هنا، ضمن إجرائية تغيير النسخة </span><span class="pln">
  let books </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">createObjectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">keyPath</span><span class="pun">:</span><span class="pln"> </span><span class="str">'id'</span><span class="pun">});</span><span class="pln">
  let index </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">createIndex</span><span class="pun">(</span><span class="str">'price_idx'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'price'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<ul>
<li>
		سيتعقب الفهرس الحقل <code>price</code>.
	</li>
	<li>
		لن يكون السعر حقلًا بقيم فريدة، إذ يمكن وجود عدة كتب لها السعر نفسه، وبالتالي لن نضبط الخيار <code>unique</code>.
	</li>
	<li>
		السعر قيمة مفردة وليس مصفوفةً، لذا لن نطبّق الخيار <code>multiEntry</code>.
	</li>
</ul>
<p>
	لنتخيل الآن وجود 4 كتب في مخزننا <code>inventory</code>، إليك الصورة التي تظهر طبيعة الفهرس <code>index</code>:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="79127" data-ss1635000182="1" data-ss1635001042="1" href="https://academy.hsoub.com/uploads/monthly_2021_10/inventory_02.png.89bd1bb87acb17a00fcc9f926c8369e6.png" rel=""><img alt="inventory_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="79127" data-unique="nyg5qkot2" src="https://academy.hsoub.com/uploads/monthly_2021_10/inventory_02.png.89bd1bb87acb17a00fcc9f926c8369e6.png"></a>
</p>

<p>
	كما قلنا سابقًا، سيحفَظ الفهرس العائد لكل قيمة من قيم <code>price</code> -الوسيط الثاني- قائمةً بالمفاتيح التي ترتبط بهذا السعر، ويُحدَّث الفهرس تلقائيًا، فلا حاجة للاهتمام بهذا الأمر.
</p>

<p>
	سنطبِّق ببساطة التوابع السابقة نفسها على الفهرس عندما نريد أن نبحث عن سعر محدد:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_49" style="">
<span class="pln">let transaction </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">);</span><span class="pln"> </span><span class="com">// قراءة فقط</span><span class="pln">
let books </span><span class="pun">=</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">);</span><span class="pln">
let priceIndex </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">index</span><span class="pun">(</span><span class="str">"price_idx"</span><span class="pun">);</span><span class="pln">

let request </span><span class="pun">=</span><span class="pln"> priceIndex</span><span class="pun">.</span><span class="pln">getAll</span><span class="pun">(</span><span class="lit">10</span><span class="pun">);</span><span class="pln">

request</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">request</span><span class="pun">.</span><span class="pln">result </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">undefined</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Books"</span><span class="pun">,</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">result</span><span class="pun">);</span><span class="pln"> </span><span class="com">// مصفوفة من الكتبالتي سعرها=10</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"No such books"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	يمكن أن نستخدم <code>IDBKeyRange</code> أيضًا لإنشاء مجال محدد، والبحث عن الكتب الرخيصة أو باهظة الثمن:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_51" style="">
<span class="com">// جد الكتب التي سعرها يساوي أو أقل من 5</span><span class="pln">
let request </span><span class="pun">=</span><span class="pln"> priceIndex</span><span class="pun">.</span><span class="pln">getAll</span><span class="pun">(</span><span class="typ">IDBKeyRange</span><span class="pun">.</span><span class="pln">upperBound</span><span class="pun">(</span><span class="lit">5</span><span class="pun">));</span></pre>

<p>
	تُصنَّف الفهارس داخليًا وفق الحقل الذي تتعقبه، وهو السعر <code>price</code> في حالتنا، لذا سترتَّب النتائج حسب السعر عندما ننفذ البحث وفق حقل السعر.
</p>

<h2>
	الحذف من مخزن
</h2>

<p>
	يبحث التابع <code>delete</code> عن القيم التي يُطلب حذفها عبر استعلام، وله صيغة شبيهة بالتابع <code>getAll</code>.
</p>

<ul>
<li>
		<code>(delete(query</code>: يحذف القيم المطابقة للاستعلام، إليك مثالًا:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_53" style="">
<span class="com">//  id='js' احذف الكتاب الذي يحقق</span><span class="pln">
books</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="str">'js'</span><span class="pun">);</span></pre>

<p>
	إذا أردنا حذف الكتب بناءً على سعرها أو بناءً على أي حقل آخر، فعلينا أولًا إيجاد المفتاح ضمن الفهرس، ثم استدعاء التابع <code>delete</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_55" style="">
<span class="com">// ايجاد المفتاح في حالة السعر=5</span><span class="pln">
let request </span><span class="pun">=</span><span class="pln"> priceIndex</span><span class="pun">.</span><span class="pln">getKey</span><span class="pun">(</span><span class="lit">5</span><span class="pun">);</span><span class="pln">
request</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let id </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">result</span><span class="pun">;</span><span class="pln">
  let deleteRequest </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="pln">id</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	ولحذف كل شيء:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_57" style="">
<span class="pln">books</span><span class="pun">.</span><span class="pln">clear</span><span class="pun">();</span><span class="pln"> </span><span class="com">// افرغ المخزن</span></pre>

<h2>
	المؤشرات Cursors
</h2>

<p>
	تعيد التوابع -مثل <code>getAll/getAllKeys</code>- مصفوفةً من المفاتيح والقيم، وقد يكون كائن التخزين ضخمًا وأكبر من أن تتسع له الذاكرة المتاحة، وبالتالي سيخفق التابع <code>getAll</code> في الحصول على كل سجلات المصفوفة، فما العمل في حالة مثل هذه؟ ستساعدنا المؤشرات على الالتفاف على هذه المشكلة.
</p>

<p>
	المؤشرات هي كائنات خاصة تتجاوز كائن التخزين عند تنفيذ استعلام، وتعيد زوجًا واحدًا (مفتاح/قيمة) في كل مرة، وبالتالي ستساعدنا في توفير الذاكرة.
</p>

<p>
	ما دام مخزن الكائن سيُصنَّف وفقًا للمفتاح، فسينتقل المؤشر عبر المخزن وفق ترتيب المفتاح تصاعديًا (افتراضيًا). إليك صيغة استخدام المؤشر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_59" style="">
<span class="com">// لكن مع مؤشر  getAll مثل </span><span class="pln">
let request </span><span class="pun">=</span><span class="pln"> store</span><span class="pun">.</span><span class="pln">openCursor</span><span class="pun">(</span><span class="pln">query</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">direction</span><span class="pun">]);</span><span class="pln">

</span><span class="com">//  store.openKeyCursor للحصول على المفاتيح لا القيم  </span></pre>

<ul>
<li>
		<code>query</code>: مفتاح أو مجال لمفتاح، ويشابه في عمله <code>getAll</code>.
	</li>
	<li>
		<code>direction</code>: وهو وسيط اختياري، ويفيد في ترتيب تنقل المؤشر بين السجلات:
	</li>
	<li>
		<code>next</code>: وهي القيمة الافتراضية، حيث يتحرك المؤشر من المفتاح ذي القيمة الأدنى إلى الأعلى.
	</li>
	<li>
		<code>prev</code>: يتحرك المؤشر من القيمة العليا للمفتاح إلى الدنيا.
	</li>
	<li>
		<code>nextunique</code> و<code>prevunique</code>: تشابهان الخيارين السابقين، لكنهما تتجاوزان المفتاح المكرر، وتعملان فقط مع المؤشرات المبنية على فهارس، فعند وجود عدة قيم لن تُعاد إلا قيمة أول سعر يحقق معيار البحث.
	</li>
</ul>
<p>
	إن الاختلاف الرئيسي في عمل المؤشرات هو أنها تسبب وقوع الحدث <code>request.onsuccess</code> عدة مرات، مرةً عند كل نتيجة.
</p>

<p>
	إليك مثالًا عن استخدام المؤشر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_61" style="">
<span class="pln">let transaction </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">);</span><span class="pln">
let books </span><span class="pun">=</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">"books"</span><span class="pun">);</span><span class="pln">

let request </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">openCursor</span><span class="pun">();</span><span class="pln">

</span><span class="com">// يُستدعى من أجل كل كتاب وجده المؤشر</span><span class="pln">
request</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let cursor </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">result</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">cursor</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let key </span><span class="pun">=</span><span class="pln"> cursor</span><span class="pun">.</span><span class="pln">key</span><span class="pun">;</span><span class="pln"> </span><span class="com">//  (id) مفتاح الكتاب </span><span class="pln">
    let value </span><span class="pun">=</span><span class="pln"> cursor</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln"> </span><span class="com">// كائن الكتاب</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
    cursor</span><span class="pun">.</span><span class="kwd">continue</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"No more books"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	للمؤشر التوابع التالية:
</p>

<ul>
<li>
		<code>(advance(count</code>: يدفع المؤشر إلى الأمام بمقدار الوسيط <code>count</code> ويتجاوز القيم.
	</li>
	<li>
		<code>([continue([key])</code>: يدفع المؤشر إلى القيمة التالية في المجال المطابق للبحث، أو إلى ما بعد مفتاح معين مباشرةً إذا حددنا القيمة <code>key</code>.
	</li>
</ul>
<p>
	يُستدعى معالج الحدث <code>onsuccess</code>، سواءً تعددت القيم التي تطابق معيار البحث أو لا، ثم سنتمكن من الحصول على المؤشر الذي يدل على السجل التالي ضمن النتيجة <code>result</code> التي حصلنا عليها، أو أن لا يؤشر إلى شيء <code>unidefined</code>.
</p>

<h3>
	تدريب
</h3>

<p>
	أنشئ المؤشر في المثال السابق لمخزن الكائن. يمكن أيضًا أن ننشئ المؤشر اعتمادًا على الفهارس، إذ تسمح الفهارس كما نتذكر بالبحث وفق أي حقل من حقول الكائن، وتتشابه المؤشرات المبنية على الفهارس مع تلك المبنية على أساس مخازن الكائنات، فهي توفر الذاكرة بإعادة قيمة واحدة في كل مرة.
</p>

<p>
	سيكون <code>cursor.key</code> مفتاح الفهرسة بالنسبة للمؤشرات المبنية على الفهارس، وينبغي أن نستخدم الخاصية <code>cursor.primaryKey</code> إذا أردنا الحصول على مفتاح الكائن.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_63" style="">
<span class="pln">let request </span><span class="pun">=</span><span class="pln"> priceIdx</span><span class="pun">.</span><span class="pln">openCursor</span><span class="pun">(</span><span class="typ">IDBKeyRange</span><span class="pun">.</span><span class="pln">upperBound</span><span class="pun">(</span><span class="lit">5</span><span class="pun">));</span><span class="pln">

</span><span class="com">// يُستدعى لكل سجل</span><span class="pln">
request</span><span class="pun">.</span><span class="pln">onsuccess </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let cursor </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">result</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">cursor</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let primaryKey </span><span class="pun">=</span><span class="pln"> cursor</span><span class="pun">.</span><span class="pln">primaryKey</span><span class="pun">;</span><span class="pln"> </span><span class="com">// مفتاح مخزن الكائن التالي (id field)</span><span class="pln">
    let value </span><span class="pun">=</span><span class="pln"> cursor</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln"> </span><span class="com">// مفتاح مخزن الكائن التالي (book object)</span><span class="pln">
    let key </span><span class="pun">=</span><span class="pln"> cursor</span><span class="pun">.</span><span class="pln">key</span><span class="pun">;</span><span class="pln"> </span><span class="com">//مفتاح الفهرسة التالي (price)</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">key</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln">
    cursor</span><span class="pun">.</span><span class="kwd">continue</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"No more books"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<h2>
	مغلف الوعود Promise wrapper
</h2>

<p>
	إنّ إضافة الحدثين <code>onsuccess/onerror</code> إلى كل طلب أمر مرهق، ويمكن أحيانًا تحسين الوضع باستخدام التفويض delegation، مثل إعداد معالجات أحداث للإجرائية المترابطة بأكملها، لكن الصيغة <code>async/await</code> أكثر ملائمةً.
</p>

<p>
	لنستخدم مُغلَّف الحدث البسيط <a data-ss1635000182="1" data-ss1635001042="1" href="https://github.com/jakearchibald/idb" rel="external nofollow">idb</a> في هذا المقال، حيث ننشئ كائن <code>idb</code> عامًا مزوّدًا بتوابع مبنية على الوعود <a data-ss1635000182="1" data-ss1635001042="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%A7%D9%84%D9%88%D8%A7%D8%B9%D8%AF%D8%A9-%D8%AA%D8%AD%D9%88%D9%8A%D9%84-%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D8%A5%D9%84%D9%89-%D9%88%D8%B9%D9%88%D8%AF-promisification-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r919/" rel="">promisified</a> لقاعدة البيانات IndexedDB، وسنكتب الشيفرة التالية بدلًا من استخدام <code>onsuccess/onerror</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_65" style="">
<span class="pln">let db </span><span class="pun">=</span><span class="pln"> await idb</span><span class="pun">.</span><span class="pln">openDB</span><span class="pun">(</span><span class="str">'store'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> db </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">db</span><span class="pun">.</span><span class="pln">oldVersion </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// نفذ عملية التهيئة</span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">createObjectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">keyPath</span><span class="pun">:</span><span class="pln"> </span><span class="str">'id'</span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

let transaction </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'readwrite'</span><span class="pun">);</span><span class="pln">
let books </span><span class="pun">=</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  await books</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(...);</span><span class="pln">
  await books</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(...);</span><span class="pln">

  await transaction</span><span class="pun">.</span><span class="pln">complete</span><span class="pun">;</span><span class="pln">

  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'jsbook saved'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'error'</span><span class="pun">,</span><span class="pln"> err</span><span class="pun">.</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	وهكذا سنرى الشيفرة المحببة للجميع، "شيفرة غير متزامنة " وكتلة "try…catch".
</p>

<h2>
	معالجة الأخطاء
</h2>

<p>
	إذا لم نلتقط الأخطاء، فستقع إلى أن تعترضها أول كتلة <code>try..catch</code>.
</p>

<p>
	ستتحول الأخطاء التي لا نعترضها إلى حدث "عملية رفض وعد غير معالجة" ضمن الكائن <code>window</code>، لكن يمكن التعامل مع هذا الخطأ بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_67" style="">
<span class="pln">window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'unhandledrejection'</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let request </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">;</span><span class="pln"> </span><span class="com">// IndexedDB كائن طلب أصلي للقاعدة</span><span class="pln">
  let error </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">reason</span><span class="pun">;</span><span class="pln"> </span><span class="com">//request.error  خطأ غير مُعتَرض للكائن </span><span class="pln">
  </span><span class="pun">...</span><span class="pln">report about the error</span><span class="pun">...</span><span class="pln">
</span><span class="pun">});</span></pre>

<h2>
	إجرائية مترابطة غير فعالة
</h2>

<p>
	تكتمل الإجرائية المترابطة -كما رأينا سابقًا- عندما ينتهي المتصفح من تنفيذ شيفرتها ومهامها المتناهية الصغر، فلو وضعنا <em>مهمةً مستقلةً</em> مثل <code>fetch</code> وسط إجرائية مترابطة، فلن تنتظر عندها الإجرائية انتهاء هذه المهمة المستقلة، بل ستكتمل الإجرائية ببساطة وسيخفق الطلب التالي.
</p>

<p>
	يبقى الأمر نفسه بالنسبة إلى مُغلَّف الوعود والصيغة <code>async/await</code>، إليك مثالًا عن العملية <code>fetch</code> داخل إجرائية مترابطة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_69" style="">
<span class="pln">let transaction </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">"inventory"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"readwrite"</span><span class="pun">);</span><span class="pln">
let inventory </span><span class="pun">=</span><span class="pln"> transaction</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">"inventory"</span><span class="pun">);</span><span class="pln">

await inventory</span><span class="pun">.</span><span class="pln">add</span><span class="pun">({</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="str">'js'</span><span class="pun">,</span><span class="pln"> price</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> created</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln"> </span><span class="pun">});</span><span class="pln">

await fetch</span><span class="pun">(...);</span><span class="pln"> </span><span class="com">// (*)</span><span class="pln">

await inventory</span><span class="pun">.</span><span class="pln">add</span><span class="pun">({</span><span class="pln"> id</span><span class="pun">:</span><span class="pln"> </span><span class="str">'js'</span><span class="pun">,</span><span class="pln"> price</span><span class="pun">:</span><span class="pln"> </span><span class="lit">10</span><span class="pun">,</span><span class="pln"> created</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">()</span><span class="pln"> </span><span class="pun">});</span><span class="pln"> </span><span class="com">// Error</span></pre>

<p>
	سيخفق الأمر <code>inventory.add</code> بعد العملية <code>fetch</code> في السطر "(*)" وسيقع الخطأ “inactive transaction” أي "إجرائية مترابطة غير فعالة"، لأنّ الإجرائية المترابطة قد اكتملت بالفعل وأغلقت، وسنلتف على هذه المشكلة كما فعلنا سابقًا، فإما أن ننشئ إجرائيةً مرتبطةً جديدةً، أو أن نفصل الأمور عن بعضها.
</p>

<ol>
<li>
		حضّر البيانات ثم أحضر كل ما يلزم عبر <code>fetch</code> أولًا.
	</li>
	<li>
		احفظ البيانات داخل قاعدة البيانات.
	</li>
</ol>
<h2>
	الحصول على كائنات أصيلة
</h2>

<p>
	يُنفِّذ المُغلَّف طلب IndexedDB أصلي داخليًا، ويضيف معالجي الحدثين <code>onerror/onsuccess</code> إليه، ثم يعيد وعدًا يُرفَض أو يُنفَّذ مع نتيجة الطلب. يعمل هذا الأمر جيّدًا في معظم الأوقات، وستجد مثالًا في <a data-ss1635000182="1" data-ss1635001042="1" href="https://github.com/jakearchibald/idb" rel="external nofollow">مستودع GitHub</a> الخاص بمُغلًّف الحدث idb. وفي حالات قليلة نادرة، وعندما نحتاج إلى الكائن <code>request</code> الأصلي، يمكن الوصول إليه باستخدام الخاصية <code>promise.request</code> العائدة للوعد.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_71" style="">
<span class="pln">let promise </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">book</span><span class="pun">);</span><span class="pln"> </span><span class="com">// الحصول على الوعد دون انتظار النتيجة </span><span class="pln">

let request </span><span class="pun">=</span><span class="pln"> promise</span><span class="pun">.</span><span class="pln">request</span><span class="pun">;</span><span class="pln"> </span><span class="com">// كائن الطلب الأصلي</span><span class="pln">
let transaction </span><span class="pun">=</span><span class="pln"> request</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">;</span><span class="pln"> </span><span class="com">// كائن الإجرائية المترابطة الأصلي</span><span class="pln">

</span><span class="com">// ...do some native IndexedDB voodoo...</span><span class="pln">

let result </span><span class="pun">=</span><span class="pln"> await promise</span><span class="pun">;</span><span class="pln"> </span><span class="com">// إن كنا بحاجة لذلك</span></pre>

<h2>
	الخلاصة
</h2>

<p>
	يمكن تشبيه قاعدة البيانات بالكائن "localStorage" لكنها مدعَّمة، وهي قاعدة بيانات (مفتاح-قيمة) لها إمكانيات كبيرة كافية للتطبيقات التي تعمل دون اتصال، كما أنها سهلة الاستخدام.
</p>

<p>
	تُمثِّل التوصيفات الخاصة بقاعدة البيانات أفضل دليل لاستخدامها، و<a data-ss1635000182="1" data-ss1635001042="1" href="https://www.w3.org/TR/IndexedDB-2/" rel="external nofollow">نسختها الحالية</a> هي 2.0، كما ستجد بعض التوابع من <a data-ss1635000182="1" data-ss1635001042="1" href="https://w3c.github.io/IndexedDB/" rel="external nofollow">النسخة 3.0</a> التي لن تختلف كثيرًا، وهي مدعومة جزئيًا.
</p>

<p>
	يمكن تلخيص طريقة استخدامها الأساسية بالشكل التالي:
</p>

<p>
	أولًا، الحصول على مُغلِّف وعود مثل <a data-ss1635000182="1" data-ss1635001042="1" href="https://github.com/jakearchibald/idb" rel="external nofollow">idb</a>.
</p>

<p>
	ثانيًا، فتح قاعدة البيانات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_8038_73" style="">
<span class="pln">idb</span><span class="pun">.</span><span class="pln">openDb</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> version</span><span class="pun">,</span><span class="pln"> onupgradeneeded</span><span class="pun">)</span></pre>

<ul>
<li>
		إنشاء مخزن للكائن وفهارس ضمن معالج الحدث <code>onupgradeneeded</code>، أو تنفيذ تحديث للنسخة عند الحاجة.
	</li>
</ul>
<p>
	ثالثًا، لتنفيذ الطلبات عليك باتباع الآتي:
</p>

<ul>
<li>
		أنشئ إجرائيةً مترابطةً <code>('db.transaction('books</code>، مع إمكانية القراءة والكتابة عند الحاجة.
	</li>
	<li>
		احصل على مخزن كائن بالشكل التالي <code>('transaction.objectStore('books</code>.
	</li>
</ul>
<p>
	رابعًا، يمكنك البحث بالمفتاح أو استدعاء التوابع المتعلقة بكائن المخزن مباشرةً.
</p>

<ul>
<li>
		أنشئ فهرسًا للبحث باستخدام حقل آخر من حقول الكائن.
	</li>
</ul>
<p>
	خامسًا، إذا لم تتسع الذاكرة للبيانات، فاستخدم مؤشرًا cursor.
</p>

<p>
	إليك هذا المثال النموذجي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_1083_16" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"https://cdn.jsdelivr.net/npm/idb@3.0.2/build/idb.min.js"</span><span class="tag">&gt;&lt;/script&gt;</span><span class="pln">

</span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">onclick</span><span class="pun">=</span><span class="atv">"</span><span class="pln">addBook</span><span class="pun">()</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">Add a book</span><span class="tag">&lt;/button&gt;</span><span class="pln">
</span><span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">onclick</span><span class="pun">=</span><span class="atv">"</span><span class="pln">clearBooks</span><span class="pun">()</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">Clear books</span><span class="tag">&lt;/button&gt;</span><span class="pln">

</span><span class="tag">&lt;p&gt;</span><span class="pln">Books list:</span><span class="tag">&lt;/p&gt;</span><span class="pln">

</span><span class="tag">&lt;ul</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"listElem"</span><span class="tag">&gt;&lt;/ul&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
let db</span><span class="pun">;</span><span class="pln">

init</span><span class="pun">();</span><span class="pln">

async </span><span class="kwd">function</span><span class="pln"> init</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  db </span><span class="pun">=</span><span class="pln"> await idb</span><span class="pun">.</span><span class="pln">openDb</span><span class="pun">(</span><span class="str">'booksDb'</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">,</span><span class="pln"> db </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    db</span><span class="pun">.</span><span class="pln">createObjectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">keyPath</span><span class="pun">:</span><span class="pln"> </span><span class="str">'name'</span><span class="pun">});</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  list</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

async </span><span class="kwd">function</span><span class="pln"> list</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let tx </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">);</span><span class="pln">
  let bookStore </span><span class="pun">=</span><span class="pln"> tx</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">);</span><span class="pln">

  let books </span><span class="pun">=</span><span class="pln"> await bookStore</span><span class="pun">.</span><span class="pln">getAll</span><span class="pun">();</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">books</span><span class="pun">.</span><span class="pln">length</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    listElem</span><span class="pun">.</span><span class="pln">innerHTML </span><span class="pun">=</span><span class="pln"> books</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">book </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">`&lt;</span><span class="pln">li</span><span class="pun">&gt;</span><span class="pln">
        name</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">book</span><span class="pun">.</span><span class="pln">name</span><span class="pun">},</span><span class="pln"> price</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">book</span><span class="pun">.</span><span class="pln">price</span><span class="pun">}</span><span class="pln">
      </span><span class="pun">&lt;/</span><span class="pln">li</span><span class="pun">&gt;`).</span><span class="pln">join</span><span class="pun">(</span><span class="str">''</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    listElem</span><span class="pun">.</span><span class="pln">innerHTML </span><span class="pun">=</span><span class="pln"> </span><span class="str">'&lt;li&gt;No books yet. Please add books.&lt;/li&gt;'</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">


</span><span class="pun">}</span><span class="pln">

async </span><span class="kwd">function</span><span class="pln"> clearBooks</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let tx </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'readwrite'</span><span class="pun">);</span><span class="pln">
  await tx</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">).</span><span class="pln">clear</span><span class="pun">();</span><span class="pln">
  await list</span><span class="pun">();</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

async </span><span class="kwd">function</span><span class="pln"> addBook</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let name </span><span class="pun">=</span><span class="pln"> prompt</span><span class="pun">(</span><span class="str">"Book name?"</span><span class="pun">);</span><span class="pln">
  let price </span><span class="pun">=</span><span class="pln"> </span><span class="pun">+</span><span class="pln">prompt</span><span class="pun">(</span><span class="str">"Book price?"</span><span class="pun">);</span><span class="pln">

  let tx </span><span class="pun">=</span><span class="pln"> db</span><span class="pun">.</span><span class="pln">transaction</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'readwrite'</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    await tx</span><span class="pun">.</span><span class="pln">objectStore</span><span class="pun">(</span><span class="str">'books'</span><span class="pun">).</span><span class="pln">add</span><span class="pun">({</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> price</span><span class="pun">});</span><span class="pln">
    await list</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">err</span><span class="pun">.</span><span class="pln">name </span><span class="pun">==</span><span class="pln"> </span><span class="str">'ConstraintError'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      alert</span><span class="pun">(</span><span class="str">"Such book exists already"</span><span class="pun">);</span><span class="pln">
      await addBook</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">throw</span><span class="pln"> err</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'unhandledrejection'</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">"Error: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">reason</span><span class="pun">.</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	وستكون النتيجة كالتالي:
</p>

<p>
	<iframe class="code-tabs__result" data-ss1635000182="1" data-ss1635000210="1" data-ss1635001042="1" src="https://javascript.info/article/indexeddb/books/" style="border: solid 1px #000000"></iframe>
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1635000182="1" data-ss1635001042="1" href="https://javascript.info/indexeddb" rel="external nofollow">indexeddb</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a data-ss1635000182="1" data-ss1635001042="1" href="https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-javascript-r1330/" rel="">ملفات تعريف الارتباط وضبطها في JavaScript</a>
	</li>
	<li>
		<a data-ss1635000182="1" data-ss1635001042="1" href="https://academy.hsoub.com/programming/html/html5/%D8%AA%D8%B9%D8%B1%D9%91%D9%81-%D8%B9%D9%84%D9%89-indexeddb-r67/" rel="">تعرّف على IndexedDB</a>
	</li>
	<li>
		<a data-ss1635000182="1" data-ss1635001042="1" href="https://academy.hsoub.com/programming/general/%D9%85%D9%81%D9%87%D9%88%D9%85-service-worker-%D9%88%D8%AA%D8%A3%D8%AB%D9%8A%D8%B1%D9%87-%D9%81%D9%8A-%D8%A3%D8%AF%D8%A7%D8%A1-%D9%88%D8%A8%D9%86%D9%8A%D8%A9-%D9%85%D9%88%D8%A7%D9%82%D8%B9-%D9%88%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-r833/" rel="">مفهوم Service Worker وتأثيره في أداء وبنية مواقع وتطبيقات الويب</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1337</guid><pubDate>Tue, 05 Oct 2021 15:00:00 +0000</pubDate></item><item><title>&#x645;&#x639;&#x627;&#x644;&#x62C;&#x629; &#x627;&#x644;&#x623;&#x62D;&#x62F;&#x627;&#x62B; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627; &#x633;&#x643;&#x631;&#x64A;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%D8%A9-%D8%A7%D9%84%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7-%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1354/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_10/617a48dc3b038_----.png.e257dfbbefbcae257a348a2fb3660117.png" /></p>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p data-gramm="false">
		إن لك سيطرة على عقلك فقط، وليس الأحداث الخارجية، وإذا تذكرت ذلك فستجد القوة.
	</p>

	<p>
		ـــ ماركوس أوريليوس.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="81061" href="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_15.jpg.3fed0822796f66d1ebb7296ef3fdb3f1.jpg" rel="" data-fileext="jpg"><img alt="chapter_picture_15.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="81061" data-unique="4jnjmmyqh" src="https://academy.hsoub.com/uploads/monthly_2021_10/chapter_picture_15.jpg.3fed0822796f66d1ebb7296ef3fdb3f1.jpg"></a>
</p>

<p>
	تتعامل بعض البرامج مع مدخلات المستخدِم المباشرة مثل مدخلات لوحة المفاتيح والفأرة، ومثل تلك المدخلات ليس لها هيكل منظَّم بل تكون لحظية جزءًا جزءًا، ويجب أن يتعامل البرنامج معها أثناء حدوثها.
</p>

<h2>
	معالجات الأحداث
</h2>

<p>
	تخيل أن هناك واجهة لا تحوي طريقةً لمعرفة المفتاح الذي ضغطت عليه على لوحة المفاتيح، إلا بقراءة حالة المفتاح الحالية، فإذا أردت التفاعل مع ضغطات المفاتيح فسيكون عليك قراءة حالة المفتاح باستمرار كي تلتقط تغيرها قبل أن يترك إصبعك المفتاح، وسيكون من الخطير إجراء أي حسابات قد تستغرق وقتًا، إذ قد تفوتك هذه الضغطة.
</p>

<p>
	إنّ هذا الأسلوب متَّبع في بعض الآلات البدائية، وأفضل منه أن نجعل العتاد أو نظام التشغيل يلاحظان ضغطات المفاتيح ويضعانها في رتل، ثم يتفقد برنامج ما هذا الرتل لينظر في الأحداث المستجِدة ويتعامل مع ما يجده هناك. ويجب أن لا يهمل هذا البرنامج قراءة الرتل، بل يجب أن يتفقّده بصورة دورية، وإلا فستلاحظ أن البرنامج الذي تتعامل معه أنت غير متجاوب، ويسمى هذا الأسلوب بالاقتراع polling، لكن يفضِّل المبرمجون تجنبه، والأفضل منهما جميعًا هو جعل النظام ينبِّه شيفرة برنامجنا كلما وقع حدث ما، وتفعل المتصفحات ذلك بالسماح لنا بتسجيل الدوال على أساس معالِجات handlers لأحداث بعينها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_6" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;اضغط</span><span class="pln"> </span><span class="pun">على</span><span class="pln"> </span><span class="pun">هذا</span><span class="pln"> </span><span class="pun">المستند</span><span class="pln"> </span><span class="pun">لتفعيل</span><span class="pln"> </span><span class="pun">المعالِج&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"You knocked?"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تشير رابطة <code>window</code> إلى كائن مضمَّن built-in يوفره المتصفح، يمثل نافذة المتصفح التي تحتوي على المستند، كما يسجل استدعاء التابع <code>addEventListener</code> الخاص بها الوسيط الثاني ليُستدعَى كلما وقع الحدث الموصوف في الوسيط الأول.
</p>

<h2>
	الأحداث وعقد DOM
</h2>

<p>
	يُسجَّل كل معالِج حدثًا لمتصفح في سياق ما، فقد استدعينا <code>addEventListener</code> في المثال السابق على كائن <code>window</code> لتسجيل معالِج للنافذة كلها، ويمكن العثور على مثل هذا التابع في عناصر DOM أيضًا، وفي بعض أنواع الكائنات الأخرى.
</p>

<p>
	لا تُستدعى مستمِعات الأحداث event listeners إلا عند وقوع الحدث في سياق الكائن الذي تكون مسجلة عليه.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_8" style=""><span class="pun">&lt;</span><span class="pln">button</span><span class="pun">&gt;اضغط</span><span class="pln"> </span><span class="pun">هنا&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;لا</span><span class="pln"> </span><span class="pun">يوجد</span><span class="pln"> </span><span class="pun">معالِج</span><span class="pln"> </span><span class="pun">هنا&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let button </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">);</span><span class="pln">
  button</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Button clicked."</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	يربط هذا المثال المعالج بعقدة زر، وأيّ ضغطة على هذا الزر تشغِّل المعالج، بينما لا يحدث شيء عند الضغط على بقية المستند.
</p>

<p>
	يعطي إلحاق سمة <code>onclick</code> لعقدة ما التأثير نفسه، وهذا يصلح لأغلب أنواع الأحداث، إذ تستطيع إلحاق معالج من خلال سمة يكون اسمها هو اسم الحدث مسبوقًا بـ <code>on</code>، غير أن العقدة تحتوي على سمة <code>onclick</code> واحدة فقط، لذا تستطيع تسجيل معالج واحد فقط لكل عقدة بهذه الطريقة.
</p>

<p>
	يسمح التابع <code>addEventListener</code> لك بأن تضيف أي عدد من المعالجات، بحيث لا تقلق من إضافتها حتى لو كان لديك معالجات أخرى للعنصر؛ أما التابع <code>removeEventListener</code> الذي تستدعيه وسائط تشبه <code>addEventListener</code>، فإنه يحذف المعالج، انظر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_10" style=""><span class="pun">&lt;</span><span class="pln">button</span><span class="pun">&gt;زر</span><span class="pln"> </span><span class="pun">الضغطة</span><span class="pln"> </span><span class="pun">الواحدة&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let button </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> once</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Done."</span><span class="pun">);</span><span class="pln">
    button</span><span class="pun">.</span><span class="pln">removeEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> once</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  button</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> once</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	يجب أن تكون الدالة المعطاة لـ <code>removeEventListener</code> لها قيمة الدالة نفسها التي أعطيت إلى <code>addEventListener</code>، بحيث إذا أردت إلغاء تسجيل معالج ما، فعليك أن تعطي الدالة الاسم <code>once</code> في المثال كي تستطيع تمرير نفس قيمة الدالة لكلا التابعين.
</p>

<h2>
	كائنات الأحداث
</h2>

<p>
	يُمرَّر كائن الحدث إلى دوال معالجات الأحداث على أساس وسيط، ويحمل معلومات إضافية عن ذلك الحدث، فإذا أردنا معرفة أي زر قد ضُغِط عليه في الفأرة مثلًا، فإننا سنبحث في خاصية <code>button</code> لكائن الحدث.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_12" style=""><span class="pun">&lt;</span><span class="pln">button</span><span class="pun">&gt;اضغط</span><span class="pln"> </span><span class="pun">عليّ</span><span class="pln"> </span><span class="pun">كيفما</span><span class="pln"> </span><span class="pun">شئت&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let button </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">);</span><span class="pln">
  button</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"mousedown"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">button </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Left button"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">button </span><span class="pun">==</span><span class="pln"> </span><span class="lit">1</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Middle button"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">button </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Right button"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	الانتشار Propagation
</h2>

<p>
	تستقبل المعالجات المسجَّلة مع فروع children على العقد أحداثًا تقع في هذه الفروع أيضًا، فإذا تم النقر على زر داخل فقرة ما، فسترى معالجات الأحداث في تلك الفقرة حدث النقر أيضًا.
</p>

<p>
	لكن إذا كان كل من الفقرة والزر لهما معالج، فإنّ المعالج الأكثر خصوصيةً -أي المعالج الذي على الزر مثلًا- هو الذي يعمل، ويقال هنا أن الحدث ينتشر propagate إلى الخارج outward، أي من العقدة التي حدث فيها إلى جذر المستند، وبعد أن تحصل جميع المعالجات المسجلة على عقدة ما على فرصة للاستجابة للحدث، فستحصل المعالجات المسجَّلة على النافذة بأكملها على فرصتها في الاستجابة للحدث هي أيضًا.
</p>

<p>
	يستطيع معالج الحدث استدعاء التابع <code>stopPropagation</code> على كائن الحدث في أي وقت لمنع المعالجات من استقبال الحدث، وهذا مفيد إذا كان لديك مثلًا زر داخل عنصر آخر قابل للنقر. ولم ترد أن تتسبب نقرات الزر في نقرات ذلك العنصر الخارجي أيضًا.
</p>

<p>
	يسجِّل المثال التالي معالجات <code>"mousedown"</code> على كل من الزر والفقرة التي حوله، فحين تنقر بالزر الأيمن سيستدعي معالج الزر الذي في الفقرة التابع <code>stopPropagation</code> الذي سيمنع المعالج الذي على الفقرة من العمل، وإذا نُقر الزر بزر آخر للفأرة فسيعمل كلا المعالجين، انظر كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_14" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;فقرة</span><span class="pln"> </span><span class="pun">فيها</span><span class="pln">  </span><span class="pun">&lt;</span><span class="pln">button</span><span class="pun">&gt;زر&lt;</span><span class="str">/button&gt;.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let para </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">);</span><span class="pln">
  let button </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"button"</span><span class="pun">);</span><span class="pln">
  para</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"mousedown"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Handler for paragraph."</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  button</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"mousedown"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Handler for button."</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">button </span><span class="pun">==</span><span class="pln"> </span><span class="lit">2</span><span class="pun">)</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">stopPropagation</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تحتوي أغلب كائنات الأحداث على خاصية <code>target</code> التي تشير إلى العقدة التي أُنشئت فيها، ونستخدم هذه الخاصية لضمان أننا لا نعالج شيئًا انتشر من عقدة لا نريد معالجتها، كما من الممكن استخدام هذه الخاصية لإلقاء شبكة كبيرة على نوع حدث معيَّن، فإذا كانت لديك عقدة تحتوي على قائمة طويلة من الأزرار مثلًا، فقد يكون أسهل أن تسجِّل معالج نقرة منفردة على العقدة الخارجية وتجعله يستخدِم خاصية <code>target</code> ليعرف إذا كان الزر قد نُقر أم لا بدلًا من تسجيل معالجات فردية لكل الأزرار.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_16" style=""><span class="pun">&lt;</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">A</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">B</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">C</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">.</span><span class="pln">nodeName </span><span class="pun">==</span><span class="pln"> </span><span class="str">"BUTTON"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Clicked"</span><span class="pun">,</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">.</span><span class="pln">textContent</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	الإجراءات الافتراضية
</h2>

<p>
	إذا نقرت على رابط ما، فستذهب إلى الهدف المرتبط بهذا الرابط؛ أما إذا نقرت على السهم المشير للأسفل، فسيهبط المتصفح بالصفحة للأسفل؛ بينما إذا نقرت بالزر الأيمن، فستحصل على القائمة المختصرة، وهكذا فإن كل حدث له إجراء افتراضي مرتبط به.
</p>

<p>
	وتُستدعى معالجات الأحداث جافاسكربت قبل حدوث السلوك الافتراضي في أغلب أنواع الأحداث، فإذا لم يرد المعالج وقوع هذا السلوك الاعتيادي لأنه قد عالج الحدث بالفعل فإنه يستدعي التابع <code>preventDefault</code> على كائن الحدث.
</p>

<p>
	يمكن استخدام هذا لتطبيق اختصارات لوحة المفاتيح الخاصة بك أو القائمة المختصرة، كما يمكن استخدامه ليتدخل معارضًا السلوك الذي يتوقعه المستخدِم.
</p>

<p>
	انظر المثال التالي لرابط لا يذهب بالمستخدِم إلى الموقع الذي يحمله:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_18" style=""><span class="pun">&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"https://developer.mozilla.org/"</span><span class="pun">&gt;</span><span class="pln">MDN</span><span class="pun">&lt;/</span><span class="pln">a</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let link </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"a"</span><span class="pun">);</span><span class="pln">
  link</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Nope."</span><span class="pun">);</span><span class="pln">
    event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	لا تفعل شيئًا كهذا إلا إن كان لديك سبب مقنع، إذ سينزعج مستخدِمو صفحتك من مثل هذا السلوك المفاجئ لهم.
</p>

<p>
	بعض الأحداث لا يمكن اعتراضها أبدًا في بعض المتصفحات، إذ لا يمكن معالجة اختصار لوحة المفاتيح الذي يغلق اللسان الحالي -أي ctrl+w في ويندوز أو ⌘+w في ماك- باستخدام جافاسكربت مثلًا.
</p>

<h2>
	أحداث المفاتيح
</h2>

<p>
	يطلِق المتصفح الحدث <code>"keydown"</code> في كل مرة تضغط فيها مفتاحًا من لوحة المفاتيح، وكلما رفعت يدك عن المفتاح، ستحصل على الحدث <code>"keyup"</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_20" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln"> v </span><span class="pun">تصير</span><span class="pln"> </span><span class="pun">هذه</span><span class="pln"> </span><span class="pun">الصفحة</span><span class="pln"> </span><span class="pun">بنفسجية</span><span class="pln"> </span><span class="pun">إذا</span><span class="pln"> </span><span class="pun">ضغطت</span><span class="pln"> </span><span class="pun">مفتاح.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"keydown"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">key </span><span class="pun">==</span><span class="pln"> </span><span class="str">"v"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">background </span><span class="pun">=</span><span class="pln"> </span><span class="str">"violet"</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"keyup"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">key </span><span class="pun">==</span><span class="pln"> </span><span class="str">"v"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">background </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	يُطلَق الحدث <code>"keydown"</code> إذا ضغطت على المفتاح وتركته أو إذا ظللت ضاغطًا عليه، حيث يُطلق في كل مرة يُكرر فيها المفتاح، وانتبه لهذا إذ أنك لو أضفت زرًا إلى DOM حين يُضغط مفتاح، ثم حذفته حين يُترك المفتاح، فقد تضيف مئات الأزرار خطأً إذا ضُغط على المفتاح ضغطًا طويلًا.
</p>

<p>
	ينظر المثال في خاصية <code>key</code> لكائن الحدث ليرى عن أي مفتاح هو، إذ تحمل هذه الخاصية سلسلةً نصيةً تتوافق مع الشيء الذي يُطبع على الشاشة إذا ضُغط ذلك المفتاح، عدا بعض الحالات الخاصة التي تحمل الخاصية اسم المفتاح الذي يُضغط مثل زر الإدخال <code>"Enter"</code>.
</p>

<p>
	إذا ظللت ضاغطًا على مفتاح "عالي" shift ثم ضغطت على مفتاح v مثلًا، فإن ذلك قد يتسبب في حمل الخاصية لاسم المفتاح أيضًا، وعندها تتحول <code>"v"</code> إلى <code>"V"</code>، وتتغير <code>"1"</code> إلى <code>"!"</code> إذا كان هذا ما يخرجه الضغط على shift+1 على حاسوبك.
</p>

<p>
	تولِّد مفاتيح التحكم مثل shift وcontrol وalt وغيرها أحداث مفاتيح مثل المفاتيح العادية، وتستطيع معرفة إذا كانت هذه المفاتيح مضغوط عليها ضغطًا مستمرًا عند البحث عن مجموعات المفاتيح من خلال النظر إلى خصائص <code>shiftKey</code> و<code>ctrlKey</code> و<code>altKey</code> و<code>metaKey</code> لأحداث لوحة المفاتيح والفأرة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_22" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Press</span><span class="pln"> </span><span class="typ">Control</span><span class="pun">-</span><span class="typ">Space</span><span class="pln"> to </span><span class="kwd">continue</span><span class="pun">.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"keydown"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">key </span><span class="pun">==</span><span class="pln"> </span><span class="str">" "</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">ctrlKey</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Continuing!"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	تعتمد عقدة DOM حيث بدأ حدث المفتاح على العنصر الذي كان نشطًا عند الضغط على المفتاح، ولا تستطيع أغلب العقد أن تكون نشطةً إلا إذا أعطيتها سمة <code>tabindex</code> على خلاف الروابط والأزرار وحقول الاستمارات، كما سنعود لحقول الاستمارات في مقال لاحق، وإذا لم يكن ثمة شيء بعينه نشطًا، فستتصرف <code>document.body</code> على أساس عقدة هدف لأحداث المفاتيح.
</p>

<p>
	لا نفضِّل استخدام أحداث المفاتيح إذا كتب المستخدِم نصًا وأردنا معرفة ما يكتبه، فبعض المنصات لا تبدأ تلك الأحداث هنا كما في حالة لوحة المفاتيح الافتراضية على هواتف الأندرويد، لكن حتى لو كانت لديك لوحة مفاتيح قديمة، فإنّ بعض أنواع النصوص المدخلة لا تتطابق مع ضغطات المفاتيح تطابقًا مباشرًا، مثل برنامج محرر أسلوب الإدخال input method editor -أو IME اختصارًا- الذي يستخدمه الأشخاص الذين لا تتناسب نصوصهم مع لوحة المفاتيح، حيث تُدمج عدة نقرات لإنشاء المحارف.
</p>

<p>
	إذا أرادت العناصر التي تستطيع الكتابة فيها معرفة ما يكتبه المستخدِم كما في وسوم <code>&lt;input&gt;</code> و<code>&lt;textarea&gt;</code>، فإنها تطلق أحداث <code>"input"</code> كلما غيّر المستخدِم محتواها، ومن الأفضل قراءة المحتوى الفعلي المكتوب من الحقل النشط إذا أردنا الحصول عليه.
</p>

<h2>
	أحداث المؤشر
</h2>

<p>
	توجد حاليًا طريقتان مستخدَمتان على نطاق واسع للإشارة إلى الأشياء على الشاشة: الفأرات -بما في ذلك الأجهزة التي تعمل عملها مثل لوحات اللمس touchpads وكرات التتبع- وشاشات اللمس touchscreens، وتُنتج هاتان الطريقتان نوعين مختلفَين تمامًا من الأحداث.
</p>

<h3>
	ضغطات الفأرة
</h3>

<p>
	يؤدي الضغط على زر الفأرة إلى إطلاق عدد من الأحداث، ويتشابه حدثَي <code>"mouseup"</code> و<code>"mousedown"</code> مع حدثَي <code>"keydown"</code> و<code>"keyup"</code>، وتنطلق عند الضغط على الزر وتركه، كما تحدث هذه على عقد DOM الموجودة أسفل مؤشر الفأرة مباشرةً عند وقوع الحدث.
</p>

<p>
	ينطلق حدث <code>"click"</code> بعد حدث <code>"mouseup"</code> على العقدة الأكثر تحديدًا التي تحتوي على كل من ضغط الزر وتحريره، فإذا ضغطت على زر الفأرة في فقرة مثلًا ثم حركت المؤشر إلى فقرة أخرى وتركت الزر، فسيقع حدث <code>"click"</code> على العنصر الذي يحتوي على هاتين الفقرتين،؛ أما في حالة حدوث نقرتين بالقرب من بعضهما، فسينطلق حدث <code>"dblclick"</code> -وهو النقرة المزدوجة- بعد حدث النقرة الثانية.
</p>

<p>
	يمكنك النظر إلى الخاصيتَين <code>clientX</code> و<code>clientY</code> إذا أردت الحصول على معلومات دقيقة حول هذا المكان الذي وقع فيه حدث الفأرة، إذ تحتويان على إحداثيات الحدث -بالبكسل- نسبةً إلى الركن العلوي الأيسر من النافذة، أو <code>pageX</code> و<code>pageY</code> نسبةً إلى الركن العلوي الأيسر من المستند كله، وقد تكون هذه مختلفةً عن تلك عند تمرير النافذة.
</p>

<p>
	ينفِّذ المثال التالي برنامج رسم بدائي، حيث توضع نقطة أسفل مؤشر الماوس في كل مرة تنقر فيها على المستند.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_9408_30" style=""><span class="tag">&lt;style&gt;</span><span class="pln">
  body </span><span class="pun">{</span><span class="pln">
    height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">200px</span><span class="pun">;</span><span class="pln">
    background</span><span class="pun">:</span><span class="pln"> beige</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">dot </span><span class="pun">{</span><span class="pln">
    height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8px</span><span class="pun">;</span><span class="pln"> width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8px</span><span class="pun">;</span><span class="pln">
    border</span><span class="pun">-</span><span class="pln">radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4px</span><span class="pun">;</span><span class="pln"> </span><span class="com">/* rounds corners */</span><span class="pln">
    background</span><span class="pun">:</span><span class="pln"> blue</span><span class="pun">;</span><span class="pln">
    position</span><span class="pun">:</span><span class="pln"> absolute</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"click"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let dot </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">);</span><span class="pln">
    dot</span><span class="pun">.</span><span class="pln">className </span><span class="pun">=</span><span class="pln"> </span><span class="str">"dot"</span><span class="pun">;</span><span class="pln">
    dot</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">pageX </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
    dot</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">pageY </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
    document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">dot</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<h3>
	حركة الفأرة
</h3>

<p>
	ينطلق حدث <code>"mousemove"</code> في كل مرة يتحرك مؤشر الفأرة، ويمكن استخدام هذا الحدث لتتبع موضع المؤشر، مثل أن نحتاج إلى تنفيذ بعض المهام المتعلقة بخاصية السحب drag للمؤشر.
</p>

<p>
	يوضح المثال التالي برنامجًا يعرض شريطًا ويضبط معالجات أحداث كي يتحكم السحب يمينًا ويسارًا في عرض الشريط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_32" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;اسحب</span><span class="pln"> </span><span class="pun">الشريط</span><span class="pln"> </span><span class="pun">لتغيير</span><span class="pln"> </span><span class="pun">عرضه:&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">div style</span><span class="pun">=</span><span class="str">"background: orange; width: 60px; height: 20px"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let lastX</span><span class="pun">;</span><span class="pln"> </span><span class="com">// Tracks the last observed mouse X position</span><span class="pln">
  let bar </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"div"</span><span class="pun">);</span><span class="pln">
  bar</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"mousedown"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">button </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      lastX </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">clientX</span><span class="pun">;</span><span class="pln">
      window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"mousemove"</span><span class="pun">,</span><span class="pln"> moved</span><span class="pun">);</span><span class="pln">
      event</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln"> </span><span class="com">// Prevent selection</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> moved</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">buttons </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      window</span><span class="pun">.</span><span class="pln">removeEventListener</span><span class="pun">(</span><span class="str">"mousemove"</span><span class="pun">,</span><span class="pln"> moved</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let dist </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">clientX </span><span class="pun">-</span><span class="pln"> lastX</span><span class="pun">;</span><span class="pln">
      let newWidth </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">max</span><span class="pun">(</span><span class="lit">10</span><span class="pun">,</span><span class="pln"> bar</span><span class="pun">.</span><span class="pln">offsetWidth </span><span class="pun">+</span><span class="pln"> dist</span><span class="pun">);</span><span class="pln">
      bar</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> newWidth </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
      lastX </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">clientX</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	لاحظ أنّ معالج <code>"mousemove"</code> يُسجَّل على النافذة كلها، حتى لو خرج المؤشر عن الشريط أثناء تغيير عرضه، وذلك طالما أن الزر مضغوط عليه ونكون لا زلنا نريد تعديل العرض. لكن يجب أن يتوقف تغيير الحجم فور تركنا لزر الفأرة، ولضمان ذلك فإننا نستخدم خاصية <code>buttons</code> -لاحظ أنها جمع وليست مفردة-، والتي تخبرنا عن الأزرار التي نضغط عليها الآن، فإذا كانت صِفرًا، فهذا يعني أن الأزرار كلها متروكة وحرة؛ أما إذا كانت ثمة أزرار مضغوط عليها، فستكون قيمة الخاصية هي مجموع رموز هذه الأزرار، إذ يحصل الزر الأيسر على الرمز 1 والأيمن على الرمز 2، والأوسط على 4، فإذا كان الزران الأيمن والأيسر مضغوطًا عليهما معًا، فستكون قيمة <code>buttons</code> هي 3.
</p>

<p>
	لاحظ أن ترتيب هذه الرموز يختلف عن الترتيب الذي تستخدمه <code>button</code>، حيث يأتي الزر الأوسط قبل الأيمن، وذلك لِما ذكرنا من قبل أنّ واجهة برمجة المتصفح تفتقر إلى التناسق.
</p>

<h3>
	أحداث اللمس
</h3>

<p>
	صُمِّم أسلوب المتصفح ذو الواجهة الرسومية في الأيام التي كانت فيها شاشات اللمس نادرةً جدًا في السوق، ولهذا لم توضع في الحسبان كثيرًا، لذا فقد كان على المتصفحات التي جاءت في أولى الهواتف ذات شاشات اللمس التظاهر بأن أحداث اللمس هي نفسها أحداث الفأرة -وإن كان إلى حد ما-، فإذا نقرت على شاشتك فستحصل على الأحداث <code>"mousedown"</code> و<code>"mouseup"</code> و<code>"click"</code>.
</p>

<p>
	لكن هذا المنظور ركيك بما أنّ شاشة اللمس تعمل بأسلوب مختلف تمامًا عن الفأرة، فلا توجد هنا أزرار متعددة ولا يمكن تتبع الإصبع إذا لم يكن على الشاشة فعلًا لمحاكاة <code>"mousemove"</code>، كما تسمح الشاشة بعدة أصابع عليها في الوقت نفسه.
</p>

<p>
	لا تغطي أحداث الفأرة شاشات اللمس إلا في حالات مباشرة، فإذا أضفت معالج <code>"click"</code> إلى زر ما، فسيستطيع المستخدِم الذي يستعمل شاشة لمس استخدام الزر هنا، لكن لن يعمل مثال الشريط السابق على شاشة لمس.
</p>

<p>
	كما أن هناك أنواعًا بعينها من الأحداث تنطلق عند التفاعل باللمس فقط، فحين يلمس الإصبع الشاشة، فستحصل على حدث <code>"touchstart"</code>، وإذا تحرك أثناء اللمس فستُطلَق أحداث <code>"touchmove"</code>؛ أما إذا ابتعد عن الشاشة فستحصل على حدث <code>"touchend"</code>.
</p>

<p>
	تمتلك كائنات هذه الأحداث خاصية <code>touches</code> التي تحمل كائنًا شبيهًا بالمصفوفة من نقاط لكل منها خصائص <code>cientX</code> و<code>clientY</code> و<code>pageX</code> و<code>pageY</code>، وذلك لأنّ كثيرًا من شاشات اللمس تدعم اللمس المتعدد في الوقت نفسه، فلا يكون لتلك الأحداث مجموعةً واحدةً فقط من الأحداث.
</p>

<p>
	تستطيع فعل شيء مشابه لتظهر دوائر حمراء حول كل إصبع يلمس الشاشة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_35" style=""><span class="pun">&lt;</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
  dot </span><span class="pun">{</span><span class="pln"> position</span><span class="pun">:</span><span class="pln"> absolute</span><span class="pun">;</span><span class="pln"> display</span><span class="pun">:</span><span class="pln"> block</span><span class="pun">;</span><span class="pln">
        border</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2px</span><span class="pln"> solid red</span><span class="pun">;</span><span class="pln"> border</span><span class="pun">-</span><span class="pln">radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">50px</span><span class="pun">;</span><span class="pln">
        height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100px</span><span class="pun">;</span><span class="pln"> width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">100px</span><span class="pun">;</span><span class="pln"> </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Touch</span><span class="pln"> </span><span class="kwd">this</span><span class="pln"> page</span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> update</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let dot</span><span class="pun">;</span><span class="pln"> dot </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"dot"</span><span class="pun">);)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      dot</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&lt;</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let </span><span class="pun">{</span><span class="pln">pageX</span><span class="pun">,</span><span class="pln"> pageY</span><span class="pun">}</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">touches</span><span class="pun">[</span><span class="pln">i</span><span class="pun">];</span><span class="pln">
      let dot </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">"dot"</span><span class="pun">);</span><span class="pln">
      dot</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">pageX </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
      dot</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">pageY </span><span class="pun">-</span><span class="pln"> </span><span class="lit">50</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
      document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">dot</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"touchstart"</span><span class="pun">,</span><span class="pln"> update</span><span class="pun">);</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"touchmove"</span><span class="pun">,</span><span class="pln"> update</span><span class="pun">);</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"touchend"</span><span class="pun">,</span><span class="pln"> update</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	قد ترغب في استدعاء <code>preventDefault</code> في معالجات أحداث اللمس لتنسخ -أي تُعدِّل- سلوك المتصفح الافتراضي الذي قد يشمل تمرير الشاشة عند تحريك الإصبع للأعلى أو الأسفل لتمرير الصفحة، ولمنع أحداث المؤشر من الانطلاق، والتي سيكون لديك معالج لها أيضًا.
</p>

<h2>
	أحداث التمرير
</h2>

<p>
	ينطلق حدث <code>"scroll"</code> كلما مُرِّر عنصر ما، ونستطيع استخدام ذلك في معرفة ما الذي ينظر إليه المستخدِم الآن كي نوقف عمليات التحريك أو الرسوم المتحركة التي خرجت من النطاق المرئي للشاشة -أو لإرسال هذه البيانات إلى جامعي بيانات المستخدِمين من مخترقي الخصوصية-، أو لإظهار تلميح لمدى تقدم المستخدِم في الصفحة بتظليل عنوان في الفهرس أو إظهار رقم الصفحة أو غير ذلك.
</p>

<p>
	يرسم المثال التالي شريط تقدم أعلى المستند ويحدِّثه ليمتلئ كلما مررت للأسفل:
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_9408_26" style=""><span class="tag">&lt;style&gt;</span><span class="pln">
  </span><span class="com">#progress {</span><span class="pln">
    border</span><span class="pun">-</span><span class="pln">bottom</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2px</span><span class="pln"> solid blue</span><span class="pun">;</span><span class="pln">
    width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    position</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">fixed</span><span class="pun">;</span><span class="pln">
    top</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> left</span><span class="pun">:</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"progress"</span><span class="tag">&gt;&lt;/div&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  </span><span class="com">// اكتب محتوى هنا</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">createTextNode</span><span class="pun">(</span><span class="pln">
    </span><span class="str">"supercalifragilisticexpialidocious "</span><span class="pun">.</span><span class="pln">repeat</span><span class="pun">(</span><span class="lit">1000</span><span class="pun">)));</span><span class="pln">

  let bar </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"#progress"</span><span class="pun">);</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"scroll"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let max </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">scrollHeight </span><span class="pun">-</span><span class="pln"> innerHeight</span><span class="pun">;</span><span class="pln">
    bar</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">width </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">$</span><span class="pun">{(</span><span class="pln">pageYOffset </span><span class="pun">/</span><span class="pln"> max</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">100</span><span class="pun">}%`;</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	إذا كان الموضع <code>position</code> الخاص بالعنصر ثابتًا <code>fixed</code>، فسيتصرف كما لو كان له موضع مطلق <code>absolute</code>، لكنه يمنعه من التمرير مع بقية المستند، ويُترجَم هذا التأثير في الحالات الواقعية على أساس حالة شريط التقدم في مثالنا، إلا أننا نريد جعل الشريط ظاهرًا في أعلى الصفحة أو المستند بغض النظر عن موضع التمرير فيه، ويتغير عرضه ليوضِّح مدى تقدمنا في المستند، كما سنستخدم <code>%</code> بدلًا من <code>px</code> لضبط وحدة العرض كي يكون حجم العنصر نسبيًا لعرض الصفحة.
</p>

<p>
	تعطينا الرابطة العامة <code>innerheight</code> ارتفاع النافذة التي يجب طرحها من الارتفاع الكلي الذي يمكن تمريره، بحيث لا يمكن التمرير بعد الوصول إلى نهاية المستند، ولدينا بالمثل <code>innerwidth</code> للعرض الخاص بالنافذة؛ وتحصل على النسبة الخاصة بشريط التمرير عبر قسمة موضع التمرير الحالي <code>pageYoffset</code> على أقصى موضع تمرير وتضرب الناتج في 100.
</p>

<p>
	كذلك لا يُستدعى معالج الحدث إلا بعد وقوع التمرير نفسه، وبالتالي لن يمنع استدعاء <code>preventDefault</code> وقوع حدث التمرير.
</p>

<h2>
	أحداث التنشيط Focus Events
</h2>

<p>
	إذا كان عنصر ما نشطًا، فسيطلق المتصفح حدث <code>"focus"</code> عليه، وإذا فقد نشاطه ذلك بانتقال التركيز منه إلى غيره فإنه يحصل على حدث <code>"blur"</code>. لا ينتشر هذان الحدثان على عكس الأحداث السابقة، ولا يُبلَّغ المعالج الذي على العنصر الأصل حين يكون عنصر فرعي نشطًا أو حين يفقد نشاطه.
</p>

<p>
	يوضح المثال التالي نص مساعدة لحقل نصي نشط:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_37" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;الاسم:</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> data</span><span class="pun">-</span><span class="pln">help</span><span class="pun">=</span><span class="str">"اسمك الكامل"</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;العمر:</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> data</span><span class="pun">-</span><span class="pln">help</span><span class="pun">=</span><span class="str">"عمرك بالأعوام"</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p id</span><span class="pun">=</span><span class="str">"help"</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let help </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"#help"</span><span class="pun">);</span><span class="pln">
  let fields </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelectorAll</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let field of </span><span class="typ">Array</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">fields</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    field</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"focus"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let text </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">target</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"data-help"</span><span class="pun">);</span><span class="pln">
      help</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> text</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    field</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"blur"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      help</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> </span><span class="str">""</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	سيستقبل كائن النافذة حدثي <code>"focus"</code> و<code>"blur"</code> كلما تحرك المستخدِم من وإلى نافذة المتصفح أو اللسان النشط الذي يكون المستند معروضًا فيه.
</p>

<h2>
	حدث التحميل Load Event
</h2>

<p>
	ينطلق حدث <code>"load"</code> على النافذة وكائنات متن المستند إذا أتمت صفحة ما تحميلها، وهو يُستخدَم عادةً لجدولة إجراءات التهيئة initialization actions التي تكون في حاجة إلى بناء المستند كاملًا.
</p>

<p>
	تذكَّر أنّ محتوى وسوم <code>&lt;script&gt;</code> يُشغَّل تلقائيًا إذا قابل الوسم، وقد يكون هذا قبل أوانه إذا احتاجت السكربت إلى فعل شيء بأجزاء المستند التي تظهر بعد وسم <code>&lt;script&gt;</code> مثلًا.
</p>

<p>
	تمتلك العناصر التي تحمِّل ملفًا خارجيًا -مثل الصور ووسوم السكربت- حدث <code>"load"</code> كذلك، حيث يوضِّح تحميل الملفات التي تشير إليها، وهذه الأحداث -أي أحداث التحميل- لا تنتشر propagate أي مثل الأحداث المتعلقة بالنشاط focus.
</p>

<p>
	حين نغلق صفحةً ما أو نذهب بعيدًا عنها إلى غيرها عند فتح رابط مثلًا، فسينطلق حدث <code>"beforeunload"</code>، والاستخدام الرئيسي لهذا الحدث هو منع المستخدِم من فقد أي عمل كان يعمله إذا أُغلق المستند، فإذا منعت السلوك الافتراضي لهذا الحدث وضبطت خاصية <code>returnvalue</code> على كائن الحدث لتكون سلسلة نصية؛ فسيظهر المتصفح للمستخدِم صندوقًا حواريًا يسأله إذا كان يرغب في ترك الصفحة حقًا.
</p>

<p>
	قد يحتوي هذا الصندوق الحواري سلسلتك النصية التي تحاول الحفاظ على بيانات المستخدِم فعليًا، لكن كثيرًا من المواقع كانت تستخدِم هذا الأسلوب من أجل وضع المستخدِمين في حيرة وخداعهم ليبقوا على صفحات هذه المواقع ويشاهدوا الإعلانات الموجودة هناك، لكن المتصفحات لم تَعُد تظهر هذه الرسائل في الغالب.
</p>

<h2>
	الأحداث وحلقات الأحداث التكرارية
</h2>

<p>
	تتصرف معالجات أحداث المتصفح في سياق حلقة الحدث التكرارية مثل إشعارات غير متزامنة كما ناقشنا في <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1304/" rel="">البرمجة غير المتزامنة في جافاسكريبت</a>، وتُجدوَل حين يقع الحدث، لكن عليها الانتظار حتى تنتهي السكربتات العاملة أولًا قبل أن تعمل هي.
</p>

<p>
	يعني هذا أنه إذا كانت حلقة الحدث التكرارية مرتبطةً بمهمة أخرى، فإن أي تفاعل مع الصفحة -وهو ما يحدث أثناء الأحداث- سيتأخر حتى نجد وقتًا لمعالجته، لذلك إذا كانت لديك مهام كثيرة مجدولة إما مع معالج حدث يستغرق وقتًا طويلًا أو مع معالجات لأحداث قصيرة لكنها كثيرة جدًا، فستصير الصفحة بطيئةً ومزعجةً في الاستخدام.
</p>

<p>
	أما إذا أردت فعل شيء يستغرق وقتًا في الخلفية دون التأثير على أداء الصفحة، فستوفِّر المتصفحات شيئًا اسمه عمّال الويب web workers، ويُعَدّ ذاك العامل في جافاسكربت مهمةً تعمل إلى جانب السكربت الرئيسية على الخط الزمني الخاص بها.
</p>

<p>
	تخيَّل أنّ تربيع عدد ما يمثل عمليةً حسابيةً طويلةً وثقيلة، وأننا نريد إجراءها في خيط thread منفصل، فنكتب حينها ملفًا اسمه <code>code/squareworker.js</code> يستجيب للرسائل بحساب التربيع وإرساله في رسالة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_39" style=""><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"message"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  postMessage</span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">data </span><span class="pun">*</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	لا تشارك العمال نطاقها العام أو أي بيانات أخرى مع البيئة الرئيسية للسكربت لتجنب مشاكل الخيوط المتعددة التي تتعامل مع البيانات نفسها، وعليك التواصل معها عبر إرسال الرسائل ذهابًا وعودة.
</p>

<p>
	ينتج المثال التالي عاملًا يشغِّل تلك السكربت ويرسل بعض الرسائل إليها ثم يخرج استجاباتها:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_41" style=""><span class="pln">let squareWorker </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Worker</span><span class="pun">(</span><span class="str">"code/squareworker.js"</span><span class="pun">);</span><span class="pln">
squareWorker</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"message"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"The worker responded:"</span><span class="pun">,</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">
squareWorker</span><span class="pun">.</span><span class="pln">postMessage</span><span class="pun">(</span><span class="lit">10</span><span class="pun">);</span><span class="pln">
squareWorker</span><span class="pun">.</span><span class="pln">postMessage</span><span class="pun">(</span><span class="lit">24</span><span class="pun">);</span></pre>

<p>
	ترسِل دالة <code>postMessage</code> رسالةً تطلق حدث <code>"message"</code> في المستقبِِل، كما ترسل السكربت التي أنشأت العامل رسائلًا، وتستقبلها من خلال كائن <code>Worker</code>؛ في حين يخاطب العامل السكربت التي أنشأته عبر الإرسال مباشرةً على نطاقها العام والاستماع إليه.
</p>

<p>
	يمكن للقيم التي تمثل على أساس JSON أن تُرسَل على أساس رسائل، وسيَستقبل الطرف المقابل نسخةً منها بدلًا من القيمة نفسها.
</p>

<h2>
	المؤقتات Timers
</h2>

<p>
	رأينا دالة <code>setTimeout</code> في البرمجة غير المتزامنة في جافاسكريبت وكيف أنها تجدوِل دالةً أخرى لتُستدعى لاحقًا بعد وقت محدد يُحسب بالميلي ثانية، لكن أحيانًا قد تحتاج إلى إلغاء دالة جدولتها بنفسك سابقًا، ويتم هذا بتخزين القيمة التي أعادتها <code>setTimeout</code> واستدعاء <code>clearTimeout</code> عليها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_43" style=""><span class="pln">let bombTimer </span><span class="pun">=</span><span class="pln"> setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"بووم!"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">500</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">random</span><span class="pun">()</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">0.5</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// 50% احتمال</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Defused."</span><span class="pun">);</span><span class="pln">
  clearTimeout</span><span class="pun">(</span><span class="pln">bombTimer</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تعمل الدالة <code>cancelAnimationFrame</code> بالطريقة نفسها التي تعمل بها <code>clearTimeout</code>، أي أنّ استدعاءها على قيمة أعادتها <code>requestAnimationFrame</code>، سيلغي هذا الإطار، وذلك على افتراض أنه لم يُستدعى بعد، كما تُستخدَم مجموعة مشابهة من الدوال هي <code>setInterval</code> و<code>clearInterval</code> لضبط المؤقتات التي يجب أن تتكرر كل عدد معيّن X من الميللي ثانية.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_45" style=""><span class="pln">let ticks </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
let clock </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"tick"</span><span class="pun">,</span><span class="pln"> ticks</span><span class="pun">++);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">ticks </span><span class="pun">==</span><span class="pln"> </span><span class="lit">10</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    clearInterval</span><span class="pun">(</span><span class="pln">clock</span><span class="pun">);</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"stop."</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">200</span><span class="pun">);</span></pre>

<h2>
	الارتداد Debouncing
</h2>

<p>
	بعض أنواع الأحداث لها قابلية الانطلاق بسرعة وعدة مرات متتابعة مثل حدثي <code>"mousemove"</code> و<code>"scroll"</code>، وحين نعالج هذه الأحداث، يجب الحذر من فعل أيّ شيء يستغرق وقتًا كبيرًا وإلا فسيأخذ معالجنا وقتًا طويلًا بحيث يبطئ التفاعل مع المستند.
</p>

<p>
	إذا أردت فعل شيء مهم بهذا المعالج، فيمكنك استخدام <code>setTimeout</code> للتأكد أنك لا تفعله كثيرًا، ويسمى هذا بارتداد الحدث event debouncing.
</p>

<p>
	إذا كتب المستخدم شيئًا ما فإننا نريد التفاعل معه في المثال الأول هنا، لكن لا نريد فعل ذلك فور كل حدث إدخال، فإذا كان يكتب بسرعة، فسنريد الانتظار حتى يتوقف ولو لبرهة قصيرة، كما نضبط مهلةً بدلًا من تنفيذ إجراء مباشرةً على معالج الحدث، إلى جانب حذفنا لأي مهلة زمنية timeout سابقة أقرب من تأخير مهلتنا الزمنية، كما ستُلغى المهلة الزمنية التي من الحدث السابق.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_47" style=""><span class="pun">&lt;</span><span class="pln">textarea</span><span class="pun">&gt;اكتب</span><span class="pln"> </span><span class="pun">شيئًا</span><span class="pln"> </span><span class="pun">هنا...&lt;/</span><span class="pln">textarea</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let textarea </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"textarea"</span><span class="pun">);</span><span class="pln">
  let timeout</span><span class="pun">;</span><span class="pln">
  textarea</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"input"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    clearTimeout</span><span class="pun">(</span><span class="pln">timeout</span><span class="pun">);</span><span class="pln">
    timeout </span><span class="pun">=</span><span class="pln"> setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Typed!"</span><span class="pun">),</span><span class="pln"> </span><span class="lit">500</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	إنّ إعطاء قيمة غير معرَّفة لـ <code>clearTimeout</code> أو استدعاءها على مهلة زمنية أُطلِقت سلفًا، ليس له أي تأثير، وعليه فلا داعي لأن تخشى شيئًا إذا أردت استدعاءها، بل افعل ذلك لكل حدث إذا شئت.
</p>

<p>
	تستطيع استخدام نمط pattern مختلف قليلًا إذا أردت المباعدة بين الاستجابات بحيث تكون مفصولةً بأقل مدة زمنية محددة، لكن في الوقت نفسه تريد إطلاقها أثناء سلسلة أحداث -وليس بعدها-، حيث يمكنك مثلًا الاستجابة إلى أحداث <code>"mousemove"</code> بعرض الإحداثيات الحالية للفأرة، لكن تعرضها كل 250 مللي ثانية، انظر منا يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_49" style=""><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let scheduled </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">
  window</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">"mousemove"</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">scheduled</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      setTimeout</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln">
          </span><span class="pun">`</span><span class="typ">Mouse</span><span class="pln"> at $</span><span class="pun">{</span><span class="pln">scheduled</span><span class="pun">.</span><span class="pln">pageX</span><span class="pun">},</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">scheduled</span><span class="pun">.</span><span class="pln">pageY</span><span class="pun">}`;</span><span class="pln">
        scheduled </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">},</span><span class="pln"> </span><span class="lit">250</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    scheduled </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	خاتمة
</h2>

<p>
	تمكننا معالجات الأحداث من استشعار الأحداث التي تحدث في صفحة الويب والتفاعل معها، ويُستخدَم التابع <code>addEventListener</code> لتسجيل مثل تلك المعالِجات.
</p>

<p>
	كل حدث له نوع يعرِّفه مثل <code>"keydown"</code> و<code>"focus"</code> وغيرهما، وتُستدعى أغلب الأحداث على عنصر DOM بعينه، ثم ينتشر إلى أسلاف هذا العنصر سامحًا للمعالجات المرتبطة بتلك العناصر أن تعالجها.
</p>

<p>
	عندما يُستدعى معالج حدث ما، فسيُمرَّر إليه كائن حدث بمعلومات إضافية عن الحدث، وذلك الكائن له تابع يسمح لنا بإيقاف الانتشار <code>stopPropagation</code>، وآخر يمنع معالجة المتصفح الافتراضية للحدث <code>preventDefault</code>.
</p>

<p>
	إذا ضغطنا مفتاحًا فسيطلق هذا حدثَي <code>"keydown"</code> و<code>"keyup"</code>؛ أما الضغط على زر الفأرة فسيطلق الأحداث <code>"mousedown"</code>، و<code>"mouseup"</code>، و<code>"click"</code>، في حين يطلق تحريك المؤشر أحداث <code>"mousemove"</code>، كما يطلق التفاعل مع شاشات اللمس الأحداث <code>"touchstart"</code> و<code>"touchmove"</code> و<code>"touchend"</code>.
</p>

<p>
	يمكن استشعار التمرير scrolling من خلال حدث <code>"scroll"</code>، كما يمكن استشعار تغيرات النافذة محل التركيز أو النافذة النشطة من خلال حدثَي <code>"focus"</code> و<code>"blur"</code>، وإذا أنهى المستند تحميله، فسينطلق حدث <code>"load"</code> للنافذة.
</p>

<h2>
	تدريبات
</h2>

<h3>
	بالون
</h3>

<p>
	اكتب صفحةً تعرض بالونًا باستخدام الصورة الرمزية للبالون balloon emoji?، بحيث يكبر هذا البالون بنسبة 10% إذا ضغطت السهم المشير للأعلى، ويصغر إذا ضغطت على السهم المشير للأسفل بنسبة 10%.
</p>

<p>
	تستطيع التحكم في حجم النص -بما أن الصورة الرمزية ما هي إلا نص- بضبط <code>font-size</code> لخاصية <code>style.fontSize</code> على العنصر الأصل لها، وتذكر ألا تنسى ذكر وحدة القياس في القيمة مثل كتابة <code>10px</code>.
</p>

<p>
	تأكد من أن المفاتيح تغير البالون فقط دون تمرير الصفحة، وأن أسماء مفاتيح الأسهم هي <code>"ArrowUp"</code> و<code>"ArrowDown"</code>. وإذا نجح ذلك فأضف ميزةً أخرى هي انفجار البالون عند بلوغه حجمًا معينًا، ويعني هذا هنا استبدال الصورة الرمزية للانفجار ? بالصورة الرمزية للبالون، ويُزال معالج الحدث هنا كي لا تستطيع تغيير حجم الانفجار.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_51" style=""><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;?&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// شيفرتك هنا</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	يجب تسجيل معالج لحدث <code>"keydown"</code> وأن تنظر في <code>event.key</code> لتعرف هل ضُغِط السهم الأعلى أم الأسفل، ويمكن الحفاظ على الحجم الحالي في رابطة binding كي تستطيع بناء الحجم الجديد عليه، وسيكون هذا نافعًا -سواءً الرابطة، أو نمط البالون في الـ DOM- في تعريف الدالة التي تحدث الحجم، وذلك كي تستدعيها من معالج الحدث الخاص بك، وربما كذلك بمجرد البدء لضبط الحجم الابتدائي.
</p>

<p>
	تستطيع تغيير البالون إلى انفجار عبر استبدال عقدة النص بأخرى باستخدام <code>replaceChild</code>، أو بضبط خاصية <code>textContent</code> لعقدتها الأصل على سلسلة نصية جديدة.
</p>

<h3>
	ذيل الفأرة
</h3>

<p>
	كان يعجب الناس في الأيام الأولى لجافاسكربت أن تكون صفحات المواقع ملأى بالصور المتحركة المبهرجة والتأثيرات البراقة، وأحد هذه التأثيرات هو إعطاء ذيل لمؤشر الفأرة في صورة سلسلة من العناصر التي تتبع المؤشر في حركته في الصفحة، وفي هذا التدريب نريدك تنفيذ ذيل للمؤشر.
</p>

<p>
	استخدم عناصر <code>&lt;div&gt;</code> التي لها مواضع مطلقة بحجم ثابت ولون خلفية -حيث يمكنك النظر في فقرة ضغطات الفأرة لتكون مرجعًا لك-، وأنشئ مجموعةً من هذه العناصر واعرضها عند تحرك المؤشر لتكون في عقبه مباشرةً.
</p>

<p>
	لديك عدة طرق تحل بها هذا التدريب، والأمر إليك إذا شئت جعل الحل سهلًا أو صعبًا؛ فالحل السهل هو أن تجعل عددًا ثابتًا من العناصر وتجعلها في دورة لتحرك العنصر التالي إلى الموضع الحالي للفأرة في كل مرة يقع حدث <code>"mousemove"</code>.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_53" style=""><span class="pun">&lt;</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">.</span><span class="pln">trail </span><span class="pun">{</span><span class="pln"> </span><span class="com">/* className for the trail elements */</span><span class="pln">
    position</span><span class="pun">:</span><span class="pln"> absolute</span><span class="pun">;</span><span class="pln">
    height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">6px</span><span class="pun">;</span><span class="pln"> width</span><span class="pun">:</span><span class="pln"> </span><span class="lit">6px</span><span class="pun">;</span><span class="pln">
    border</span><span class="pun">-</span><span class="pln">radius</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3px</span><span class="pun">;</span><span class="pln">
    background</span><span class="pun">:</span><span class="pln"> teal</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  body </span><span class="pun">{</span><span class="pln">
    height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">300px</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">style</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// ضع شيفرتك هنا.</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	يفضَّل إنشاء العناصر باستخدام حلقة تكرارية، وتُلحق العناصر بالمستند كي تظهر، كما ستحتاج إلى تخزين هذه العناصر في مصفوفة كي تستطيع الوصول إليها لاحقًا لتغيير موقعها.
</p>

<p>
	يمكن تنفيذ الدورة عليها بتوفير متغير عدّاد وإضافة 1 إليه في كل مرة ينطلق فيها حدث <code>"mousemove"</code>، بعدها يمكن استخدام عامل <code>‎% elements.length</code> للحصول على فهرس مصفوفة صالحة لاختيار العنصر الذي تريد موضعته خلال الحدث الذي لديك.
</p>

<p>
	تستطيع أيضًا تحقيق تأثير جميل عبر نمذجة نظام فيزيائي بسيط باستخدام حدث <code>"mousemove"</code> لتحديث زوج من الرابطات التي تتبع موضع الفأرة، ثم استخدام <code>requestAnimationFrame</code> لمحاكاة العناصر اللاحقة التي تنجذب إلى موضع مؤشر الفأرة. حدِّث موضعها في كل خطوة تحريك وفقًا لموضعها النسبي للمؤشر وربما سرعتها أيضًا إذا خزنتها لكل عنصر، وسنترك لك التفكير في طريقة جيدة لفعل ذلك.
</p>

<h3>
	التبويبات Tabs
</h3>

<p>
	تُستخدم اللوحات المبوَّبة في واجهات المستخدِم بكثرة، إذ تسمح لك باختيار لوحة من خلال اختيار التبويب الذي في رأسها، وفي هذا التدريب ستنفذ واجهةً مبوبةً بسيطةً.
</p>

<p>
	اكتب الدالة <code>asTabs</code> التي تأخذ عقدة DOM وتنشئ واجهةً مبوبةً تعرض العناصر الفرعية من تلك العقدة، ويجب إدخال قائمة من عناصر <code>&lt;buttons&gt;</code> في أعلى العقدة، بحيث يكون عندك واحد لكل عنصر فرعي، كما يحتوي على نص يأتي من سمة <code>data-tabname</code> للفرع.
</p>

<p>
	يجب أن تكون كل العناصر الفرعية الأصلية مخفيةً عدا واحدًا منها -أي تعطيها قيمة <code>none</code> لنمط <code>display</code>-، كما يمكن اختيار العقدة المرئية الآن عبر النقر على الأزرار.
</p>

<p>
	وسِّع ذلك إذا نجح معك لتنشئ نمطًا لزر التبويب المختار يختلف عما حوله ليُعلم أي تبويب تم اختياره.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9408_55" style=""><span class="pun">&lt;</span><span class="pln">tab</span><span class="pun">-</span><span class="pln">panel</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">div data</span><span class="pun">-</span><span class="pln">tabname</span><span class="pun">=</span><span class="str">"one"</span><span class="pun">&gt;التبويب</span><span class="pln"> </span><span class="pun">الأول&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">div data</span><span class="pun">-</span><span class="pln">tabname</span><span class="pun">=</span><span class="str">"two"</span><span class="pun">&gt;التبويب</span><span class="pln"> </span><span class="pun">الثاني&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">div data</span><span class="pun">-</span><span class="pln">tabname</span><span class="pun">=</span><span class="str">"three"</span><span class="pun">&gt;التبويب</span><span class="pln"> </span><span class="pun">الثالث&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">tab</span><span class="pun">-</span><span class="pln">panel</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> asTabs</span><span class="pun">(</span><span class="pln">node</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ضع شيفرتك هنا.</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  asTabs</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"tab-panel"</span><span class="pun">));</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h4>
	إرشادات الحل
</h4>

<p>
	إحدى المشاكل التي قد تواجهها هي أنك لن تستطيع استخدام خاصية <code>childNodes</code> الخاصة بالعقدة استخدامًا مباشرًا مثل تجميعة لعقد التبويب، ذلك لأنك حين تضيف الأزرار، إذ أنها ستصبح عقدًا فرعيةً كذلك وتنتهي في ذلك الكائن لأنه هيكل بيانات حي، كذلك فإنّ العقد النصية المنشأة للمسافات الفارغة بين العقد هي عناصر فرعية <code>childNodes</code> أيضًا، ويجب ألا تحصل على تبويبات خاصة بها، وبالتالي استخدم <code>children</code> بدلًا من <code>childNodes</code> لتجاهل العقد النصية.
</p>

<p>
	قد تبدأ ببناء مصفوفة تبويبات كي يكون لديك وصول سهل لها، وتستطيع تخزين الكائنات التي تحتوي كلًا من لوحة التبويب والزر الخاص بها لتنفيذ التنسيق styling الخاص بالأزرار، كما ننصحك بكتابة دالة منفصلة لتغيير التبويبات؛ فإما تخزين التبويب المختار سابقًا وتغيير الأنماط المطلوب إخفاؤها وعرض الجديدة فقط، أو تحديث نمط جميع التبويبات في كل مرة يُختار تبويب جديد فيها.
</p>

<p>
	قد تريد استدعاء هذه الدالة فورًا لتبدأ الواجهة مع أول تبويب مرئي.
</p>

<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/15_event.html" rel="external nofollow">للفصل الخامس عشر من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
	<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%86%D8%AF-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1322/" rel="">نموذج كائن المستند في جافاسكريبت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D8%A7%D9%84%D9%85%D8%A4%D8%B4%D8%B1-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1154/" rel="">أحداث المؤشر والتعامل معها في جافاسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D9%85%D8%AE%D8%B5%D8%B5%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1144/" rel="">إنشاء أحداث مخصصة في المتصفح عبر جافاسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D8%A7%D9%84%D9%81%D8%A3%D8%B1%D8%A9-%D9%81%D9%8A-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1151/" rel="">أحداث الفأرة في المتصفح والتعامل معها في جافاسكربت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%85%D8%AF%D8%AE%D9%84-%D8%A5%D9%84%D9%89-%D8%A3%D8%AD%D8%AF%D8%A7%D8%AB-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D9%88%D9%83%D9%8A%D9%81%D9%8A%D8%A9-%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9%D9%87%D8%A7-%D8%B9%D8%A8%D8%B1-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1140/" rel="">مدخل إلى أحداث المتصفح وكيفية التعامل معها عبر جافاسكربت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1354</guid><pubDate>Sat, 02 Oct 2021 15:00:00 +0000</pubDate></item><item><title>&#x645;&#x644;&#x641;&#x627;&#x62A; &#x62A;&#x639;&#x631;&#x64A;&#x641; &#x627;&#x644;&#x627;&#x631;&#x62A;&#x628;&#x627;&#x637; &#x648;&#x636;&#x628;&#x637;&#x647;&#x627; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D8%B6%D8%A8%D8%B7%D9%87%D8%A7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1330/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_10/615e83e84f636_------JavaScript.png.7b16355dfad4dd1fd9b6a1b04b2366a6.png" /></p>

<p>
	ملفات تعربف الارتباط هي بيانات نصية تُخزَّن مباشرةً في ذاكرة المتصفح، وهي في الواقع جزء من بروتوكول HTTP تُعرِّفها المواصفات <a data-ss1634999020="1" href="https://tools.ietf.org/html/rfc6265" rel="external nofollow">RFC 6265</a>، ويضبط الخادم عادةً هذه الملفات مستخدمًا ترويسة الاستجابة <code>Set-Cookie</code>، ثم يضيفها المتصفح إلى كل طلب -تقريبًا- يُرسل إلى النطاق ذاته مستخدمًا الترويسة <code>Cookie</code>.
</p>

<p>
	يُعَد الاستيثاق authentication أكثر الحالات شيوعًا لاستعمال ملفات تعريف الارتبابط، وذلك بسبب ما يلي:
</p>

<ol>
<li>
		يستخدم الخادم الترويسة <code>Set-Cookie</code> في الاستجابة أثناء تسجيل الدخول لإعداد ملف تعريف ارتباط له مُعرِّف جلسة عمل session فريد.
	</li>
	<li>
		عند إرسال الطلب في المرة القادمة إلى نفس النطاق، سيُرسل المتصفح ملف تعريف الارتباط عبر الشبكة باستخدام الترويسة <code>Cookie</code>.
	</li>
	<li>
		وبالتالي سيعلم الخادم الجهة التي أرسلت الطلب.
	</li>
</ol>
<p>
	يمكن الوصول إلى ملفات تعريف الارتباط من المتصفح باستخدام الخاصية <code>document.cookie</code>، وستواجهك الكثير من النقاط المربكة عند استخدام ملفات تعريف الارتباط والخيارات المتعلقة بها، وسنغطي هذه النقاط بالتفصيل في هذا المقال.
</p>

<h2>
	القراءة من الخاصية document.cookie
</h2>

<p>
	لتعرف إذا ما كان متصفحك يُخزِّن أي ملفات تعريف ارتباط عائدة لموقع ما، استخدم الأمر:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_7" style="">
<span class="pln">alert</span><span class="pun">(</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">);</span><span class="pln"> </span><span class="com">// cookie1=value1; cookie2=value2;...</span></pre>

<p>
	تتكون قيمة الخاصية <code>document.cookie</code> من أزواج <code>name=value</code> تفصل بينها فاصلة منقوطة ";"، حيث يمثل كل منها ملف تعريف ارتباط منفصل.
</p>

<p>
	لإيجاد ملف تعريف ارتباط معين يمكن فصل قيمة الخاصية <code>document.cookie</code> بالاستفادة من الفاصلة المنقوطة ";"، ثم إيجاد الاسم المطلوب، وننفذ ذلك باستخدام تعابير برمجية نظامية أو مصفوفة دوال، وسنترك ذلك تمرينًا للقارئ، كما ستجد في نهاية المقال دوالًا مساعدةً للتعامل مع ملفات تعريف الارتباط.
</p>

<h2>
	الكتابة إلى الخاصية document.cookie
</h2>

<p>
	يمكن أن نكتب قيمًا ضمن الخاصية <code>document.cookie</code>، وهي ليست خاصية بيانات، وإنما هي أشبه بدالة وصول accessor (<a data-ss1634999020="1" href="https://academy.hsoub.com/programming/javascript/%D8%AC%D8%A7%D9%84%D8%A8%D8%A7%D8%AA-%D8%A7%D9%84%D8%AE%D8%A7%D8%B5%D9%8A%D8%A7%D8%AA-%D9%88%D8%B6%D8%A7%D8%A8%D8%B7%D8%A7%D8%AA%D9%87%D8%A7-getters-and-setters-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r882/" rel="">ضابط setter/ جالب getter</a>)، لذا لا بدّ من التعامل معها بخصوصية.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		تعدّل عملية الكتابة ملفات تعريف الارتباط المحددة ضمن أمر الكتابة فقط، ولا تغير بقية الملفات.
	</p>
</blockquote>

<p>
	يضبط الاستدعاء التالي مثلًا ملف تعريف ارتباط بالاسم user على القيمة John:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_9" style="">
<span class="pln">document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John"</span><span class="pun">;</span><span class="pln"> 
alert</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">cookie</span><span class="pun">);</span><span class="pln"> </span></pre>

<p>
	ستشاهد عند تنفيذ الشيفرة السابقة ملفات ارتباط عدةً غالبًا، لأن الأمر <code>= document.cookie</code> يغيِّر قيمة ملف الارتباط الذي يحمل الاسم <code>user</code> فقط.
</p>

<p>
	يمكن أن يتألف الاسم أو القيمة تقنيًا من أي محارف، لكن للإبقاء على تنسيق قابل للقراءة، ينبغي ترميز المحارف الخاصة باستخدام الدالة المدمجة <code>encodeURIComponent</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_11" style="">
<span class="com">// ينبغي ترميز المحارف الخاصة</span><span class="pln">
let name </span><span class="pun">=</span><span class="pln"> </span><span class="str">"my name"</span><span class="pun">;</span><span class="pln">
let value </span><span class="pun">=</span><span class="pln"> </span><span class="str">"John Smith"</span><span class="pln">

</span><span class="com">// encodes the cookie as my%20name=John%20Smith</span><span class="pln">
document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> encodeURIComponent</span><span class="pun">(</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'='</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> encodeURIComponent</span><span class="pun">(</span><span class="pln">value</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">cookie</span><span class="pun">);</span><span class="pln"> </span><span class="com">// ...; my%20name=John%20Smith</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		توجد حدود عدة للعمل مع ملفات الارتباط، وهي:
	</p>

	<ul>
<li>
			لا ينبغي أن يزيد حجم الزوج <code>name=value</code> مع الدالة <code>encodeURIComponent</code> عن 4 كيلوبايت، وبهذا لن نتمكن من تخزين قيم ضخمة في ملفات الارتباط.
		</li>
		<li>
			يُحدَّد العدد الكلي لملفات الارتباط المخزنة ضمن نطاق ما بعشرين أو أكثر بقليل، وذلك حسب المتصفح المستخدَم.
		</li>
	</ul>
</blockquote>

<p>
	لملفات الارتباط خيارات عدة، وينبغي ضبط بعضها لأهميته، حيث توضع هذه الخيارات بعد الأزواج <code>key=value</code>، وتفصل بينها فواصل منقوطة ";":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_13" style="">
<span class="pln">document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"</span></pre>

<h3>
	الخيار path
</h3>

<ul>
<li>
		<strong><code>path=/mypath</code></strong>: ينبغي أن يكون مسار العنوان مُطلقًا absolute، بحيث يمكن الوصول إلى ملفات الارتباط ضمن جميع الصفحات التي تقع تحت هذا المسار، أما القيمة الافتراضية فهي "المسار الحالي"، فلو كان المسار <code>path=/admin</code>، فستكون الملفات مرئيةً في الصفحة الحالية <code>admin</code> وفي أي صفحة تحتها <code>admin/something/</code>، لكن ليس في صفحات مثل <code>home/</code> أو <code>adminpage/</code>، ويُضبط هذا الخيار عادةً على القيمة <code>/=path</code> لتتمكن كل صفحات الموقع من الوصول إلى ملفات الارتباط.
	</li>
</ul>
<h3>
	الخيار domain
</h3>

<ul>
<li>
		<strong><code>domain=site.com</code></strong>: يحدد هذا الخيار المكان الذي يمكن الوصول منه إلى ملفات الارتباط، لكن لن نتمكن عمليًا من تحديد أي نطاقات نريد، إذ لا يمكن الوصول إلى ملفات الارتباط افتراضيًا سوى من النطاق الذي أعدّها، فلو أعد الموقع <code>hsoub.com</code> ملفات الارتباط، فلن يتمكن الموقع <code>site.com</code> من الوصول إليها، لكن الأمر المربك فعلًا، هو أننا لن نصل إلى ملفات الارتباط ضمن النطاقات الفرعية مثل <code>academy.hsoub.com</code>!.
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_15" style="">
<span class="com">// hsoub.com في الموقع </span><span class="pln">
document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John"</span><span class="pln">

</span><span class="com">// academy.hsoub.com في الموقع </span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">cookie</span><span class="pun">);</span><span class="pln"> </span><span class="com">// no user</span></pre>

<p>
	لا توجد طريقة تُمكِّن نطاقًا من المستوى الثاني من الوصول إلى ملفات الارتباط التي يُعدّها نطاق آخر، إذ تمنعنا أمور أمنية من تخزين بيانات حساسة لا يجب أن تظهر سوى في الموقع داخل ملفات الارتباط، لكن يمكن -إن أردنا- السماح للنطاق الفرعي <code>academy.hsoub.com</code> بالوصول إلى ملفات الارتباط، وذلك بضبط الخيار <code>domain</code> عند إعداد ملف ارتباط في النطاق <code>hsoub.com</code>، على قيمة النطاق الجذر <code>domain=hsoub.com</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_17" style="">
<span class="com">// at hsoub.com</span><span class="pln">
</span><span class="com">// الوصول لملف الارتباط من النطاقات الفرعية</span><span class="pln">
</span><span class="com">// make the cookie accessible on any subdomain *.hsoub.com:</span><span class="pln">
document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John; domain=hsoub.com"</span><span class="pln">

</span><span class="com">// لاحقًا</span><span class="pln">

</span><span class="com">// academy.hsoub.com في </span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">cookie</span><span class="pun">);</span><span class="pln"> </span><span class="com">// has cookie user=John</span></pre>

<p>
	يعمل الأمر <code>domain=.hsoub.com</code> -لاحظ النقطة قبل اسم النطاق- بالطريقة ذاتها لأسباب تاريخية، حيث سيسمح بالوصول إلى ملفات الارتباط من النطاقات الفرعية، ولا بدّ من استخدام هذا الأسلوب القديم إذا أردنا دعم المتصفحات القديمة.
</p>

<p>
	إذًا سيسمح لنا الخيار <code>domain</code> بجعل النطاقات الفرعية قادرةً على الوصول إلى ملفات الارتباط.
</p>

<h3>
	الخياران expire و max-age
</h3>

<p>
	إذا لم يتضمن ملف الارتباط أيًا من هذين الخيارين، فسيختفي الملف افتراضيًا مع إغلاق المتصفح، ويُدعى هذا النوع من ملفات الارتباط "ملفات ارتباط جلسة العمل"، ولنحافظ على ملف الارتباط بعد إغلاق المتصفح فلابدّ من ضبط أحد الخيارين <code>expires</code> أو <code>max-age</code>، وإليك مثالًا عن ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_19" style="">
<span class="pln">expires</span><span class="pun">=</span><span class="typ">Tue</span><span class="pun">,</span><span class="pln"> </span><span class="lit">19</span><span class="pln"> </span><span class="typ">Jan</span><span class="pln"> </span><span class="lit">2038</span><span class="pln"> </span><span class="lit">03</span><span class="pun">:</span><span class="lit">14</span><span class="pun">:</span><span class="lit">07</span><span class="pln"> GMT</span></pre>

<p>
	يحدد تاريخ انتهاء الصلاحية الوقت الذي سيحذف فيه المتصفح ملف الارتباط، وينبغي أن يكون التاريخ بالتنسيق السابق تمامًا ووفق توقيت GMT، ويمكن أن نستخدم الدالة <code>date.toUTCString</code> للحصول على التاريخ بهذا التنسيق، حيث يمكن على سبيل المثال ضبط فترة انتهاء الصلاحية بعد يوم كامل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_21" style="">
<span class="com">//بعد يوم من الآن</span><span class="pln">
let date </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">(</span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">86400e3</span><span class="pun">);</span><span class="pln">
date </span><span class="pun">=</span><span class="pln"> date</span><span class="pun">.</span><span class="pln">toUTCString</span><span class="pun">();</span><span class="pln">
document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John; expires="</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> date</span><span class="pun">;</span></pre>

<p>
	سيُحذف الملف إذا ضبطنا قيمة الخيار <code>expires</code> على قيمة في الماضي.
</p>

<ul>
<li>
		<strong><code>max-age=3600</code></strong>: يمثل الخيار <code>max-age</code> بديلًا عن <code>expires</code>، ويعبّر عن زمن انتهاء صلاحية ملف الارتباط بالثواني ابتداءً من اللحظة الحالية، وسيُحذف ملف الارتباط إذا ضُبط على القيمة صفر أو أي قيمة سالبة.
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_23" style="">
<span class="com">// سيحذف الملف بعد ساعة </span><span class="pln">
document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John; max-age=3600"</span><span class="pun">;</span><span class="pln">

</span><span class="com">// سيحذف الملف مباشرة</span><span class="pln">
document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John; max-age=0"</span><span class="pun">;</span></pre>

<h3>
	الخيار secure
</h3>

<ul>
<li>
		<strong><code>secure</code></strong>: يجب نقل ملفات الارتباط من خلال بروتوكول HTTPS فقط.
	</li>
</ul>
<p>
	لو أعددنا ملف ارتباط ضمن الموقع <a data-ss1634999020="1" href="http://hsoub.xn--com-nwe" ipsnoembed="true" rel="external nofollow">http://hsoub.com،</a> فسيظهر افتراضيًا أيضًا ضمن الموقع <a data-ss1634999020="1" href="https://hsoub.xn--com-nwe" ipsnoembed="true" rel="external nofollow">https://hsoub.com،</a> والعكس بالعكس، فملفات الارتباط متعلقة بالنطاقات ولا تميّز بين البروتوكولات، لكن يمكن تفادي ذلك باستخدام الخيار <code>secure</code>، إذ لن تظهر ملفات الارتباط التي يُعدها النطاق <a data-ss1634999020="1" href="https://hsoub.com" ipsnoembed="true" rel="external">https://hsoub.com</a> ضمن النطاق <a data-ss1634999020="1" href="http://hsoub.xn--com-nwe" ipsnoembed="true" rel="external nofollow">http://hsoub.com،</a> لأن استخدام هذا الخيار مهم في منع نقل البيانات الحساسة عبر بروتوكول HTTP غير المشفر.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_25" style="">
<span class="com">// https:// بافتراض أننا حاليًا في </span><span class="pln">
</span><span class="com">// ضبط ملف الارتباط ليكون آمنًا</span><span class="pln">
document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> </span><span class="str">"user=John; secure"</span><span class="pun">;</span></pre>

<h3>
	الخيار samesite
</h3>

<p>
	وهو خيار من خيارات الأمان، وقد صُمم للحماية ضد الهجوم المعروف باسم "تزوير الطلبات ما بين الصفحات cross-site request forgery" أو اختصارًا XSFR، ولفهم الآلية التي يعمل بها هذا الخيار، ومتى يكون مفيدًا لنلق نظرةً على الهجوم XSFR.
</p>

<h3>
	الهجوم XSFR
</h3>

<p>
	لنفترض أنك سجلت دخولك إلى الموقع <code>bank.com</code>، وبالتالي ستحصل على ملف ارتباط استيثاق من هذا الموقع، وسيرسل متصفحك هذا الملف إلى الموقع مع كل طلب حتى يميّزك وينفذ لك عمليات ماليةً حساسةً مثلًا، ولنتخيل الآن أنك تتصفح الشابكة في نافذة أخرى ووصل بك الأمر إلى الموقع المشبوه <code>evil.com</code>، الذي يمتلك شيفرة JavaScript. ترسل إلى الموقع bank.com النموذج <code>&lt;form action="https://bank.com/pay"&gt;</code> الذي يحتوي حقولًا تهيئ لعملية تحويل مالي إلى حساب المخترق، وسيرسل المتصفح ملف الارتباط في كل مرة تزور فيها الموقع <code>bank.com</code>، حتى لو أُرسل النموذج من الموقع <code>evil.com</code>، وهكذا سيميُّزك البنك وينفذ عملية الدفع؛ هذا هو الهجوم XSFR.
</p>

<p style="text-align: center;">
	<img alt="xsrf_attack_1.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78323" data-unique="aa1jwlkbz" src="https://academy.hsoub.com/uploads/monthly_2021_09/xsrf_attack_1.png.2fe2f95823a2cb74debbcede7e2db69c.png"></p>

<p>
	طبعًا تحمي البنوك الحقيقية نفسها من هذا الهجوم، فجميع النماذج التي يولّدها الموقع <code>bank.com</code> تحتوي على حقل مميز يدعى مفتاح الحماية المشفّر protection token من هجوم XSRF، والتي لا يمكن للصفحات المشبوهة توليدها أو استخلاصها من صفحة أخرى، وبالتالي ستستطيع هذه الصفحات إرسال النماذج، لكن لا يمكن أن تحصل على بيانات من المواقع المستهدفة، وسيتحقق الموقع <code>bank.com</code> من وجود مفتاح الحماية المشفر في كل نموذج يتلقاه. تستغرق طريقة الحماية هذه وقتًا لإنجازها، إذ يجب التحقق من وجود المفتاح في كل نموذج، كما ينبغي التحقق من كل طلب.
</p>

<h3>
	استخدام الخيار samsite
</h3>

<p>
	يؤمّن هذا الخيار طريقةً للحماية من الهجوم السابق، ولا يتطلب -نظريًا- استخدام مفتاح الحماية المشفر، ولهذا الخيار قيمتان، هما:
</p>

<ul>
<li>
		<code>samesite=strict</code>: وهو مشابه لاستخدام الخيار بلا قيمة، حيث لن يُرسل ملف الارتباط -عند استخدام هذه القيمة- إذا لم يكن المستخدم ضمن النطاق نفسه. وبعبارة أخرى، لن تُرسل ملفات الارتباط سواءً تبع المستخدم رابطًا ضمن بريده الإلكتروني أم أرسل نموذجًا من موقع مشبوه أو نفّذ عمليةً خارج النطاق.
	</li>
</ul>
<p>
	عندما يكون لملف ارتباط الاستيثاق الخيار <code>samesite</code>، فلن تكون هناك فرصة لتحقيق هجوم XSRF، لأن النموذج الذي يرسله موقع مشبوه <code>evil.com</code> سيأتي دون ملفات ارتباط، وبالتالي لن يميز الموقع <code>bank.com</code> المستخدم ولن يتابع عملية الدفع، كما لن ترسل ملفات الارتباط فعليًا في هذه الحالة.
</p>

<p>
	يمكن الاعتماد على أسلوب الحماية هذا، لكن مع ذلك يمكن أن نقلق، فعندما يتتبع المستخدم رابطًا مشروعًا إلى الموقع <code>bank.com</code> -مثل أن يكون ضمن الملاحظات التي يوّلدها الموقع-؛ سيتفاجأ المستخدم أن الموقع لن يميّزه، ويمكن أن نتخطى هذا الأمر باستخدام نوعين من ملفات الارتباط، حيث يُستخدم أحدهما لتمييز المستخدم لأغراض عامة مثل أن تلقي التحية عليه "مرحبًا أحمد"، أما النوع الآخر فيُستخدم لعمليات تغيير البيانات وسيُزود بالخيار <code>samesite=strict</code>، بهذا سيرى المستخدم الذي يصل إلى الموقع <code>bank.com</code> من موقع آخر رسالة ترحيب، لكن العمليات المالية لن تُنفَّذ إلا من الموقع نفسه، بعد إرسال ملف الارتباط الثاني.
</p>

<ul>
<li>
		<p>
			<code>samesite=lax</code>: وهو خيار أقل تشددًا، يحمي من هجوم XSRF ولا يهدد تجربة المستخدم، حيث يمنع المتصفح من إرسال ملفات الارتباط التي تأتي من خارج الموقع، لكنه يضيف بعض الاستثناءات.
		</p>

		<p>
			تُرسل ملفات الارتباط ذات الخيار <code>samesite=lax</code> إذا تحقق الشرطان التاليان:
		</p>
	</li>
</ul>
<ol>
<li>
		نوع طلب HTTP آمن (GET مثلًا وليس POST)، وستجد قائمةً كاملةً بطلبات HTTP الآمنة في التوصيفات <a data-ss1634999020="1" href="https://tools.ietf.org/html/rfc7231" rel="external nofollow">RFC7231 specification</a>، لكن مبدئيًا، ستستخدم هذه الطلبات في قراءة البيانات وليس في كتابتها، فلا ينبغي لها تنفيذ عمليات لتغيير البيانات، ويُتبع الرابط دائمًا بطلب آمن من النوع GET.
	</li>
	<li>
		أن تُنفِّذ العملية انتقالًا إلى عنوان عالي المستوى (تُغيِّر العنوان في شريط عناوين المتصفح)، وهو أمر محقق عادةً، لكن إذا جرى الانتقال إلى عنوان من خلال نافذة ضمنية <code>&lt;iframe&gt;</code> فلن يكون انتقالًا عالي المستوى، ولا تنفذ طلبات JavaScript عبر الشبكة أي تنقلات كونها لن تتناسب مع هذه العملية.
	</li>
</ol>
<p>
	لذلك نرى أن كل ما يفعله الخيار <code>samesite=lax</code> هو السماح بامتلاك عمليات التنقل الأساسية ملفات ارتباط، مثل عمليات الانتقال من ملاحظة يولدها موقع إلى الموقع ذاته، فهي تحقق الشرطين السابقين، لكن ستفقد العمليات الأكثر تعقيدًا -مثل طلبات الشبكة بين موقعين مختلفين- ملفات الارتباط، لذا استخدم هذا الخيار إن كان الأمر مناسبًا لك، إذ لن يؤثر ذلك غالبًا على تجربة المستخدم وسيؤمن لك الحماية.
</p>

<p>
	وعلى الرغم من أن استخدام الخيار <code>samesite</code> أمر جيد، لكنك ستواجه العائق المهم الذي يتمحور حول تجاهل المتصفحات القديمة -2017 وما قبلها- لهذا الخيار، بحيث ستخترَق المتصفحات القديمة إذا اعتمدت على هذه الطريقة في الحماية فقط. لكن يمكننا استخدام <code>samesite</code> مع معايير حماية أخرى، مثل مفاتيح الحماية المشفرة من هجمات xsrf، وبذلك سنضيف طبقة حماية جديدة، وبالتالي سنتمكن غالبًا من تفادي هجمات xsrf حين يتوقف دعم المتصفحات القديمة مستقبلًا.
</p>

<h3>
	الخيار httpOnly
</h3>

<p>
	ليس لهذا الخيار علاقة بلغة JavaScript، لكن لا بدّ من ذكره حرصًا على اكتمال الصورة.
</p>

<p>
	يُستخدم خادم الويب الترويسة <code>Set-Cookie</code> لإعداد ملف الارتباط، كما يمكن للخادم استخدام الخيار <code>httpOnly</code> الذي يمنع أي شيفرة JavaScript من الوصول إلى ملف الارتباط، إذ لا يمكن عندها رؤية ملف الارتباط أو التعامل معه باستعمال الأمر <code>document.cookie</code>، ويستعمل هذا الخيار عاملًا احترازيًا لحماية المستخدم من هجمات معينة، يدفع فيها المخترق شيفرة JavaScript إلى صفحة ويب وينتظر الضحية ليزورها، وبالطبع لا ينبغي أن يتمكن المخترقون من دفع شيفراتهم إلى موقعنا، لكن قد نترك وراءنا ثغرات تمكنهم من ذلك. وإذا حدث شيء كهذا وزار المستخدم صفحة ويب تحتوي على شيفرة لمخترقين، فستُنفَّذ هذه الشيفرة وسيصل المخترق إلى <code>document.cookie</code>، وبالتالي إلى ملف الارتباط التي يحتوي على معلومات الاستيثاق.
</p>

<p>
	لكن إذا استُخدم الخيار <code>httpOnly</code> فلن تتمكن الخاصية <code>document.cookie</code> من رؤية ملف الارتباط، وسيبقى المستخدم محميًا.
</p>

<h2>
	الدوال التي تتعامل مع ملفات الارتباط
</h2>

<p>
	سنبين مجموعةً صغيرةً من الدوال التي تتعامل مع ملفات الارتباط، وهي ملاءمة أكثر من التعديلات اليدوية على الخاصية <code>document.cookie</code>، وستجد العديد من المكتبات التي ستساعدك في إنجاز ذلك، لكننا سنستعرض هذه الدوال على سبيل التجربة.
</p>

<h3>
	الدالة (getCookie(name
</h3>

<p>
	إنّ الطريقة الأقصر للوصول إلى ملف الارتباط ستكون باستخدام <a data-ss1634999020="1" href="https://javascript.info/regular-expressions" rel="external nofollow">التعابير النظامية</a>. حيث تعيد الدالة <code>(getCookie(name</code> ملف ارتباط باسم محدد <code>name</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_27" style="">
<span class="com">// تعيد الدالة ملف ارتباط باسم محدد</span><span class="pln">
</span><span class="com">// أو تعيد كائنًا غير محدد إن لم يوجد الملف</span><span class="pln">
</span><span class="kwd">function</span><span class="pln"> getCookie</span><span class="pun">(</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let matches </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">cookie</span><span class="pun">.</span><span class="pln">match</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">RegExp</span><span class="pun">(</span><span class="pln">
    </span><span class="str">"(?:^|; )"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> name</span><span class="pun">.</span><span class="pln">replace</span><span class="pun">(</span><span class="str">/([\.$?*|{}\(\)\[\]\\\/\+^])/</span><span class="pln">g</span><span class="pun">,</span><span class="pln"> </span><span class="str">'\\$1'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"=([^;]*)"</span><span class="pln">
  </span><span class="pun">));</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> matches </span><span class="pun">?</span><span class="pln"> decodeURIComponent</span><span class="pun">(</span><span class="pln">matches</span><span class="pun">[</span><span class="lit">1</span><span class="pun">])</span><span class="pln"> </span><span class="pun">:</span><span class="pln"> </span><span class="kwd">undefined</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يوّلد الكائن <code>new RegExp</code> ديناميكيًا ليُطابق اسم ملف الارتباط <code>&lt;name=&lt;value</code>. لاحظ أن قيمة ملف الارتباط مرمّزة، لذلك تستخدم الدالة <code>getCookie</code> الدالة <code>decodeURIComponent</code> لفك الترميز.
</p>

<h3>
	الدالة (setCookie(name, value, options
</h3>

<p>
	تجعل هذه الدالة القيمة <code>value</code> اسمًا لملف الارتباط <code>name</code>، وتضبط خيار المسار افتراضيًا على القيمة <code>=/path</code>، ويمكن تعديلها لإضافة قيم افتراضية لخيارات أخرى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_29" style="">
<span class="kwd">function</span><span class="pln"> setCookie</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">,</span><span class="pln"> options </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  options </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    path</span><span class="pun">:</span><span class="pln"> </span><span class="str">'/'</span><span class="pun">,</span><span class="pln">
    </span><span class="com">// إضافة قيم افتراضية أخرى</span><span class="pln">
    </span><span class="pun">...</span><span class="pln">options
  </span><span class="pun">};</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">options</span><span class="pun">.</span><span class="pln">expires instanceof </span><span class="typ">Date</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    options</span><span class="pun">.</span><span class="pln">expires </span><span class="pun">=</span><span class="pln"> options</span><span class="pun">.</span><span class="pln">expires</span><span class="pun">.</span><span class="pln">toUTCString</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  let updatedCookie </span><span class="pun">=</span><span class="pln"> encodeURIComponent</span><span class="pun">(</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"="</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> encodeURIComponent</span><span class="pun">(</span><span class="pln">value</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let optionKey in options</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    updatedCookie </span><span class="pun">+=</span><span class="pln"> </span><span class="str">"; "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> optionKey</span><span class="pun">;</span><span class="pln">
    let optionValue </span><span class="pun">=</span><span class="pln"> options</span><span class="pun">[</span><span class="pln">optionKey</span><span class="pun">];</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">optionValue </span><span class="pun">!==</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      updatedCookie </span><span class="pun">+=</span><span class="pln"> </span><span class="str">"="</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> optionValue</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  document</span><span class="pun">.</span><span class="pln">cookie </span><span class="pun">=</span><span class="pln"> updatedCookie</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// :مثال الاستخدام</span><span class="pln">
setCookie</span><span class="pun">(</span><span class="str">'user'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'John'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">secure</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">,</span><span class="pln"> </span><span class="str">'max-age'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3600</span><span class="pun">});</span></pre>

<h3>
	الدالة (deleteCookie(name
</h3>

<p>
	تُستدعى هذه الدالة بقيم سالبة لخيار فترة الصلاحية <code>max-age</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_199_31" style="">
<span class="kwd">function</span><span class="pln"> deleteCookie</span><span class="pun">(</span><span class="pln">name</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  setCookie</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> </span><span class="str">""</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'max-age'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pln">
  </span><span class="pun">})</span><span class="pln">
</span><span class="pun">}</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		لابدّ من استخدام خياري المسار والنطاق تمامًا كما جرى إعدادهما، وذلك عند حذف أو تحديث محتويات ملف ارتباط. راجع <a data-ss1634999020="1" href="https://javascript.info/article/cookie/cookie.js" rel="external nofollow">cookie.js</a>.
	</p>
</blockquote>

<h2>
	ملفات ارتباط الطرف الثالث Third-party cookie
</h2>

<p>
	يُعَد ملف الارتباط من "طرف ثالث" إذا أعدّه نطاق مختلف عن الصفحة التي تزورها، فمثلًا:
</p>

<ol>
<li>
		تُحمِّل صفحة من الموقع <code>hsoub.com</code> شريطًا دعائيًا من موقع آخر <code>&lt;img src="https://ads.com/banner.png"</code>‎<code>&gt;</code>.
	</li>
	<li>
		يمكن أن يهيئ الخادم الذي يستضيف الموقع <code>ads.com</code> الترويسة <code>Set-Cookie</code> متضمنةً ملف الارتباط <code>id=1234</code> مثلًا، وسيكون هذا الملف -الذي يعود أصلًا إلى النطاق <code>ads.com</code>- مرئيًا فقط لصفحات هذا النطاق.
	</li>
</ol>
<p style="text-align: center;">
	<img alt="set_get_cookie_2.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78322" data-unique="gfhzmqrrg" src="https://academy.hsoub.com/uploads/monthly_2021_09/set_get_cookie_2.png.ce3522f4e9c1f86d572c08c0c7c5625c.png"></p>

<ol start="3">
<li>
		عندما ننتقل إلى <code>ads.com</code> في المرة القادمة، سيحصل الخادم على مُعرِّف <code>id</code> ملف الارتباط وسيميِّز المستخدم.
	</li>
</ol>
<p style="text-align: center;">
	<img alt="set_cookie_3.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78321" data-unique="5ggcpyqq9" src="https://academy.hsoub.com/uploads/monthly_2021_09/set_cookie_3.png.49c0c40ec14101d52855771b10459b90.png"></p>

<ol start="4">
<li>
		سيظهر الأمر الأكثر أهميةً عند انتقال المستخدم من الموقع <code>hsoub.com</code> إلى موقع آخر يحوي شريطًا دعائيًا، إذ سيحصل النطاق <code>ads.com</code> على ملف الارتباط لأنه ينتمي إليه، وبالتالي سيميّز المستخدم وسيتعقبه عند التنقل بين المواقع.
	</li>
</ol>
<p style="text-align: center;">
	<img alt="get_track_visitor_4.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78320" data-unique="0covtb1jy" src="https://academy.hsoub.com/uploads/monthly_2021_09/get_track_visitor_4.png.7ddc9d30df6f38960a1669771cb0ced1.png"></p>

<p>
	استُخدمت ملفات ارتباط الطرف الثالث تقليديًا لخدمات الدعاية وتعقب المستخدمين نظرًا لطبيعتها، إذ ترتبط هذه الملفات بالنطاقات التي أعدَّتها، حيث يمكن للنطاق <code>ads.com</code> أن يتعقب المستخدم ذاته عبر مواقع مختلفة إذا أمكن لهذه المواقع الوصول إلى هذا النطاق.
</p>

<p>
	تسمح المتصفحات بتعطيل عمل هذا النوع من ملفات الارتباط لأن المستخدم لا يحب أن يُتتبع عادةً، كما تتبع بعض المتصفحات الحديثة سياسات خاصةً بملفات الارتباط هذه، وهي:
</p>

<ul>
<li>
		لا يسمح المتصفح Safari بملفات ارتباط الطرف الثالث.
	</li>
	<li>
		يأتي المتصفح Firefox بقائمة سوداء تضم مجموعة نطاقات تحجب ملفات الارتباط التي تعود لها.
	</li>
</ul>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		إذا حمّلنا سكربتًا من نطاق عائد لطرف ثالث مثل <code>&lt;script src="https://google-analytics.com/analytics.js"&gt;</code>، واستخدم السكربت الخاصية <code>document.cookie</code> لضبط ملف ارتباط؛ فلن يعَد ملف الارتباط حينها من طرف ثالث.
	</p>

	<p>
		إذا أعدّ سكربت ملف ارتباط، فسينتمي ملف الارتباط إلى نطاق الصفحة الحالية، بغض النظر عن مصدر السكربت،.
	</p>
</blockquote>

<h2>
	التشريع الأوروبي GDPR
</h2>

<p>
	لا يتعلق الموضوع باللغة إطلاقًا، لكن انتبه إليه عندما تعمل على إعداد ملفات الارتباط، حيث يجبر تشريع أوروبي يُدعى "GDPR" صفحات الويب على احترام مجموعة من القواعد التي تضمن خصوصية المستخدم، وتتطلب إحدى هذه القواعد إذنًا صريحًا لتتبع ملفات الارتباط العائدة لمستخدم، وتذكر أنه يخص ملفات ارتباط التتبع أو التعريف أو الاستيثاق، فإذا أردنا إعداد ملف ارتباط لحفظ بعض المعلومات -لا لتتبع أو تحديد مستخدم- فلنا كامل الحرية، ولابدّ من إذن صريح من المستخدم في حال تتبعه أو التعرف عليه.
</p>

<p>
	تطبق مواقع الويب هذا التشريع بأسلوبين، ولا بدّ أنك لاحظتهما أثناء تصفحك للإنترنت:
</p>

<ol>
<li>
		عندما تحاول صفحة الويب تتبع مستخدمين مستَوثَقين، فلا بدّ عندها أن يحتوي نموذج التسجيل على صندوق تحقق checkbox يحمل عنوانًا مثل "قبول سياسة الخصوصية"، والتي تصف كيفية استخدام ملفات الارتباط، ولا بدّ أن يختاره المستخدم حتى يتابع، عندها ستستخدم الصفحة ملفات ارتباط الاستيثاق بحرية.
	</li>
	<li>
		عندما تحاول صفحة الويب تتبع كل مستخدم فستعرض الصفحة -إن أرادت تنفيذ ذلك بصورة قانونية- شاشة بداية splash screen للقادمين الجدد، وتطلب منهم الموافقة على استخدام ملفات الارتباط. عندها ستتمكن الصفحة من إنشاء ملفات ارتباط، ثم ستسمح للمستخدم بمتابعة محتوى الصفحة. قد يكون الأمر مربكًا للمستخدم، فلا أحد يحب النقر الإجباري على شاشة بداية قبل متابعة المحتوى، لكن التشريع GDPR يتطلب موافقةً صريحةً.
	</li>
</ol>
<p>
	لا يتعلق التشريع GDPR بملفات الارتباط، بل بأمور أخرى تتعلق بالخصوصية أيضًا، لكنها خارج إطار موضوعنا.
</p>

<h2>
	خلاصة
</h2>

<p>
	توفِّر الخاصية <code>document.cookie</code> وصولًا إلى ملفات الارتباط cookies، مع ملاحظة ما يلي:
</p>

<ul>
<li>
		لا تعدل عمليات الكتابة سوى ملفات الارتباط التي تذكرها الخاصية.
	</li>
	<li>
		لا بدّ من ترميز الزوج "اسم/قيمة" name/value.
	</li>
	<li>
		لا يتجاوز حجم ملف الارتباط عتبة 4 كيلوبايت، ولا يتجاوز عدد ملفات الارتباط 20 ملفًا في الموقع، وفقًا للمتصفح المستخدم.
	</li>
</ul>
<p>
	خيارات ملف الارتباط:
</p>

<ul>
<li>
		<code>/=path</code>: قيمته الافتراضية هي المسار الحالي، ويجعل ملف الارتباط مرئيًا فقط تحت المسار المحدد.
	</li>
	<li>
		<code>domain=site.com</code>: يكون ملف الارتباط مرئيًا ضمن النطاق المحدد فقط افتراضيًا، لكن ستكون ملفات الارتباط مرئيةً ضمن النطاقات الفرعية أيضًا إذا صُرّح عن النطاق.
	</li>
	<li>
		<code>expires</code> أو <code>max-age</code>: تحدد زمن انتهاء صلاحية ملف الارتباط، وبدونها يُحذف ملف الارتباط عند إغلاق المتصفح.
	</li>
	<li>
		<code>secure</code>: لا بدّ من استخدام بروتوكول HTTPS حتى تُرى ملفات الارتباط.
	</li>
	<li>
		<code>samesite</code>: يمنع المتصفح من إرسال ملفات الارتباط مع الطلبات الواردة من خارج نطاق الموقع، وسيساعد ذلك في إيقاف هجمات XSRF.
	</li>
</ul>
<p>
	إضافةً إلى ذلك:
</p>

<ul>
<li>
		يمكن أن يمنع المتصفح استخدام ملفات الارتباط القادمة من طرف ثالث افتراضيًا مثل المتصفح Safari.
	</li>
	<li>
		ينبغي التقيد بالتشريع GDPR عند إعداد ملفات ارتباط لتتبع مستخدمين من الاتحاد الأوروبي.
	</li>
</ul>
<p>
	ترجمة -وبتصرف- للفصل <a data-ss1634999020="1" href="https://javascript.info/cookie" rel="external nofollow">cookies, document.cookies</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a data-ss1634999020="1" href="https://academy.hsoub.com/programming/javascript/%D8%A2%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%85%D8%B9-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1301/" rel="">آليات الاتصال المستمر مع الخادم في جافاسكربت</a>
	</li>
	<li>
		<a data-ss1634999020="1" href="https://academy.hsoub.com/programming/php/%D8%A7%D9%84%D8%AC%D9%84%D8%B3%D8%A7%D8%AA-%D9%88%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%AA%D8%B9%D8%B1%D9%8A%D9%81-%D8%A7%D9%84%D8%A7%D8%B1%D8%AA%D8%A8%D8%A7%D8%B7-%D9%88%D9%85%D9%83%D8%AA%D8%A8%D8%A9-curl-%D9%81%D9%8A-php-r1081/" rel="">الجلسات وملفات تعريف الارتباط ومكتبة cURL في PHP</a>
	</li>
	<li>
		<a data-ss1634999020="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-fetch-api-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1297/" rel="">الواجهة البرمجية Fetch <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> في جافاسكريبت</a>
	</li>
	<li>
		<a data-ss1634999020="1" href="https://academy.hsoub.com/programming/general/%D8%A7%D8%AD%D9%85-%D9%85%D9%88%D9%82%D8%B9%D9%83-%D9%85%D9%86-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%B1%D8%A7%D9%82-r864/" rel="">احم موقعك من الاختراق</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1330</guid><pubDate>Tue, 28 Sep 2021 15:00:00 +0000</pubDate></item><item><title>&#x646;&#x645;&#x648;&#x630;&#x62C; &#x643;&#x627;&#x626;&#x646; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x646;&#x62F; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x64A;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D9%86%D9%85%D9%88%D8%B0%D8%AC-%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%86%D8%AF-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1322/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_09/6155b2cfdea02_-----.png.19f567b68f6758f93d3c993e5fa953a6.png" /></p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		التخطيط قبل العمل يقلل مرات الفشل.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="78251" href="https://academy.hsoub.com/uploads/monthly_2021_09/chapter_picture_14.jpg.f697c1b81b3b277f8c0ba02da0a12b97.jpg" rel=""><img alt="chapter_picture_14.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="78251" data-unique="fgyakleon" src="https://academy.hsoub.com/uploads/monthly_2021_09/chapter_picture_14.jpg.f697c1b81b3b277f8c0ba02da0a12b97.jpg"></a>
</p>

<p>
	حين تفتح صفحة ويب في متصفحك فإن المتصفح يجلب نص HTML الخاص بها ويحلله كما يفعل المحلل parser الذي أنشأناه في <a href="https://academy.hsoub.com/programming/advance/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-r1305/" rel="">المقال الثاني عشر</a> مع البرامج، كما يبني المتصفح نموذجًا لهيكل المستند ويستخدم هذا النموذج لإظهار الصفحة كما تراها على الشاشة.
</p>

<p>
	يُعَدّ هذا التمثيل للمستند طريقةً في صناديق الاختبار sandboxes التي في برامج جافاسكربت، وهو هيكل بيانات يمكنك قراءته وتعديله، كما يتصرف على أساس هيكل بيانات حي تتغير الصفحة بتعديله لتعكس هذه التغييرات.
</p>

<h2>
	هيكل المستند
</h2>

<p>
	تخيَّل مستند HTML أنه مجموعة متشعِّبة من الصناديق، وتغلِّف فيها وسوم مثل <code>&lt;body&gt;</code> و<code>&lt;‎/body&gt;</code> وسومًا أخرى، وهذه الوسوم تحتوي بدورها على وسوم أو نصوص أخرى.
</p>

<p>
	انظر هذا المثال من <a href="https://academy.hsoub.com/programming/javascript/%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D8%A8%D8%AA%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-r1310/" rel="">المقال االسابق</a>: جافاسكربت والمتصفحات.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_7" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">
  </span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">My home page</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;/head&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">My home page</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;</span><span class="pln">Hello, I am Marijn and this is my home page.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;</span><span class="pln">I also wrote a book! Read it
      </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"http://eloquentjavascript.net"</span><span class="tag">&gt;</span><span class="pln">here</span><span class="tag">&lt;/a&gt;</span><span class="pln">.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	ستظهر الصفحة التي في هذا المثال كما يلي:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="78253" href="https://academy.hsoub.com/uploads/monthly_2021_09/html-boxes.png.d856d08341eadb0eefabf588267b89df.png" rel=""><img alt="html-boxes.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78253" data-unique="u01uk1162" src="https://academy.hsoub.com/uploads/monthly_2021_09/html-boxes.png.d856d08341eadb0eefabf588267b89df.png"></a>
</p>

<p>
	يتبع هيكل البيانات الذي يستخدمه المتصفح لتمثيل هذا المستند هذا الشكل، فهناك كائن لكل صندوق يمكننا التفاعل معه لمعرفة أشياء، مثل وسم HTML التي يمثلها والصناديق والنصوص التي يحتوي عليها، ويسمى هذا التمثيل بنموذج كائن المستند Document Object Model -أو DOM اختصارًا-.
</p>

<p>
	وتعطينا رابطة <code>document</code> العامة وصولًا إلى هذه الكائنات، وتشير خاصية <code>documentElement</code> إلى الكائن الذي يمثل وسم <code>&lt;html&gt;</code>، وبما أيّ مستند HTML فيه ترويسة Head ومتن Body، فسيحتوي على خاصيتي <code>head</code> و<code>body</code> اللتين تشيران إلى هذين العنصرين أيضًا.
</p>

<h2>
	الأشجار
</h2>

<p>
	لو أنك تذكر أشجار البُنى syntax trees التي تحدثنا عنها في المقال الثاني عشر والتي تشبه هياكلها هياكل المستندات التي في المتصفح شبهًا كبيرا؛ فكل عُقدة node تشير إلى عُقد أخرى وقد يكون للفروع children فروعًا أخرى، وهذا الشكل هو نموذج للهياكل المتشعبة حيث تحتوي العناصر على عناصر فرعية تشبهها.
</p>

<p>
	نقول على هيكل البيانات أنه شجرة إذا احتوي على هيكل ذي بنية متفرعة branching وليس فيه دورات cycles -بحيث لا يمكن للعُقدة أن تحتوي نفسها مباشرةً أو بصورة غير مباشرة-، وله جذر واحد معرَّف جيدًا، وهذا الجذر في حالة DOM هو <code>document.documentElement</code>.
</p>

<p>
	نتعرض للأشجار كثيرًا في علوم الحاسوب، فهي تُستخدَم للحفاظ على مجموعات مرتبة من البيانات، حيث يكون إدخال العناصر أو العثور عليها أسهل داخل شجرة من لو كان في مصفوفة مسطحة، وذلك إضافة إلى استخدامات أخرى مثل تمثيل الهياكل التعاودية recursive structures مثل مستندات HTML أو البرامج.
</p>

<p>
	تمتلك الشجرة عدة أنواع مختلفة من العُقد، فشجرة البُنى للغة Egg التي أنشأناها في المقال الثاني عشر من هذه السلسلة كانت لها معرِّفات identifiers وقيم values وعُقد تطبيقات application nodes، كما يمكن أن يكون لعُقد التطبيقات تلك فروعًا، في حين يكون للمعرِّفات وللقيم أوراقًا leaves أو عُقدًا دون فروع.
</p>

<p>
	ينطبق المنطق نفسه على DOM، فعُقد العناصر التي تمثِّل وسوم HTML تحدِّد هيكل المستند، ويمكن أن يكون لها عُقدًا فرعيةً child nodes، وأحد الأمثلة على تلك العُقد هو <code>document.body</code>. كذلك فإن تلك العُقد الفرعية قد تكون عُقدًا ورقيةً leaf nodes مثل النصوص أو عُقد التعليقات comment nodes.
</p>

<p>
	يملك كل كائن عُقدة في DOM خاصية <code>nodeType</code> تحتوي على رمز -أو عدد- يعرِّف نوع العُقدة، فتحمل العناصر الرمز 1 الذي يُعرَّف أيضًا على أساس خاصية ثابتة لـ <code>Node.ELEMENT_NODE</code>؛ أما عُقد النصوص التي تمثِّل أجزاءً من النصوص داخل المستند فتحصل على الرمز 3 وهو <code>Node.TEXT_NODE</code>، في حين تحمل التعليقات الرمز 8 الذي هو <code>Node.COMMENT_NODE</code>.
</p>

<p>
	يوضِّح الشكل التالي شجرة مستندنا بصورة أفضل:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="78255" href="https://academy.hsoub.com/uploads/monthly_2021_09/html-tree.png.ad27a75c1e000422ce77d60fc6959e91.png" rel=""><img alt="html-tree.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78255" data-unique="x75bs3wjo" src="https://academy.hsoub.com/uploads/monthly_2021_09/html-tree.png.ad27a75c1e000422ce77d60fc6959e91.png"></a>
</p>

<p>
	العُقد النصية هنا هي الأوراق، والأسهم توضح علاقة الأصل والفرع بين العُقد.
</p>

<h2>
	المعيار
</h2>

<p>
	لا يتلاءم استخدام رموز عددية مبهمة لتمثيل أنواع العُقد مع طبيعة جافاسكربت، وسنرى في هذا المقال أجزاءً أخرى من واجهة DOM ستبدو متعِبة ومستهجنة، وذلك لأن DOM لم يصمَّم من أجل جافاسكربت وحدها، بل يحاول أن يكون واجهة غير مرتبطة بلغة بعينها ليُستخدم في أنظمة أخرى، فلا يكون من أجل HTML وحدها بل لـ XML كذلك، وهي صيغة بيانات عامة لها بنية تشبه HTML.
</p>

<p>
	لكن مزية المعيارية هنا ليست مقنعة ولا مبررة، فالواجهة التي تتكامل تكاملًا حسنًا مع اللغة التي تستخدمها ستوفر عليك وقتًا موازنة بالواجهة التي تكون موحدة على اختلاف اللغات، وانظر خاصية <code>childNodes</code> التي في عُقد العناصر في DOM لتكون مثالًا على هذا التكامل السيء، فتلك الخاصية تحمل كائنًا شبيهًا بالمصفوفة array-like object مع خاصية <code>length</code> وخصائص معنونة بأعداد للوصول إلى العُقد الفرعية، لكنه نسخة instance من النوع <code>NodeList</code> وليس مصفوفةً حقيقيةً، لذا فليس لديه توابع مثل <code>slice</code> و<code>map</code>.
</p>

<p>
	ثم هناك مشاكل ليس لها مراد إلا سوء التصميم، فليست هناك مثلًا طريقةً لإنشاء عقدة جديدة وإضافة فروع أو سمات إليها، بل يجب عليك إنشاء العُقدة ثم إضافة الفروع والسمات واحدة واحدة باستخدام الآثار الجانبية side effects، وعلى ذلك ستكون الشيفرة التي تتعامل مع DOM طويلةً ومتكررةً وقبيحةً أيضًا.
</p>

<p>
	لكن هذه المشاكل والعيوب ليست حتميةً، فمن الممكن تصميم طرق مطوَّرة وأفضل للتعبير عن العمليات التي تنفذها أنت طالما تسمح لنا جافاسكربت بإنشاء تجريداتنا الخاصة، كما تأتي العديد من المكتبات الموجهة للبرمجة للمتصفحات بمثل تلك الأدوات.
</p>

<h2>
	التنقل داخل الشجرة
</h2>

<p>
	تحتوي عُقد DOM على روابط link كثيرة جدًا تشير إلى العُقد المجاورة لها، انظر المخطط التالي مثلًا:
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="78254" href="https://academy.hsoub.com/uploads/monthly_2021_09/html-links.png.76a001067af872f56ee47f6bf86159e5.png" rel=""><img alt="html-links.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78254" data-unique="s268cf4dx" src="https://academy.hsoub.com/uploads/monthly_2021_09/html-links.png.76a001067af872f56ee47f6bf86159e5.png"></a>
</p>

<p>
	رغم أن المخطط لا يظهر إلا رابطًا واحدًا من كل نوع إلا أنّ كل عُقدة لها خاصية <code>parentNode</code> التي تشير إلى العُقدة التي هي جزء منها إن وجدت، وبالمثل فكل عُقدة عنصر -التي تحمل النوع 1- لها خاصية <code>childNodes</code> التي تشير إلى كائن شبيه بالمصفوفة يحمل فروعه.
</p>

<p>
	تستطيع نظريًا التحرك في أي مكان داخل الشجرة باستخدام روابط الأصول والفروع هذه، لكن جافاسكربت تعطيك أيضًا وصولًا إلى عدد من الروابط الإضافية الأخرى، فتشير الخاصيتان <code>firstChild</code> و<code>lastChild</code> إلى العنصرين الفرعيين الأول والأخير، أو تكون لهما القيمة <code>null</code> للعُقد التي ليس لها فروع، وبالمثل أيضًا تشير <code>previousSibling</code> و<code>nextSibling</code> إلى العُقد المتجاورة، وهي العُقد التي لها الأصل نفسه أو الأصل الذي يظهر قبل أو بعد العُقدة مباشرةً، وستحمل <code>previousSibling</code> القيمة <code>null</code> لأول فرع لعدم وجود شيء قبله، وكذلك ستحمل <code>nextSibling</code> القيمة <code>null</code> لآخر فرع.
</p>

<p>
	لدينا أيضًا الخاصية <code>children</code> التي تشبه <code>childNodes</code> لكن لا تحتوي إلا عناصر فرعية -أي ذات النوع 1- ولا شيء آخر من بقية أنواع العُقد الفرعية، وذلك مفيد إذا لم تكن تريد العُقد النصية.
</p>

<p>
	نفضِّل استخدام الدوال التعاودية recursive functions عند التعامل مع هيكل بيانات متشعب كما في المثال أدناه، حيث تقرأ الدالة التالية المستند بحثًا عن العُقد النصية التي تحتوي على نص معطى وتُعيد <code>true</code> إذا وجدته:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_11" style="">
<span class="kwd">function</span><span class="pln"> talksAbout</span><span class="pun">(</span><span class="pln">node</span><span class="pun">,</span><span class="pln"> string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">nodeType </span><span class="pun">==</span><span class="pln"> </span><span class="typ">Node</span><span class="pun">.</span><span class="pln">ELEMENT_NODE</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let child of node</span><span class="pun">.</span><span class="pln">childNodes</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">talksAbout</span><span class="pun">(</span><span class="pln">child</span><span class="pun">,</span><span class="pln"> string</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">node</span><span class="pun">.</span><span class="pln">nodeType </span><span class="pun">==</span><span class="pln"> </span><span class="typ">Node</span><span class="pun">.</span><span class="pln">TEXT_NODE</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">nodeValue</span><span class="pun">.</span><span class="pln">indexOf</span><span class="pun">(</span><span class="pln">string</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&gt;</span><span class="pln"> </span><span class="pun">-</span><span class="lit">1</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">talksAbout</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> </span><span class="str">"book"</span><span class="pun">));</span><span class="pln">
</span><span class="com">// → true</span></pre>

<p>
	تحمل الخاصية <code>nodeValue</code> للعُقدة النصية السلسلة النصية التي تمثلها.
</p>

<h2>
	البحث عن العناصر
</h2>

<p>
	رغم أنّ التنقل بين الروابط سابقة الذكر يصلح بين الأصول parents والفروع children والأشقاء siblings، إلا أننا سنواجه مشاكل إذا أردنا البحث عن عُقدة بعينها في المستند.
</p>

<p>
	فمن السيء اتباع الطريق المعتاد من <code>document.body</code> عبر مسار ثابت من الخصائص، إذ يسمح هذا بوضع فرضيات كثيرة في برنامجنا عن الهيكل الدقيق للمستند، وهو الهيكل الذي قد تريد تغييره فيما بعد.
</p>

<p>
	تُنشأ كذلك العُقد النصية للمسافات الفارغة بين العُقد الأخرى، فوسم <code>&lt;body&gt;</code> يحمل أكثر من ثلاثة فروع والذين هم عنصر <code>&lt;h1&gt;</code> وعنصرين <code>&lt;p&gt;</code>، وإنما المسافات الفارغة بينها وقبلها وبعدها أيضًا، وبالتالي يكون سبعة فروع.
</p>

<p>
	إذا أردنا الوصول إلى سمة <code>href</code> للرابط الذي في ذلك المستند فلن نكتب "اجلب الفرع الثاني للفرع السادس من متن المستند"، بل الأفضل هو قول "اجلب الرابط الأول في المستند"، ونحن نستطيع فعل ذلك، انظر كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_13" style="">
<span class="pln">let link </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">"a"</span><span class="pun">)[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">link</span><span class="pun">.</span><span class="pln">href</span><span class="pun">);</span></pre>

<p>
	تحتوي جميع عُقد العناصر على التابع <code>getElementsByTagName</code> الذي يجمع العناصر التي تحمل اسم وسم ما، وتكون منحدرة -فروعًا مباشرةً أو غير مباشرة- من تلك العُقدة ويُعيدها على أساس كائن شبيه بالمصفوفة.
</p>

<p>
	لإيجاد عُقدة منفردة بعينها، أعطها سمة <code>id</code> واستخدم <code>document.getElementById</code>، أي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_15" style="">
<span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">My</span><span class="pln"> ostrich </span><span class="typ">Gertrude</span><span class="pun">:&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">img id</span><span class="pun">=</span><span class="str">"gertrude"</span><span class="pln"> src</span><span class="pun">=</span><span class="str">"img/ostrich.png"</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  let ostrich </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">"gertrude"</span><span class="pun">);</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">ostrich</span><span class="pun">.</span><span class="pln">src</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	هناك تابع ثالث شبيه بما سبق هو <code>getElementsByClassName</code> يبحث في محتويات عُقدة العنصر مثل <code>getElementsByTagName</code> ويجلب جميع العناصر التي لها السلسلة النصية المعطاة في سمة <code>class</code>.
</p>

<h2>
	تغيير المستند
</h2>

<p>
	يمكن تغيير كل شيء تقريبًا في هيكل البيانات الخاص ب DOM، إذ يمكن تعديل شكل شجرة المستند من خلال تغيير علاقات الأصول والفروع.
</p>

<p>
	تحتوي العُقد على التابع <code>remove</code> لإزالتها من عُقدة أباها، ولكي تضيف عُقدة فرعية إلى عُقدة عناصرية element node فيمكننا استخدام <code>appendChild</code> التي تضعها في نهاية قائمة الفروع، أو <code>insertBefore</code> التي تدخِل العُقدة المعطاة على أساس أول وسيط argument قبل العُقدة المعطاة على أساس وسيط ثاني.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_17" style="">
<span class="tag">&lt;p&gt;</span><span class="pln">One</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;</span><span class="pln">Two</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;</span><span class="pln">Three</span><span class="tag">&lt;/p&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let paragraphs </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">);</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">insertBefore</span><span class="pun">(</span><span class="pln">paragraphs</span><span class="pun">[</span><span class="lit">2</span><span class="pun">],</span><span class="pln"> paragraphs</span><span class="pun">[</span><span class="lit">0</span><span class="pun">]);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	لا يمكن للعُقدة أن توجد في المستند إلا في مكان واحد فقط، وعليه فإنّ إدخال فقرة Three في مقدمة الفقرة One سيزيلها أولًا من نهاية المستند ثم يدخلها في أوله، لنحصل على Three|One|Two، وبناءً على ذلك ستتسبب جميع العمليات التي تدخل عُقدة في مكان ما -على أساس أثر جانبي- في إزالتها من موقعها الحالي إن كان لها واحد.
</p>

<p>
	يُستخدَم التابع <code>replaceChild</code> لاستبدال عُقدة فرعية بأخرى، ويأخذ عُقدتين على أساس وسيطين، واحدة جديدة والعُقدة التي يراد تغييرها، ويجب أن تكون العُقدة المراد تغييرها عُقدة فرعية من العنصر الذي استُدعي عليه التابع، لاحظ أنّ كلًا من <code>replaceChild</code> و<code>insertBefore</code> تتوقعان العُقدة الجديدة على أساس وسيط أول لهما.
</p>

<h2>
	إنشاء العقد
</h2>

<p>
	لنقل أنك تريد كتابة سكربت يستبدل جميع الصور -أي وسوم <code>&lt;img&gt;</code>- في المستند ويضع مكانها نصوصًا في سمات <code>alt</code> لها، والتي تحدِّد نصًا بديلًا عن الصور، حيث سيحذف الصور وسيضيف عُقدًا نصيةً جديدةً لتحل محلها، كما ستُنشأ العُقد النصية باستخدام تابع <code>document.createTextNode</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_20" style="">
<span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">The</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">img src</span><span class="pun">=</span><span class="str">"img/cat.png"</span><span class="pln"> alt</span><span class="pun">=</span><span class="str">"Cat"</span><span class="pun">&gt;</span><span class="pln"> in the
  </span><span class="pun">&lt;</span><span class="pln">img src</span><span class="pun">=</span><span class="str">"img/hat.png"</span><span class="pln"> alt</span><span class="pun">=</span><span class="str">"Hat"</span><span class="pun">&gt;.&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">button onclick</span><span class="pun">=</span><span class="str">"replaceImages()"</span><span class="pun">&gt;</span><span class="typ">Replace</span><span class="pun">&lt;</span><span class="str">/button&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> replaceImages</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let images </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let i </span><span class="pun">=</span><span class="pln"> images</span><span class="pun">.</span><span class="pln">length </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> i </span><span class="pun">&gt;=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">--)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let image </span><span class="pun">=</span><span class="pln"> images</span><span class="pun">[</span><span class="pln">i</span><span class="pun">];</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">alt</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        let text </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createTextNode</span><span class="pun">(</span><span class="pln">image</span><span class="pun">.</span><span class="pln">alt</span><span class="pun">);</span><span class="pln">
        image</span><span class="pun">.</span><span class="pln">parentNode</span><span class="pun">.</span><span class="pln">replaceChild</span><span class="pun">(</span><span class="pln">text</span><span class="pun">,</span><span class="pln"> image</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	إذا كان لدينا سلسلة نصية، فستعطينا <code>createTextNode</code> عُقدةً نصية نستطيع إدخالها إلى المستند لنجعلها تظهر على الشاشة، وستبدأ الحلقة التكرارية التي ستمر على الصور من نهاية القائمة، لأن قائمة العُقد التي أعادها تابع مثل <code>getElementsByTagName</code> -أو سمة مثل <code>childNodes</code>- هي قائمة حية بمعنى أنها تتغير كلما تغير المستند، وإذا بدأنا من المقدمة وحذفنا أول صورة فسنُفقِد القائمة أول عناصرها كي تتكرر الحلقة التكرارية الثانية، حيث <code>i</code> تساوي 1، وستتوقف لأن طول المجموعة الآن صار 1 كذلك.
</p>

<p>
	أما إذا أردت تجميعة ثابتة solid collection من العُقد -على النقيض من العُقد الحية- فستستطيع تحويل التجميعة إلى مصفوفة حقيقية باستدعاء <code>Array.from</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_22" style="">
<span class="pln">let arrayish </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{</span><span class="lit">0</span><span class="pun">:</span><span class="pln"> </span><span class="str">"one"</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1</span><span class="pun">:</span><span class="pln"> </span><span class="str">"two"</span><span class="pun">,</span><span class="pln"> length</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pun">};</span><span class="pln">
let array </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Array</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">arrayish</span><span class="pun">);</span><span class="pln">
console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">array</span><span class="pun">.</span><span class="pln">map</span><span class="pun">(</span><span class="pln">s </span><span class="pun">=&gt;</span><span class="pln"> s</span><span class="pun">.</span><span class="pln">toUpperCase</span><span class="pun">()));</span><span class="pln">
</span><span class="com">// → ["ONE", "TWO"]</span></pre>

<p>
	استخدم التابع <code>document.createElement</code> لإنشاء عُقد عناصر، حيث يأخذ هذا التابع اسم الوسم ويعيد عُقدةً جديدةً فارغةً من النوع المعطى.
</p>

<p>
	انظر المثال التالي الذي يعرِّف الأداة <code>elt</code> التي تنشئ عُقدة عنصر وتعامل بقية وسائطها على أساس فروع لها، ثم تُستخدَم هذه الدالة لإضافة خصائص إلى اقتباس نصي، أي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_24" style="">
<span class="tag">&lt;blockquote</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"quote"</span><span class="tag">&gt;</span><span class="pln">
  No book can ever be finished. While working on it we learn
  just enough to find it immature the moment we turn away
  from it.
</span><span class="tag">&lt;/blockquote&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> elt</span><span class="pun">(</span><span class="pln">type</span><span class="pun">,</span><span class="pln"> </span><span class="pun">...</span><span class="pln">children</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let node </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="pln">type</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let child of children</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">typeof</span><span class="pln"> child </span><span class="pun">!=</span><span class="pln"> </span><span class="str">"string"</span><span class="pun">)</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">child</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">else</span><span class="pln"> node</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">createTextNode</span><span class="pun">(</span><span class="pln">child</span><span class="pun">));</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> node</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">"quote"</span><span class="pun">).</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">
    elt</span><span class="pun">(</span><span class="str">"footer"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"—"</span><span class="pun">,</span><span class="pln">
        elt</span><span class="pun">(</span><span class="str">"strong"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Karl Popper"</span><span class="pun">),</span><span class="pln">
        </span><span class="str">", preface to the second edition of "</span><span class="pun">,</span><span class="pln">
        elt</span><span class="pun">(</span><span class="str">"em"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"The Open Society and Its Enemies"</span><span class="pun">),</span><span class="pln">
        </span><span class="str">", 1950"</span><span class="pun">));</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<h2>
	السمات Attributes
</h2>

<p>
	يمكن الوصول إلى بعض سمات العناصر مثل <code>href</code> الخاصة بالروابط من خلال خاصية الاسم نفسه على كائن DOM الخاص بالعنصر وهذا شأن أغلب السمات القياسية المستخدَمة، لكن تسمح لك HTML بإسناد set أيّ عدد من السمات إلى العُقد، وذلك مفيد لأنه يسمح لك بتخزين معلومات إضافية في المستند، فإذا ألّفت أسماء سمات خاصة بك فلن تكون موجودة على أساس خصائص في عُقدة العنصر، بل يجب أن تستخدِم التابعَين <code>getAttribute</code> و<code>setAttribute</code> لكي تتعامل معها.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_26" style="">
<span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">data-classified</span><span class="pun">=</span><span class="atv">"secret"</span><span class="tag">&gt;</span><span class="pln">The launch code is 00000000.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">data-classified</span><span class="pun">=</span><span class="atv">"unclassified"</span><span class="tag">&gt;</span><span class="pln">I have two feet.</span><span class="tag">&lt;/p&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let paras </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let para of </span><span class="typ">Array</span><span class="pun">.</span><span class="pln">from</span><span class="pun">(</span><span class="pln">paras</span><span class="pun">))</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">para</span><span class="pun">.</span><span class="pln">getAttribute</span><span class="pun">(</span><span class="str">"data-classified"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="str">"secret"</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      para</span><span class="pun">.</span><span class="pln">remove</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	يفضَّل أن تسبق أسماء هذه السمات التي تنشئها أنت بـ <code>data‎-‎</code> كي تتأكد أنها لن تتعارض مع أي سمة أخرى. فمثلًا، لدينا سمة <code>class</code> شائعة الاستخدام وهي كلمة مفتاحية في لغة جافاسكربت، كما كانت بعض تطبيقات جافاسكربت القديمة -لأسباب تاريخية- لا تستطيع التعامل مع أسماء الخصائص التي تطابق كلمات مفتاحية، وقد كانت الخاصية التي تُستخدَم للوصول إلى هذه السمة هي <code>className</code>، لكن تستطيع الوصول إليها تحت اسمها الحقيقي <code>"class"</code> باستخدام التابعَين <code>getAttribute</code> و<code>setAttribute</code>.
</p>

<h2>
	مخطط المستند Layout
</h2>

<p>
	لعلك لاحظت أن الأنواع المختلفة من العناصر توضع بتخطيط مختلف، فبعضها -مثل الفقرات <code>&lt;p&gt;</code> أو الترويسات <code>&lt;h1&gt;</code>- يأخذ عرض المستند بأكمله وتُخرَج على أسطر مستقلة، وتسمى هذه العناصر بالعناصر الكتلية block elements؛ في حين بعضها الآخر مثل الروابط <code>&lt;a&gt;</code> والخط السميك <code>&lt;strong&gt;</code> تُخرَج على السطر نفسه مع النص المحيط بها، وتسمى هذه العناصر بـ: العناصر السطرية inline elements.
</p>

<p>
	يستطيع المتصفح أن يضع مخططًا لأي مستند، بحيث يعطي كل عنصر فيه حجمًا وموضعًا وفقًا لنوعه ومحتواه، بعدها يُستخدَم هذا المخطط لرسم المستند في ما يعرضه المتصفح.
</p>

<p>
	يمكن الوصول إلى حجم وموضع أي عنصر من خلال جافاسكربت، إذ تعطيك الخاصيتان <code>offsetWidth</code> و<code>offsetHeight</code> المساحة التي تأخذها العناصر مقاسةً بالبكسلات pixels، وتُعَدّ البكسل أصغر وحدة قياس في المتصفح، وقد كانت تساوي أصغر نقطة تستطيع الشاشة عرضها، لكن الشاشات الحديثة التي تستطيع رسم نقاط صغيرة للغاية لا ينطبق عليها هذا المقياس، حيث يساوي البكسل الواحد عدة نقاط فيها، وبالمثل تعطي <code>clientWidth</code> و<code>clientHeight</code> حجم المساحة داخل العنصر متجاهلة عرض الإطار.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_28" style="">
<span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">border</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3px</span><span class="pln"> solid red</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
  أنا موجود داخل إطار
</span><span class="tag">&lt;/p&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let para </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">.</span><span class="pln">getElementsByTagName</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">)[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"clientHeight:"</span><span class="pun">,</span><span class="pln"> para</span><span class="pun">.</span><span class="pln">clientHeight</span><span class="pun">);</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"offsetHeight:"</span><span class="pun">,</span><span class="pln"> para</span><span class="pun">.</span><span class="pln">offsetHeight</span><span class="pun">);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	أفضل طريقة لمعرفة الموضع الدقيق لأي عنصر على الشاشة هي باستخدام التابع <code>getBoundingClientRect</code>، حيث يُعيد كائنًا فيه خصائص <code>top</code> و<code>bottom</code> و<code>left</code> و<code>right</code>، مشيرةً إلى مواضع البكسلات لجوانب العنصر نسبةً إلى أعلى يسار الشاشة، فإذا أردتها منسوبةً إلى المستند كله، فيجب إضافة موقع التمرير الحالي في المستند والذي ستجده في الرابطتين <code>pageXoffset</code> و<code>pageYoffset</code>.
</p>

<p>
	قد يكون تخطيط المستند مجهدًا لكثرة تفاصيله، لذا لا تعيد محركات المتصفحات إعادة تخطيط المستند في كل مرة تغيره بل تنتظر أطول فترة ممكنة، فحين ينتهي برنامج جافاسكربت من تعديل مستند، فسيكون على المتصفح أن يحسب تخطيطًا جديدًا لرسم المستند الجديد على الشاشة. كذلك إذا طلب برنامج ما موضع أو حجم شيء من خلال قراءة خاصية مثل <code>offsetHeight</code> أو استدعاء <code>getBoundingClientRect</code>، ذلك أن توفير المعلومات الصحيحة يتطلب حساب التخطيط.
</p>

<p>
	أما إذا كان البرنامج ينتقل بين قراءة معلومات مخطط DOM وتغيير DOM، فسيتطلب الكثير من حسابات التخطيط وعليه سيكون بطيئًا جدًا، كما تُعَدّ الشيفرة التالية مثالًا على ذلك، إذ تحتوي على برنامجين مختلفين يبنيان سطرًا من محارف X بعرض 2000 بكسل، ويقيسان الوقت الذي يستغرقه كل واحد منهما.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_31" style="">
<span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">span id</span><span class="pun">=</span><span class="str">"one"</span><span class="pun">&gt;&lt;</span><span class="str">/span&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">span id</span><span class="pun">=</span><span class="str">"two"</span><span class="pun">&gt;&lt;</span><span class="str">/span&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> time</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> action</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let start </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">();</span><span class="pln"> </span><span class="com">// Current time in milliseconds</span><span class="pln">
    action</span><span class="pun">();</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> </span><span class="str">"took"</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Date</span><span class="pun">.</span><span class="pln">now</span><span class="pun">()</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> start</span><span class="pun">,</span><span class="pln"> </span><span class="str">"ms"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  time</span><span class="pun">(</span><span class="str">"naive"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let target </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">"one"</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">while</span><span class="pln"> </span><span class="pun">(</span><span class="pln">target</span><span class="pun">.</span><span class="pln">offsetWidth </span><span class="pun">&lt;</span><span class="pln"> </span><span class="lit">2000</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      target</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">createTextNode</span><span class="pun">(</span><span class="str">"X"</span><span class="pun">));</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  </span><span class="com">// → naive took 32 ms</span><span class="pln">

  time</span><span class="pun">(</span><span class="str">"clever"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let target </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">"two"</span><span class="pun">);</span><span class="pln">
    target</span><span class="pun">.</span><span class="pln">appendChild</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">createTextNode</span><span class="pun">(</span><span class="str">"XXXXX"</span><span class="pun">));</span><span class="pln">
    let total </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">ceil</span><span class="pun">(</span><span class="lit">2000</span><span class="pln"> </span><span class="pun">/</span><span class="pln"> </span><span class="pun">(</span><span class="pln">target</span><span class="pun">.</span><span class="pln">offsetWidth </span><span class="pun">/</span><span class="pln"> </span><span class="lit">5</span><span class="pun">));</span><span class="pln">
    target</span><span class="pun">.</span><span class="pln">firstChild</span><span class="pun">.</span><span class="pln">nodeValue </span><span class="pun">=</span><span class="pln"> </span><span class="str">"X"</span><span class="pun">.</span><span class="pln">repeat</span><span class="pun">(</span><span class="pln">total</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
  </span><span class="com">// → clever took 1 ms</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	التنسيق Styling
</h2>

<p>
	رأينا أنّ عناصر HTML المختلفة تُعرَض على الشاشة بطرق مختلفة، فبعضها يُعرَض في كتل مستقلة، وبعضها يكون داخل السطر نفسه، كما يضاف تخصيص مثل <code>&lt;strong&gt;</code> إلى بعض النصوص لجعلها سميكة، وكذلك يُضاف <code>&lt;a&gt;</code> إلى بعضها الآخر كي تظهر بلون أزرق وتحتها خط دلالةً على كونها رابطًا تشعبيًا.
</p>

<p>
	ترتبط الطريقة التي يعرض بها وسم <code>&lt;img&gt;</code> صورة ما، أو يجعل وسم <code>&lt;a&gt;</code> رابطًا يذهب إلى صفحة أخرى عند النقر عليه ارتباطًا وثيقًا إلى نوع العنصر، لكن نستطيع تغيير التنسيق المرتبط بالعنصر مثل لون النص أو وضع خط أسفله.
</p>

<p>
	انظر المثال التالي على استخدام خاصية <code>style</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_33" style="">
<span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"."</span><span class="pun">&gt;</span><span class="typ">Normal</span><span class="pln"> link</span><span class="pun">&lt;</span><span class="str">/a&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;&lt;</span><span class="pln">a href</span><span class="pun">=</span><span class="str">"."</span><span class="pln"> style</span><span class="pun">=</span><span class="str">"color: green"</span><span class="pun">&gt;</span><span class="typ">Green</span><span class="pln"> link</span><span class="pun">&lt;</span><span class="str">/a&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span></pre>

<p>
	يمكن لسمة التنسيق style attribute أن تحتوي تصريحًا واحدًا أو أكثر، وهو خاصية -مثل <code>color</code>- متبوعة بنقطتين رأسيتين وقيمة -مثل <code>green</code> في المثال أعلاه-، وإذا كان لدينا أكثر من تصريح واحد فيجب فصل التصريحات بفواصل منقوطة كما في <code>"color: red; border: none"</code>.
</p>

<p>
	يتحكم التنسيق كما ترى في جوانب كثيرة من المستند. فمثلًا، تتحكم خاصية <code>display</code> في عرض العنصر على أنه كتلة مستقلة أو عنصر سطري، أي كما يلي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_35" style="">
<span class="pln">يُعرض هذا النص </span><span class="tag">&lt;strong&gt;</span><span class="pln">في السطر كما ترى</span><span class="tag">&lt;/strong&gt;</span><span class="pln">,
</span><span class="tag">&lt;strong</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">display</span><span class="pun">:</span><span class="pln"> block</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">مثل كتلة</span><span class="tag">&lt;/strong&gt;</span><span class="pln">, و
</span><span class="tag">&lt;strong</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">display</span><span class="pun">:</span><span class="pln"> none</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">لا يُعرض على الشاشة</span><span class="tag">&lt;/strong&gt;</span><span class="pln">.</span></pre>

<p>
	سيُعرض وسم <code>block</code> في المثال السابق في سطر منفصل بما أن عناصر الكتل لا تُعرض داخل سطر مع نصوص حولها؛ أما الوسم الأخير فلن يُعرض مطلقًا بسبب <code>none</code> التي تمنع العنصر من الظهور على الشاشة، وتلك طريقة لإخفاء العناصر وهي مفضَّلة على الحذف النهائي من المستند لاحتمال الحاجة إليها في وقت لاحق.
</p>

<p>
	يمكن لشيفرة جافاسكربت أن تعدّل مباشرةً على تنسيق عنصر ما من خلال خاصية <code>style</code> لذلك العنصر، وهذه الخاصية تحمل كائنًا له خصائص لكل خصائص التنسيق المحتملة، كما تكون قيم هذه الخصائص سلاسل نصية نكتبها كي نغيِّر جزءًا بعينه من تنسيق العنصر.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_37" style="">
<span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"para"</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">color</span><span class="pun">:</span><span class="pln"> purple</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
  هذا نص جميل
</span><span class="tag">&lt;/p&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let para </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">"para"</span><span class="pun">);</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">para</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">color</span><span class="pun">);</span><span class="pln">
  para</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">color </span><span class="pun">=</span><span class="pln"> </span><span class="str">"magenta"</span><span class="pun">;</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	تحتوي بعض أسماء خصائص التنسيقات على شرطة <code>-</code> مثل <code>font-family</code>، وبما أنّ أسماء هذه الخصائص يصعب التعامل معها في جافاسكربت إذ يجب كتابة <code>style["font-family"]‎</code>، فإن الأسماء التي في كائن <code>style</code> لتلك الخصائص تُحذف منها الشُّرَط التي فيها وتُجعل الأحرف التي بعدها أحرف كبيرة كما في <code>style.fontFamily</code>.
</p>

<h2>
	التنسيقات المورثة Cascading Styles
</h2>

<p>
	يسمى نظام تصميم وعرض العناصر في HTML باسم CSS، وهي اختصار لعبارة Cascading Style Sheets أو صفحات التنسيقات المُورَّثة، وتُعَدّ صفحة التنسيق style sheet مجموعةً من القوانين التي تحكم مظهر العناصر في مستند ما، ويمكن كتابتها داخل وسم <code>&lt;style&gt;</code>.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_4828_39" style="">
<span class="tag">&lt;style&gt;</span><span class="pln">
  strong </span><span class="pun">{</span><span class="pln">
    font</span><span class="pun">-</span><span class="pln">style</span><span class="pun">:</span><span class="pln"> italic</span><span class="pun">;</span><span class="pln">
    color</span><span class="pun">:</span><span class="pln"> gray</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;</span><span class="pln">الآن </span><span class="tag">&lt;strong&gt;</span><span class="pln">النص السميك </span><span class="tag">&lt;/strong&gt;</span><span class="pln">صار مائلًا ورماديًا.</span><span class="tag">&lt;/p&gt;</span></pre>

<p>
	وتشير المُورَّثة التي في هذه التسمية إلى إمكانية جمع عدة قواعد معًا لإنتاج التنسيق النهائي لعنصر ما.
</p>

<p>
	تعطَّل أثر التنسيق الافتراضي لوسوم <code>&lt;strong&gt;</code> في المثال السابق التي تجعل الخط سميكًا بسبب القاعدة الموجودة في وسم <code>&lt;style&gt;</code> التي تضيف تنسيق الخط <code>font-style</code> ولونه <code>color</code>.
</p>

<p>
	وإذا عرَّفت عدة قواعد قيمةً لنفس الخاصية، فإن أحدث قاعدة قُرِئت ستحصل على أسبقية أعلى وتفوز، لذا فإذا كان وسم <code>&lt;style&gt;</code> يحتوي على <code>font-weight: normal</code> وعارض قاعدة <code>font-weight</code> الافتراضية، فسيكون النص عاديًا وليس سميكًا، فالتنسيقات التي في سمة <code>style</code> والتي تُطبَّق مباشرةً على العُقدة لها أولوية أعلى وتكون هي الفائزة دائمًا.
</p>

<p>
	من الممكن استهداف أشياء غير أسماء الوسوم في قواعد CSS، إذ سستُطبَّق قاعدة موجهة لـ <code>‎.abc</code> على جميع العناصر التي فيها <code>"abc"</code> في سمة <code>class</code> الخاصة بها، وكذلك قاعدة لـ <code>‎#xyz</code> ستُطبق على عنصر له سمة <code>id</code> بها <code>"xyz"</code>، والتي يجب أن تكون فريدةً ولا تتكرر في المستند.
</p>

<pre class="ipsCode prettyprint lang-css prettyprinted" id="ips_uid_4828_41" style="">
<span class="pun">.</span><span class="pln">subtle </span><span class="pun">{</span><span class="pln">
  color</span><span class="pun">:</span><span class="pln"> gray</span><span class="pun">;</span><span class="pln">
  font</span><span class="pun">-</span><span class="pln">size</span><span class="pun">:</span><span class="pln"> </span><span class="lit">80</span><span class="pun">%;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="com">#header {</span><span class="pln">
  background</span><span class="pun">:</span><span class="pln"> blue</span><span class="pun">;</span><span class="pln">
  color</span><span class="pun">:</span><span class="pln"> white</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="com">/* p elements with id main and with classes a and b */</span><span class="pln">
p</span><span class="com">#main.a.b {</span><span class="pln">
  margin</span><span class="pun">-</span><span class="pln">bottom</span><span class="pun">:</span><span class="pln"> </span><span class="lit">20px</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لا تنطبق قاعدة الأولوية التي تفضِّل أحدث قاعدة معرَّفة إلا حين تكون جميع القواعد لها النوعية specificity نفسها، ونوعية القاعدة مقياس لدقة وصف العناصر المتطابقة، وتُحدِّد بعدد جوانب العنصر التي يتطلبها ونوعها -أي الوسم أو الصنف أو المعرِّف ID. فمثلًا، تكون القاعدة التي تستهدف <code>p.a</code> أكثر تحديدًا من قاعدة تستهدف <code>p</code> أو <code>‎.a</code> فقط، وعليه تكون لها الأولوية.
</p>

<p>
	تطبّق الصيغة <code>p &gt; a {...}‎</code> التنسيقات المعطاة على جميع وسوم <code>&lt;a&gt;</code> التي تكون فروعًا مباشرةً من وسوم <code>&lt;p&gt;</code>، وبالمثل تطبّق <code>p a {...}‎</code> على جميع وسوم <code>&lt;a&gt;</code> الموجودة داخل وسوم <code>&lt;p&gt;</code> سواءً كانت فروعًا مباشرةً أو غير مباشرة.
</p>

<h2>
	محددات الاستعلامات Query Selectors
</h2>

<p>
	لن نستخدم صفحات التنسيقات كثيرًا في هذه السلسلة، إذ يحتاج تعقيدها وتفصيلها إلى سلسلة خاصة بها، لكن فهمها ينفعك عند البرمجة في المتصفح، والسبب الذي جعلنا نذكر بُنية المحدِّد هنا -وهي الصيغة المستخدَمة في صفحات التنسيقات لتحديد العناصر التي تنطبق عليها مجموعة تنسيقات بعينها- هو أننا نستطيع استخدام التركيب اللغوي نفسه على أساس طريقة فعالة للعثور على عناصر DOM.
</p>

<p>
	يأخذ التابع <code>querySelectorAll</code> المعرَّف في كائن <code>document</code> وفي عُقد العناصر، ويأخذ سلسلةً نصيةً لمحدِّد ويُعيد <code>NodeList</code> تحتوي جميع العناصر المطابقة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_4828_43" style="">
<span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">And</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> you go chasing
  </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"animal"</span><span class="pun">&gt;</span><span class="pln">rabbits</span><span class="pun">&lt;</span><span class="str">/span&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">And</span><span class="pln"> you know you</span><span class="str">'</span><span class="pln">re going to fall</span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Tell</span><span class="pln"> </span><span class="str">'</span><span class="pln">em a </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"character"</span><span class="pun">&gt;</span><span class="pln">hookah smoking
  </span><span class="pun">&lt;</span><span class="pln">span </span><span class="kwd">class</span><span class="pun">=</span><span class="str">"animal"</span><span class="pun">&gt;</span><span class="pln">caterpillar</span><span class="pun">&lt;</span><span class="str">/span&gt;&lt;/</span><span class="pln">span</span><span class="pun">&gt;&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">p</span><span class="pun">&gt;</span><span class="typ">Has</span><span class="pln"> given you the call</span><span class="pun">&lt;/</span><span class="pln">p</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> count</span><span class="pun">(</span><span class="pln">selector</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelectorAll</span><span class="pun">(</span><span class="pln">selector</span><span class="pun">).</span><span class="pln">length</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">count</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">));</span><span class="pln">           </span><span class="com">// All &lt;p&gt; elements</span><span class="pln">
  </span><span class="com">// → 4</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">count</span><span class="pun">(</span><span class="str">".animal"</span><span class="pun">));</span><span class="pln">     </span><span class="com">// Class animal</span><span class="pln">
  </span><span class="com">// → 2</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">count</span><span class="pun">(</span><span class="str">"p .animal"</span><span class="pun">));</span><span class="pln">   </span><span class="com">// Animal inside of &lt;p&gt;</span><span class="pln">
  </span><span class="com">// → 2</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">count</span><span class="pun">(</span><span class="str">"p &gt; .animal"</span><span class="pun">));</span><span class="pln"> </span><span class="com">// Direct child of &lt;p&gt;</span><span class="pln">
  </span><span class="com">// → 1</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	لا يكون الكائن المعاد من <code>querySelectorAll</code> حيًا على عكس توابع مثل <code>getElementsByTagName</code>، كما لن يتغير إذا غيرت المستند، إذ لا يزال مصفوفةً غير حقيقية، لذا ستحتاج إلى استدعاء <code>Array.from</code> إذا أردت معاملته على أنه مصفوفة.
</p>

<p>
	يعمل التابع <code>querySelector</code> -دون <code>All</code>- بأسلوب مشابه، وهو مفيد إذا أردت عنصرًا منفردًا بعينه، إذ سيعيد أول عنصر مطابق أو <code>null</code> إذا لم يكن ثمة مطابقة.
</p>

<h2>
	التموضع والتحريك
</h2>

<p>
	تؤثر خاصية التنسيق <code>position</code> على شكل التخطيط تأثيرًا كبيرًا، ولها قيمة <code>static</code> افتراضيًا، أي أن العنصر يظل في موضعه العادي في المستند، وحين تُضبط على <code>relative</code> فسيأخذ مساحةً في المستند أيضًا لكن مع اختلاف أنّ الخصائص التنسيقية <code>top</code> و<code>left</code> يمكن استخدامها لتحريكه نسبة إلى ذلك الموضع العادي له.
</p>

<p>
	أما حين تُضبط <code>position</code> على <code>absolute</code> فسيُحذَف العنصر من التدفق الاعتيادي للمستند normal flow، أي لا يأخذ مساحة، وإنما قد يتداخل مع عناصر أخرى، وتُستخدم <code>top</code> و<code>left</code> هذه المرة لموضعة العنصر بصورة مطلقة هذه المرة نسبةً إلى الركن الأيسر العلوي لأقرب عنصر مغلِّف تكون خاصية <code>position</code> له غير <code>static</code>، أو نسبة إلى المستند ككل إن لم يوجد عنصر مغلِّف.
</p>

<p>
	نستخدِم ما سبق عند إنشاء تحريك animation، كما يوضح المستند التالي الذي يعرض صورة قطة تتحرك في مسار قطع ناقص ellipse.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_45" style="">
<span class="tag">&lt;p</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">text</span><span class="pun">-</span><span class="pln">align</span><span class="pun">:</span><span class="pln"> center</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
  </span><span class="tag">&lt;img</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"img/cat.png"</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">position</span><span class="pun">:</span><span class="pln"> relative</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;/p&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let cat </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"img"</span><span class="pun">);</span><span class="pln">
  let angle </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">PI </span><span class="pun">/</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> animate</span><span class="pun">(</span><span class="pln">time</span><span class="pun">,</span><span class="pln"> lastTime</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lastTime </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      angle </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">time </span><span class="pun">-</span><span class="pln"> lastTime</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.001</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    cat</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">sin</span><span class="pun">(</span><span class="pln">angle</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">20</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
    cat</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">cos</span><span class="pun">(</span><span class="pln">angle</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
    requestAnimationFrame</span><span class="pun">(</span><span class="pln">newTime </span><span class="pun">=&gt;</span><span class="pln"> animate</span><span class="pun">(</span><span class="pln">newTime</span><span class="pun">,</span><span class="pln"> time</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  requestAnimationFrame</span><span class="pun">(</span><span class="pln">animate</span><span class="pun">);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	تكون صورتنا في منتصف الصفحة ونضبط خاصية <code>position</code> لتكون <code>relative</code>، وسنحدِّث تنسيقي الصورة <code>top</code> و<code>left</code> باستمرار من أجل تحريك الصورة.
</p>

<p>
	تستخدِم السكربت <code>requestAnimationFrame</code> لجدولة دالة <code>animate</code> كي تعمل كلما كان المتصفح جاهزًا لإعادة رسم الشاشة أو تغيير المعروض عليها، وتستدعي دالة <code>animate</code> نفسها <code>requestAnimationFrame</code> لجدولة التحديث التالي، وحين تكون نافذة المتصفح كلها نشطةً أو نافذة اللسان (تبويب) فقط، فإن ذلك يتسبب في جعل معدل التحديثات نحو 60 تحديثًا في الثانية، مما يجعل مظهر العرض ناعمًا وجميلًا، فإذا حدَّثنا DOM في حلقة تكرارية فستتجمد الصفحة ولن يظهر شيء على الشاشة، إذ لا تحدِّث المتصفحات العرض الخاص بها أثناء تشغيل برنامج جافاسكربت ولا تسمح لأيّ تفاعل مع الصفحة، من أجل ذلك نحتاج إلى <code>requestAnimationFrame</code>، إذ تسمح للمتصفح أن يعرف أننا انتهينا من هذه المرحلة، ويستطيع متابعة فعل المهام الخاصة بالمتصفحات، مثل تحديث الشاشة والتجاوب مع تفاعل المستخدم.
</p>

<p>
	يُمرَّر الوقت الحالي إلى دالة التحريك على أساس وسيط، ولكي نضمن أن حركة القطة ثابتة لكل ميلي ثانية، فإنها تبني السرعة التي تتغير بها الزاوية على الفرق بين الوقت الحالي وبين آخر وقت عملت فيه الدالة، فإذا حرَّكتَ الزاوية بمقدار ثابت لكل خطوة، فستبدو الحركة متعثرةً وغير ناعمة إذا كان لدينا مهمة أخرى كبيرة تعمل على نفس الحاسوب مثلًا، وتمنع الدالة من العمل حتى ولو كانت فترة المنع تلك جزء من الثانية.
</p>

<p>
	تُنفَّذ الحركة الدائرية باستخدام دوال حساب المثلثات <code>Math.cos</code> و<code>Math.sin</code>، كما سنشرح هذه الدوال إذا لم يكن لك بها خبرة سابقة بما أننا سنستخدمها بضعة مرات في هذه السلسلة.
</p>

<p>
	تُستخدَم هاتان الدالتان لإيجاد نقاط تقع على دائرة حول نقطة الإحداثي الصفري (0,0) والتي لها نصف قطر يساوي 1، كما تفسِّران وسيطها على أساس موضع على هذه الدائرة مع صفر يشير إلى النقطة التي على أقصى يمين الدائرة، ويتحرك باتجاه عقارب الساعة حتى يقطع محيطها الذي يساوي 2 باي -أي 2π- والتي يكون مقدارها هنا 6.28 تقريبًا.
</p>

<p>
	تخبرك <code>Math.cos</code> بإحداثية x للنقطة الموافقة للموضع الحالي، في حين تخبرك <code>Math.sin</code> بإحداثية y، وأيّ موضع أو زاوية أكبر من 2 باي 2π -أي محيط الدائرة- أو أقل من صفر يكون صالحًا ومقبولًا، ويتكرر الدوران إلى أن تشير <code>a+2π</code> إلى نفس الزاوية التي تشير إليها a.
</p>

<p>
	تسمى هذه الوحدة التي تقاس بها الزوايا باسم الزاوية نصف القطرية أو راديان radian، والدائرة الكاملة تحتوي على ‎2 π راديان، ويمكن الحصول على الثابت الطبيعي باي π في جافاسكربت من خلال <code>Math.PI</code>.
</p>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="78252" href="https://academy.hsoub.com/uploads/monthly_2021_09/cos_sin.png.6835411184061f6148388516cfb6909c.png" rel=""><img alt="cos_sin.png" class="ipsImage ipsImage_thumbnailed" data-fileid="78252" data-unique="nw1gtjaxn" src="https://academy.hsoub.com/uploads/monthly_2021_09/cos_sin.png.6835411184061f6148388516cfb6909c.png"></a>
</p>

<p>
	يكون لشيفرة تحريك القطة مقياسًا يدعى <code>angle</code> للزاوية الحالية التي عليها التحريك، بحيث يتزايد في كل مرة تُستدعى فيها دالة <code>animate</code>، ثم يمكن استخدام هذه الزاوية لحساب الموضع الحالي لعنصر الصورة.
</p>

<p>
	يُحسب التنسيق العلوي <code>top</code> باستخدام <code>Math.sin</code> ويُضرب في 20، وهو نصف القطر الرأسي للقطع الناقصة في مثالنا، وبالمثل يُبنى تنسيق <code>left</code> على <code>Math.cos</code>، ويُضرب في 200 لأن القطع الناقص عرضه أكبر من ارتفاعه.
</p>

<p>
	لاحظ أن التنسيقات تحتاج إلى وحدات في الغالب، وفي تلك الحالة فإننا نحتاج أن نلحق <code>"px"</code> إلى العدد ليخبر المتصفح أن وحدة العدّ التي نستخدمها هي البكسل -وليس سنتيمترات أو ems أو أي شيء آخر-، وهذه النقطة مهمة لسهولة نسيانها، حيث ستتسبب كتابة أعداد دون وحدات في تجاهل التنسيق الخاص بك، إلا إن كان العدد صفرًا، وذلك لأن معناه لا يختلف مهما اختلفت الوحدات.
</p>

<h2>
	خاتمة
</h2>

<p>
	تستطيع البرامج المكتوبة بجافاسكربت فحص المستند الذي يعرضه المتصفح والتدخل فيه بالتعديل، من خلال هيكل بيانات يسمى DOM، حيث يمثِّل هذا الهيكل نموذج المتصفح للمستند، ويعدّله برنامج جافاسكربت من أجل التعديل في المستند المعروض على الشاشة.
</p>

<p>
	يُنظَّم DOM في هيئة شجرية، بحيث تُرتَّب العناصر فيها هرميًا وفقًا لهيكل المستند، والكائنات التي تمثل العناصر لها خصائص مثل <code>parentNode</code> و<code>childNodes</code> التي يمكن استخدامها للتنقل في الشجرة، كما يمكن التأثير على طريقة عرض المستند من خلال التنسيقات، إما عبر إلحاق تنسيقات بالعُقد مباشرةً، أو عبر تعريف قواعد تتطابق مع عُقد بعينها، ولدينا العديد من خصائص التنسيقات مثل <code>color</code> و<code>display</code>، كما تستطيع شيفرة جافاسكربت التعديل في تنسيق العنصر مباشرةً من خلال خاصية <code>style</code>.
</p>

<h2>
	تدريبات
</h2>

<h3>
	بناء جدول
</h3>

<p>
	يُبنى الجدول في لغة HTML بهيكل الوسم التالي:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_47" style="">
<span class="tag">&lt;table&gt;</span><span class="pln">
  </span><span class="tag">&lt;tr&gt;</span><span class="pln">
    </span><span class="tag">&lt;th&gt;</span><span class="pln">name</span><span class="tag">&lt;/th&gt;</span><span class="pln">
    </span><span class="tag">&lt;th&gt;</span><span class="pln">height</span><span class="tag">&lt;/th&gt;</span><span class="pln">
    </span><span class="tag">&lt;th&gt;</span><span class="pln">place</span><span class="tag">&lt;/th&gt;</span><span class="pln">
  </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
  </span><span class="tag">&lt;tr&gt;</span><span class="pln">
    </span><span class="tag">&lt;td&gt;</span><span class="pln">Kilimanjaro</span><span class="tag">&lt;/td&gt;</span><span class="pln">
    </span><span class="tag">&lt;td&gt;</span><span class="pln">5895</span><span class="tag">&lt;/td&gt;</span><span class="pln">
    </span><span class="tag">&lt;td&gt;</span><span class="pln">Tanzania</span><span class="tag">&lt;/td&gt;</span><span class="pln">
  </span><span class="tag">&lt;/tr&gt;</span><span class="pln">
</span><span class="tag">&lt;/table&gt;</span></pre>

<p>
	ويحتوي وسم <code>&lt;table&gt;</code> على وسم <code>&lt;tr&gt;</code> يمثل الصف الواحد، ونستطيع في كل صف وضع عناصر الخلايا سواءً كانت خلايا ترويسات <code>&lt;th&gt;</code> أو عادية <code>&lt;td&gt;</code>.
</p>

<p>
	ولِّد هيكل DOM لجدول يَعُد الكائنات إذا أُعطيت مجموعة بيانات لجبال ومصفوفة من الكائنات لها الخصائص <code>name</code> و<code>height</code> و<code>place</code>، بحيث يجب أن تحتوي على عمود لكل مفتاح وصَفّ لكل كائن، إضافةً إلى صف ترويسة بعناصر <code>&lt;th&gt;</code> في الأعلى لتسرد أسماء الأعمدة.
</p>

<p>
	اكتب ذلك بحيث تنحدر الأعمدة مباشرةً من الكائنات، من خلال أخذ أسماء الخصائص للكائن الأول في البيانات، وأضف الجدول الناتج إلى العنصر الذي يحمل سمة <code>id</code> لـ <code>"mountains"</code> كي يصبح ظاهرًا في المستند.
</p>

<p>
	بمجرد أن يعمل هذا، اجعل محاذاة الخلايا التي تحتوي قيمًا عدديةً إلى اليمين من خلال ضبط خاصية <code>style.textAlign</code> لها لتكون <code>"right"</code>.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_49" style="">
<span class="tag">&lt;h1&gt;</span><span class="pln">Mountains</span><span class="tag">&lt;/h1&gt;</span><span class="pln">

</span><span class="tag">&lt;div</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"mountains"</span><span class="tag">&gt;&lt;/div&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  </span><span class="kwd">const</span><span class="pln"> MOUNTAINS </span><span class="pun">=</span><span class="pln"> </span><span class="pun">[</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Kilimanjaro"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5895</span><span class="pun">,</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Tanzania"</span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Everest"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">8848</span><span class="pun">,</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Nepal"</span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Mount Fuji"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3776</span><span class="pun">,</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Japan"</span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Vaalserberg"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">323</span><span class="pun">,</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Netherlands"</span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Denali"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">6168</span><span class="pun">,</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="str">"United States"</span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Popocatepetl"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5465</span><span class="pun">,</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Mexico"</span><span class="pun">},</span><span class="pln">
    </span><span class="pun">{</span><span class="pln">name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Mont Blanc"</span><span class="pun">,</span><span class="pln"> height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4808</span><span class="pun">,</span><span class="pln"> place</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Italy/France"</span><span class="pun">}</span><span class="pln">
  </span><span class="pun">];</span><span class="pln">

  </span><span class="com">// ضع شيفرتك هنا</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	استخدِم <code>document.createElement</code> لإنشاء عُقد عناصر جديدة، و<code>document.createTextNode</code> لإنشاء عُقد نصية، والتابع <code>appendChild</code> لوضع العُقد داخل عُقد أخرى.
</p>

<p>
	قد تريد التكرار على أسماء المفاتيح مرةً كي تملأ الصف العلوي، ثم مرةً أخرى لكل كائن في المصفوفة لتضع بيانات الصفوف، كما يمكنك استخدام <code>Object.keys</code> للحصول على مصفوفة أسماء المفاتيح من الكائن الأول.
</p>

<p>
	استخدِم <code>document.getElementById</code> أو <code>document.querySelector</code> لإيجاد العُقدة التي لها سمة <code>id</code> الصحيحة، إذا أردت إضافة الجدول إلى العُقدة الأصل المناسبة.
</p>

<h3>
	جلب العناصر بأسماء وسومها
</h3>

<p>
	يُعيد التابع <code>document.getElementsByTagName</code> جميع العناصر الفرعية التي لها اسم وسم معيَّن. استخدِم نسختك الخاصة منه على أساس دالة تأخذ عُقدةً وسلسلةً نصيةً -هي اسم الوسم- على أساس وسائط، وتُعيد مصفوفةً تحتوي على عُقد العناصر المنحدرة منه، والتي لها اسم الوسم المعطى.
</p>

<p>
	استخدِم خاصية <code>nodeName</code> لعنصر ما كي تحصل على اسم الوسم الخاص به، لكن لاحظ أن هذا سيعيد اسم الوسم بأحرف إنجليزية من الحالة الكبيرة capital، لذا يمكنك استخدام التابعين النصيين <code>toLowerCase</code> أو <code>toUpperCase</code> لتعديل حالة تلك الحروف كما تريد.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_51" style="">
<span class="tag">&lt;h1&gt;</span><span class="pln">Heading with a </span><span class="tag">&lt;span&gt;</span><span class="pln">span</span><span class="tag">&lt;/span&gt;</span><span class="pln"> element.</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;</span><span class="pln">A paragraph with </span><span class="tag">&lt;span&gt;</span><span class="pln">one</span><span class="tag">&lt;/span&gt;</span><span class="pln">, </span><span class="tag">&lt;span&gt;</span><span class="pln">two</span><span class="tag">&lt;/span&gt;</span><span class="pln">
  spans.</span><span class="tag">&lt;/p&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> byTagName</span><span class="pun">(</span><span class="pln">node</span><span class="pun">,</span><span class="pln"> tagName</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// ضع شيفرتك هنا.</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">byTagName</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> </span><span class="str">"h1"</span><span class="pun">).</span><span class="pln">length</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → 1</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">byTagName</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">body</span><span class="pun">,</span><span class="pln"> </span><span class="str">"span"</span><span class="pun">).</span><span class="pln">length</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → 3</span><span class="pln">
  let para </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"p"</span><span class="pun">);</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">byTagName</span><span class="pun">(</span><span class="pln">para</span><span class="pun">,</span><span class="pln"> </span><span class="str">"span"</span><span class="pun">).</span><span class="pln">length</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// → 2</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	يمكن حل هذا التدريب بسهولة باستخدام دالة تعاودية كما فعلنا في دالة <code>talksAbout</code> التي تقدم شرحها هنا.
</p>

<p>
	استدع <code>byTagname</code> نفسها تعاوديًا للصق المصفوفات الناتجة ببعضها لتكون هي الخرج، أو تستطيع إنشاء دالة داخلية تستدعي نفسها تعاوديًا ولها وصول إلى رابطة مصفوفة معرَّفة في الدالة الخارجية، بحيث يمكنها إضافة العناصر التي تجدها إليها، ولا تنسى استدعاء الدالة الداخلية من الدالة الخارجية كي تبدأ العملية.
</p>

<p>
	يجب أن تتحقق الدالة التعاودية من نوع العُقدة، وما يهمنا هنا هو العُقدة التي من النوع 1 أي <code>Node.ELEMENT_NODE</code>، كما علينا في مثل تلك العُقد علينا التكرار على فروعها، وننظر في كل فرع إن كان يطابق الاستعلام في الوقت نفسه الذي نستدعيه تعاوديًا فيه للنظر في فروعه هو.
</p>

<h3>
	قبعة القطة
</h3>

<p>
	وسِّع مثال تحريك القطة الذي سبق كي تدور القطة على جهة مقابلة من القبعة <code>&lt;img src="img/hat.png"&gt;</code> في القطع الناقص أو اجعل القبعة تدور حول القطة أو أي تعديل يعجبك في طريقة حركتيهما.
</p>

<p>
	لتسهيل موضعة الكائنات المتعددة، من الأفضل استخدام التموضع المطلق absolute positioning، وهذا يعني أن <code>top</code> و<code>left</code> تُحسبان نسبةً إلى أعلى يسار المستند.
</p>

<p>
	أضف عددًا ثابتًا من البكسلات إلى قيم الموضع كي تتجنب استخدام الإحداثيات السالبة التي ستجعل الصورة تتحرك خارج الصفحة المرئية.
</p>

<p>
	تستطيع تعديل شيفرة التدريب لكتابة الحل وتشغيلها في طرفية المتصفح إن كنت تقرأ من متصفح، أو بنسخها إلى <a href="codepen.io" rel="">codepen</a>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_4828_53" style="">
<span class="tag">&lt;style&gt;</span><span class="pln">body </span><span class="pun">{</span><span class="pln"> min</span><span class="pun">-</span><span class="pln">height</span><span class="pun">:</span><span class="pln"> </span><span class="lit">200px</span><span class="pln"> </span><span class="pun">}</span><span class="tag">&lt;/style&gt;</span><span class="pln">
</span><span class="tag">&lt;img</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"img/cat.png"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"cat"</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">position</span><span class="pun">:</span><span class="pln"> absolute</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;img</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"img/hat.png"</span><span class="pln"> </span><span class="atn">id</span><span class="pun">=</span><span class="atv">"hat"</span><span class="pln"> </span><span class="atn">style</span><span class="pun">=</span><span class="atv">"</span><span class="pln">position</span><span class="pun">:</span><span class="pln"> absolute</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">

</span><span class="tag">&lt;script&gt;</span><span class="pln">
  let cat </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"#cat"</span><span class="pun">);</span><span class="pln">
  let hat </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">querySelector</span><span class="pun">(</span><span class="str">"#hat"</span><span class="pun">);</span><span class="pln">

  let angle </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
  let lastTime </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> animate</span><span class="pun">(</span><span class="pln">time</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">lastTime </span><span class="pun">!=</span><span class="pln"> </span><span class="kwd">null</span><span class="pun">)</span><span class="pln"> angle </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">time </span><span class="pun">-</span><span class="pln"> lastTime</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0.001</span><span class="pun">;</span><span class="pln">
    lastTime </span><span class="pun">=</span><span class="pln"> time</span><span class="pun">;</span><span class="pln">
    cat</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">top </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">sin</span><span class="pun">(</span><span class="pln">angle</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">40</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">40</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">
    cat</span><span class="pun">.</span><span class="pln">style</span><span class="pun">.</span><span class="pln">left </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="typ">Math</span><span class="pun">.</span><span class="pln">cos</span><span class="pun">(</span><span class="pln">angle</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">200</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">230</span><span class="pun">)</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">"px"</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// ضع شيفرتك هنا.</span><span class="pln">

   requestAnimationFrame</span><span class="pun">(</span><span class="pln">animate</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  requestAnimationFrame</span><span class="pun">(</span><span class="pln">animate</span><span class="pun">);</span><span class="pln">
</span><span class="tag">&lt;/script&gt;</span></pre>

<h4>
	إرشادات للحل
</h4>

<p>
	تقيس الدالتان <code>Math.cos</code> و<code>Math.sin</code> الزوايا بصورة نصف دائرية أي بواحدة الراديان، فإذا كانت الدائرة تساوي 2 باي 2π كما تقدَّم، فستستطيع الحصول على الزاوية المقابلة بإضافة نصف هذه القيمة -والتي تساوي باي أو π- باستخدام <code>Math.PI</code>، وبالتالي سيسهل عليك وضع القبعة على الجهة المقابلة من القطة.
</p>

<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/14_dom.html" rel="external nofollow">للفصل الرابع عشر من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86-xmlhttprequest-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1299/" rel="">الكائن XMLHttpRequest في جافاسكريبت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-url-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1298/" rel="">كائنات URL في جافاسكريبت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AF%D9%88%D8%A7%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1233/" rel="">الدوال في جافاسكريبت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1322</guid><pubDate>Thu, 23 Sep 2021 15:00:00 +0000</pubDate></item><item><title>&#x622;&#x644;&#x64A;&#x627;&#x62A; &#x627;&#x644;&#x627;&#x62A;&#x635;&#x627;&#x644; &#x627;&#x644;&#x645;&#x633;&#x62A;&#x645;&#x631; &#x645;&#x639; &#x627;&#x644;&#x62E;&#x627;&#x62F;&#x645; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A2%D9%84%D9%8A%D8%A7%D8%AA-%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D8%A7%D9%84%D9%85%D8%B3%D8%AA%D9%85%D8%B1-%D9%85%D8%B9-%D8%A7%D9%84%D8%AE%D8%A7%D8%AF%D9%85-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1301/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_08/611953ea7f47e_--.png.bb94615f90563e5f936ad3c06cc4cf37.png" /></p>

<p>
	سنشرح في هذا المقال آليات الاتصال الدائم المستمر مع الخادم وهو الاتصال الذي يُنشَأ فيه اتصال مستمر بين الخادم والعميل (غالبًا متصفح الويب) يجري فيه تبادل الطلبيات والردود آنيًا، وتنحصر آلية تنفيذ الاتصال الدائم تلك في ثلاثة أنواع: آلية الاستطلاع المفتوح Long polling وآلية مقابس الويب عبر البروتوكول WebSocket وآلية الأحداث المرسلة EventSource، سنناقش كل واحدة بالتفصيل على حدة.
</p>

<h2>
	آلية الاستطلاع
</h2>

<p>
	آلية الجس والاستطلاع polling هي الطريقة الأبسط للمحافظة على اتصال ثابت مع الخادم، ولا تتطلب أي بروتوكولات خاصة مثل WebSocket، أو أي أحداث من طرف الخادم، كما أنها سهلة الإنجاز وكافية في الكثير من الحالات.
</p>

<h3>
	الاستطلاع الدوري Regular polling
</h3>

<p>
	الطريقة الأبسط للحصول على معلومات من الخادم هي الاستطلاع الدوري، بإرسال طلبات إلى الخادم لسؤاله عن أي معلومات جديدة على فترات زمنية منتظمة (مثلًا: كل 10 ثوان)، وعند الاستجابة ينتبه الخادم إلى اتصال العميل به، ثم يرسل حزمةً من الرسائل التي تلقاها حتى اللحظة، سيعمل الأمر كما هو متوقع، لكن مع بعض الجوانب السلبية، منها:
</p>

<ol>
<li>
		تمرَّر الرسائل بتأخير زمني قد يصل إلى 10 ثوان (الزمن الفاصل بين الطلبات).
	</li>
	<li>
		سيُغرَق الخادم بالطلبات كل 10 ثوان حتى لو غادر المستخدم الموقع أو استغرق في النوم، وهذا حمل كبير عليه من منظور الأداء.
	</li>
</ol>
<p>
	لهذا قد تكون هذه المقاربة جيدةً عندما تكون الخدمة بسيطةً، لكنها وبشكل عام تحتاج إلى التحسين.
</p>

<h3>
	الاستطلاع المفتوح Long polling
</h3>

<p>
	وهي طريقة أفضل للاستطلاع أو الجس من حالة الخادم، وتتميز بسهولة تنفيذها، وقدرتها على تسليم الرسائل دون تأخير.
</p>

<p>
	حيث:
</p>

<ol>
<li>
		يُرسَل الطلب إلى الخادم.
	</li>
	<li>
		لا يغلق الخادم الاتصال حتى تكون لديه رسالة لتسليمها.
	</li>
	<li>
		عندما تظهر الرسالة، يستجيب الخادم للطلب بهذه الرسالة.
	</li>
	<li>
		يرسل المتصفح طلبًا جديدًا مباشرة.
	</li>
</ol>
<p style="text-align: center;">
	<img alt="request_recieve_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="74598" data-unique="3v1hgek96" src="https://academy.hsoub.com/uploads/monthly_2021_08/request_recieve_01.png.2e4fcf5a754fbbb6cc09e220a06a1310.png"></p>

<p>
	إن أساس هذه الطريقة هو إرسال المتصفح للطلب وإبقاء الاتصال مع الخادم قيد الانتظار، ولا يهيّئ المتصفح الاتصال من جديد حتى يتسلم الاستجابة، فإن فُقد الاتصال نتيجة خطأ ما، فسيرسل المتصفح طلبًا آخر مباشرةً.
</p>

<p>
	تمثل الشيفرة التالية الدالة <code>subscribe</code> التي ترسل الطلبات المفتوحة من جهة العميل:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_7" style="">
<span class="pln">async </span><span class="kwd">function</span><span class="pln"> subscribe</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let response </span><span class="pun">=</span><span class="pln"> await fetch</span><span class="pun">(</span><span class="str">"/subscribe"</span><span class="pun">);</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="lit">502</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// 502 خطأ تجاوز الوقت المسموح للاتصال</span><span class="pln">
    </span><span class="com">// يحدث عند بقاء الاتصال مفتوحًا لفترة طويلة جدًا</span><span class="pln">
    </span><span class="com">// ويغلقه الخادم لذا يجب إعادة الاتصال</span><span class="pln">
    await subscribe</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// إظهار الخطأ</span><span class="pln">
    showMessage</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">);</span><span class="pln">
    </span><span class="com">// إعادة الاتصال خلال ثانية</span><span class="pln">
    await </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> setTimeout</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">));</span><span class="pln">
    await subscribe</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// الحصول على الرسالة وإظهارها</span><span class="pln">
    let message </span><span class="pun">=</span><span class="pln"> await response</span><span class="pun">.</span><span class="pln">text</span><span class="pun">();</span><span class="pln">
    showMessage</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
    </span><span class="com">// استدعاء التابع من جديد </span><span class="pln">
    await subscribe</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

subscribe</span><span class="pun">();</span></pre>

<p>
	يرسل التابع <code>subscribe</code> طلب إحضار، وينتظر الاستجابة ليعالجها عندما تصل، ثم يستدعي نفسه مجددًا.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		يجب أن لا تمثل الاتصالات المفتوحة عبئًا على الخادم ينبغي أن تكون بنية الخادم قادرةً على العمل مع عدة اتصالات مفتوحة، حيث تنفذ بعض الخوادم عمليةً واحدةً لكل اتصال، مما ينتج عنه الكثير من العمليات المستهلكة للذاكرة عند تخديم عدد أكبر من الاتصالات، وتظهر هذه المشكلة عادةً عند كتابة شيفرة الواجهة الخلفية باستخدام PHP أو Ruby، لكنها لن تظهر عند استخدام Node.JS. مع ذلك لا تتعلق المشكلة بلغات البرمجة إطلاقًا، بل ينبغي التأكد من قدرة بنية الخادم على التعامل بكفاءة مع الاتصالات المفتوحة في نفس الوقت.
	</p>
</blockquote>

<h3>
	مثال اختباري لنظام محادثة بسيط
</h3>

<p>
	إليك تطبيق محادثة اختباريًا يمكنك تنزيله وتشغيله محليًا إذا كنت على دراية باستخدام Node.JS وتثبيت الوحدات البرمجية اللازمة:
</p>

<p>
	ملف شيفرة العميل "browser.js":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_9" style="">
<span class="com">// POSTإرسال رسالة، طلب </span><span class="pln">
</span><span class="kwd">function</span><span class="pln"> </span><span class="typ">PublishForm</span><span class="pun">(</span><span class="pln">form</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> sendMessage</span><span class="pun">(</span><span class="pln">message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fetch</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      method</span><span class="pun">:</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">,</span><span class="pln">
      body</span><span class="pun">:</span><span class="pln"> message
    </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  form</span><span class="pun">.</span><span class="pln">onsubmit </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let message </span><span class="pun">=</span><span class="pln"> form</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      form</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">value </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</span><span class="pun">;</span><span class="pln">
      sendMessage</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// تلقي الرسائل باستخدام الاستطلاع المفتوح </span><span class="pln">
</span><span class="kwd">function</span><span class="pln"> </span><span class="typ">SubscribePane</span><span class="pun">(</span><span class="pln">elem</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> showMessage</span><span class="pun">(</span><span class="pln">message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let messageElem </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">'div'</span><span class="pun">);</span><span class="pln">
    messageElem</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
    elem</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">messageElem</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  async </span><span class="kwd">function</span><span class="pln"> subscribe</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let response </span><span class="pun">=</span><span class="pln"> await fetch</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="lit">502</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// تجاوز الوقت المسموح</span><span class="pln">
      </span><span class="com">// يحدث عندما يبقى الاتصال مفتوحًا لفترة طويلة</span><span class="pln">
      </span><span class="com">// لا بد من إعادة الاتصال</span><span class="pln">
      await subscribe</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// Show Error</span><span class="pln">
      showMessage</span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">);</span><span class="pln">
      </span><span class="com">// إعادة الاتصال خلال ثانية </span><span class="pln">
      await </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">(</span><span class="pln">resolve </span><span class="pun">=&gt;</span><span class="pln"> setTimeout</span><span class="pun">(</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">));</span><span class="pln">
      await subscribe</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// الحصول على الرسالة</span><span class="pln">
      let message </span><span class="pun">=</span><span class="pln"> await response</span><span class="pun">.</span><span class="pln">text</span><span class="pun">();</span><span class="pln">
      showMessage</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
      await subscribe</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  subscribe</span><span class="pun">();</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	ملف شيفرة الواجهة الخلفية "server.js":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_11" style="">
<span class="pln">let http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">);</span><span class="pln">
let url </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'url'</span><span class="pun">);</span><span class="pln">
let querystring </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'querystring'</span><span class="pun">);</span><span class="pln">
let </span><span class="kwd">static</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'node-static'</span><span class="pun">);</span><span class="pln">

let fileServer </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="kwd">static</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">);</span><span class="pln">

let subscribers </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> onSubscribe</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let id </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">random</span><span class="pun">();</span><span class="pln">

  res</span><span class="pun">.</span><span class="pln">setHeader</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'text/plain;charset=utf-8'</span><span class="pun">);</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">setHeader</span><span class="pun">(</span><span class="str">"Cache-Control"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"no-cache, must-revalidate"</span><span class="pun">);</span><span class="pln">

  subscribers</span><span class="pun">[</span><span class="pln">id</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> res</span><span class="pun">;</span><span class="pln">

  req</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'close'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">delete</span><span class="pln"> subscribers</span><span class="pun">[</span><span class="pln">id</span><span class="pun">];</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> publish</span><span class="pun">(</span><span class="pln">message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let id in subscribers</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let res </span><span class="pun">=</span><span class="pln"> subscribers</span><span class="pun">[</span><span class="pln">id</span><span class="pun">];</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  subscribers </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> accept</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let urlParsed </span><span class="pun">=</span><span class="pln"> url</span><span class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// مستخدم جديد يريد النتائج </span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">urlParsed</span><span class="pun">.</span><span class="pln">pathname </span><span class="pun">==</span><span class="pln"> </span><span class="str">'/subscribe'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    onSubscribe</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// إرسال رسالة </span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">urlParsed</span><span class="pun">.</span><span class="pln">pathname </span><span class="pun">==</span><span class="pln"> </span><span class="str">'/publish'</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// POSTقبول </span><span class="pln">
    req</span><span class="pun">.</span><span class="pln">setEncoding</span><span class="pun">(</span><span class="str">'utf8'</span><span class="pun">);</span><span class="pln">
    let message </span><span class="pun">=</span><span class="pln"> </span><span class="str">''</span><span class="pun">;</span><span class="pln">
    req</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'data'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">chunk</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      message </span><span class="pun">+=</span><span class="pln"> chunk</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}).</span><span class="pln">on</span><span class="pun">(</span><span class="str">'end'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      publish</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln"> </span><span class="com">// النشر للجميع</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="str">"ok"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">

    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// البقية ثابتة </span><span class="pln">
  fileServer</span><span class="pun">.</span><span class="pln">serve</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">);</span><span class="pln">

</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> close</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">let id in subscribers</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let res </span><span class="pun">=</span><span class="pln"> subscribers</span><span class="pun">[</span><span class="pln">id</span><span class="pun">];</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="com">// -----------------------------------</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">module</span><span class="pun">.</span><span class="pln">parent</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">accept</span><span class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">8080</span><span class="pun">);</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Server running on port 8080'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  exports</span><span class="pun">.</span><span class="pln">accept </span><span class="pun">=</span><span class="pln"> accept</span><span class="pun">;</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">process</span><span class="pun">.</span><span class="pln">send</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
     process</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'message'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
       </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">msg </span><span class="pun">===</span><span class="pln"> </span><span class="str">'shutdown'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
         close</span><span class="pun">();</span><span class="pln">
       </span><span class="pun">}</span><span class="pln">
     </span><span class="pun">});</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  process</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'SIGINT'</span><span class="pun">,</span><span class="pln"> close</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	الملف الأساسي "index.html":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_13" style="">
<span class="pun">&lt;!</span><span class="pln">DOCTYPE html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script src</span><span class="pun">=</span><span class="str">"browser.js"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">

</span><span class="typ">All</span><span class="pln"> visitors of </span><span class="kwd">this</span><span class="pln"> page will see messages of each other</span><span class="pun">.</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">form name</span><span class="pun">=</span><span class="str">"publish"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"message"</span><span class="pln"> </span><span class="pun">/&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Send"</span><span class="pln"> </span><span class="pun">/&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">div id</span><span class="pun">=</span><span class="str">"subscribe"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">PublishForm</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">forms</span><span class="pun">.</span><span class="pln">publish</span><span class="pun">,</span><span class="pln"> </span><span class="str">'publish'</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// random url parameter to avoid any caching issues</span><span class="pln">
  </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">SubscribePane</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">'subscribe'</span><span class="pun">),</span><span class="pln"> </span><span class="str">'subscribe?random='</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="typ">Math</span><span class="pun">.</span><span class="pln">random</span><span class="pun">());</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	وستظهر النتيجة بالشكل التالي:
</p>

<p>
	<iframe __idm_frm__="141" class="code-tabs__result" data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" src="https://javascript.info/article/long-polling/longpoll/"></iframe>
</p>

<h3>
	مجال الاستخدام
</h3>

<p>
	تعمل طريقة الاستطلاع المفتوح بصورة ممتازة عندما تكون الرسائل نادرةً، لكن عندما تتكرر الرسائل بكثرة، فسيكون منحني (طلب-استجابة) المرسوم في الأعلى على هيئة أسنان المنشار، ولأن لكل رسالة طلبًا مستقلًا له ترويسات وآلية استيثاق خاصة، فمن الأفضل استخدام طرق أخرى مثل: Websocket أوServer Sent Events، التي سنشرحها في الفقرات التالية.
</p>

<h2>
	استخدام البروتوكول WebSocket
</h2>

<p>
	يزوّدنا هذا البروتوكول الموصوف في المعيار <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="http://tools.ietf.org/html/rfc6455" rel="external nofollow">RFC 6455</a> بطريقة لتبادل البيانات بين الخادم والمتصفح عبر اتصال ثابت، حيث تمرَّر البيانات إلى كلا الطرفين على شكل حزم دون قطع الاتصال أو استخدام طلبات HTTP إضافية، وتظهر الفائدة الكبيرة لاستخدامه عند وجود خدمات تتطلب التبادل المستمر للبيانات، مثل ألعاب الشبكة وأنظمة التجارة العاملة في الزمن الحقيقي وغيرها.
</p>

<h3>
	مثال بسيط
</h3>

<p>
	سنحتاج إلى كائن مقبس اتصال جديد <code>new WebSocket</code> لفتح اتصال websocket باستخدام بروتوكول نقل خاص هو <code>ws</code> في العنوان:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_15" style="">
<span class="pln">let socket </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">WebSocket</span><span class="pun">(</span><span class="str">"ws://javascript.info"</span><span class="pun">);</span></pre>

<p>
	كما يمكن استخدام البروتوكول المشفّر <code>//:wss</code>، فهو مشابه للبروتوكول HTTPS لكن مع websockets.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		يُفضّل دومًا استخدام البروتوكول <code>//:wss</code>، لأنه مشفّر وأكثر وثوقيةً، فالبيانات التي تنتقل عبر البروتوكول <code>//:ws</code> غير مشفّرة ومرئية بالنسبة لأي وسيط، وقد لا تميزه الخوادم الوكيلة القديمة، كما ستصل إليها ترويسات ستعدها غريبةً وتقطع الاتصال. من ناحية أخرى فالبروتوكول <code>//:wss</code> هو WebSocket يستخدم <abbr title="Transport Layer Security | بروتوكول أمن طبقة النقل">TLS</abbr> (وهي اختصار للعبارة transport security layer وتعني طبقة أمن النقل)، وهي طبقة تشفّر البيانات عند إرسالها وتفك تشفيرها عند استقبالها، وبالتالي ستكون حزم البيانات مشفرةً أثناء انتقالها عبر الأوساط فلا يمكن معرفة محتواها واختراقه.
	</p>
</blockquote>

<p>
	ينبغي الاستماع إلى الأحداث التي تطرأ على مقبس الاتصال Socket بعد تأسيسه مباشرةً، وهي أربعة أحداث:
</p>

<ul>
<li>
		<code>open</code>: عند تأسيس الاتصال.
	</li>
	<li>
		<code>message</code>: عند استقبال البيانات.
	</li>
	<li>
		<code>error</code>: عند وقوع خطأ يتعلق بالبروتوكول websocket.
	</li>
	<li>
		<code>close</code>: عند إغلاق الاتصال.
	</li>
</ul>
<p>
	ولإرسال أي بيانات يكفي استخدام الصيغة <code>(socket.send(data</code>، وإليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_17" style="">
<span class="pln">let socket </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">WebSocket</span><span class="pun">(</span><span class="str">"wss://javascript.info/article/websocket/demo/hello"</span><span class="pun">);</span><span class="pln">

socket</span><span class="pun">.</span><span class="pln">onopen </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">"[open] Connection established"</span><span class="pun">);</span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">"Sending to server"</span><span class="pun">);</span><span class="pln">
  socket</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="str">"My name is John"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

socket</span><span class="pun">.</span><span class="pln">onmessage </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`[</span><span class="pln">message</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Data</span><span class="pln"> received from server</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

socket</span><span class="pun">.</span><span class="pln">onclose </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">wasClean</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    alert</span><span class="pun">(`[</span><span class="pln">close</span><span class="pun">]</span><span class="pln"> </span><span class="typ">Connection</span><span class="pln"> closed cleanly</span><span class="pun">,</span><span class="pln"> code</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">code</span><span class="pun">}</span><span class="pln"> reason</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">reason</span><span class="pun">}`);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// كأن تلغى العملية أو فشل في الشبكة</span><span class="pln">
    </span><span class="com">// 1006 قيمته في هذه الحالة عادة event.code </span><span class="pln">
    alert</span><span class="pun">(</span><span class="str">'[close] Connection died'</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

socket</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">error</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`[</span><span class="pln">error</span><span class="pun">]</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">error</span><span class="pun">.</span><span class="pln">message</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	لقد كتبنا شيفرة خادم تجريبي باستخدام Node.JS في الملف <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://javascript.info/article/websocket/demo/server.js" rel="external nofollow">server.js</a> لتشغيل الشيفرة في المثال السابق، وسيرد الخادم بالرسالة "Hello from server, John"، ثم ينتظر 5 ثوان ويقطع الاتصال.
</p>

<p>
	سترى إذًا تسلسل الأحداث <code>open</code> → <code>message</code> → <code>close</code>، والأمر بهذه البساطة فعليًا، لأنك تستخدم الآن البروتوكول WebSocket، لنتحدث الآن في أمور أكثر عمقًا.
</p>

<h3>
	فتح اتصال مقبس ويب WebSocket
</h3>

<p>
	يُنشئ الأمر <code>(new WebSocket(url</code> كائنًا جديدًا ويبدأ عملية الاتصال مباشرةً، سيسأل المتصفح الخادم خلال تأسيس الاتصال -عبر الترويسات- إذا كان يدعم البروتوكول Websocket، فإن كان الجواب نعم فسيستمر العمل به، وهو يختلف عن HTTP كليًا.
</p>

<p style="text-align: center;">
	<img alt="Copy of websocket_02.png" class="ipsImage ipsImage_thumbnailed" data-fileid="74597" data-unique="sg5robemz" src="https://academy.hsoub.com/uploads/monthly_2021_08/611953ea9b893_Copyofwebsocket_02.png.5e8d42e9b95e3bcb0339fda80a81018f.png"></p>

<p>
	وإليك مثالًا عن ترويسات المتصفح للطلب الذي يرسله الأمر <code>(new WebSocket("wss://javascript.info/chat</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_19" style="">
<span class="pln">GET </span><span class="pun">/</span><span class="pln">chat
</span><span class="typ">Host</span><span class="pun">:</span><span class="pln"> javascript</span><span class="pun">.</span><span class="pln">info
</span><span class="typ">Origin</span><span class="pun">:</span><span class="pln"> https</span><span class="pun">:</span><span class="com">//javascript.info</span><span class="pln">
</span><span class="typ">Connection</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Upgrade</span><span class="pln">
</span><span class="typ">Upgrade</span><span class="pun">:</span><span class="pln"> websocket
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Key</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Iv8io</span><span class="pun">/</span><span class="lit">9s</span><span class="pun">+</span><span class="pln">lYFgZWcXczP8Q</span><span class="pun">==</span><span class="pln">
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Version</span><span class="pun">:</span><span class="pln"> </span><span class="lit">13</span></pre>

<ul>
<li>
		<code>Origin</code>: أصل الصفحة العميلة (<code><a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://javascript.info" ipsnoembed="true" rel="external nofollow">https://javascript.info</a></code> مثلًا)، فالكائنات <code>WebSocket</code> هي كائنات من أصول مختلطة بطبيعتها، حيث لا توجد أي قيود لاستخدامها، وطالما أنّ الخوادم القديمة لا تدعم هذا البروتوكول، فلن نواجه مشكلة توافق. تظهر أهمية الترويسة <code>Origin</code> في أنها تسمح للخادم بالاختيار إن كان سيتواصل مع الصفحة العميلة وفق هذا البروتوكول أم لا.
	</li>
	<li>
		<code>Connection: Upgrade</code>: يشير إلى رغبة العميل في تغيير البروتوكول.
	</li>
	<li>
		<code>Upgrade: websocket</code>: تدل أنّ البروتوكول المطلوب هو websocket.
	</li>
	<li>
		<code>Sec-WebSocket-Key</code>: مفتاح أمان عشوائي يولده المتصفح.
	</li>
	<li>
		<code>Sec-WebSocket-Version</code>: نسخة بروتوكول websocket، وهي حاليًا النسخة 13.
	</li>
</ul>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		لا يمكن تقليد المصافحة (أي تأسيس الاتصال) الخاصة بالبروتوكول WebSocket لا يمكن استخدام الكائنين <code>fetch</code> أو <code>XMLHttpRequest</code> لتنفيذ هذا النوع من طلبات HTTP، لأنه لا يسمح لشيفرة JavaScript بضبط تلك الترويسات.
	</p>
</blockquote>

<p>
	ينبغي على الخادم أن يعيد الرمز 101 عندما يوافق على التحوّل إلى البروتوكول WebSocket:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_21" style="">
<span class="lit">101</span><span class="pln"> </span><span class="typ">Switching</span><span class="pln"> </span><span class="typ">Protocols</span><span class="pln">
</span><span class="typ">Upgrade</span><span class="pun">:</span><span class="pln"> websocket
</span><span class="typ">Connection</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Upgrade</span><span class="pln">
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Accept</span><span class="pun">:</span><span class="pln"> hsBlbuDTkk24srzEOTBUlZAlC2g</span><span class="pun">=</span></pre>

<p>
	ستكون الترويسة <code>Sec-WebSocket-Accept</code> هي نفسها <code>Sec-WebSocket-Key</code> مسجلةً باستخدام خوارزمية خاصة، وسيستخدمها المتصفح للتأكد من توافق الاستجابة مع الطلب. بعد ذلك ستُنقل البيانات باستخدام بروتوكول WebSocket، وسنطلع على بنيتها (إطاراتها) قريبًا.
</p>

<h3>
	الموسعات والبروتوكولات الفرعية
</h3>

<p>
	هنالك أيضًا الترويستان <code>Sec-WebSocket-Extensions</code> التي تصف الموسّعات، و<code>Sec-WebSocket-Protocol</code> التي تصف البروتوكولات الفرعية.
</p>

<p>
	فمثلًا:
</p>

<ul>
<li>
		<code>Sec-WebSocket-Extensions: deflate-frame</code>: يعني أن المتصفح سيدعم ضغط البيانات، فالموسّع هو شيء يتعلق بنقل البيانات ووظيفة توسّع عمل البروتوكول، يرسل المتصفح الترويسة <code>Sec-WebSocket-Extensions</code> تلقائيًا مزودةً بقائمة الموسّعات المدعومة كلها.
	</li>
	<li>
		<code>Sec-WebSocket-Protocol: soap, wamp</code>: يعني أننا سننقل البيانات باستخدام أحد البروتوكولين الفرعيين <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="http://en.wikipedia.org/wiki/SOAP" rel="external nofollow">SOAP</a> أو WAMP (وهو اختصار للعبارة " WebSocket Application Messaging Protocol" أي "بروتوكول نقل الرسائل لتطبيق websocket"). تُسجل البروتوكولات الفرعية في <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="http://www.iana.org/assignments/websocket/websocket.xml" rel="external nofollow">IANA catalogue</a>، أي أن هذه الترويسة الاختيارية تحدد صيغ البيانات التي سنستخدمها، وتُضبَط باستخدام المعامل الثاني للدالة البانية <code>new WebSocket</code>، فلو أردنا استخدام SOAP أو WAMP مثلًا، فسنضعهما في مصفوفة بالشكل التالي:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_23" style="">
<span class="pln">let socket </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">WebSocket</span><span class="pun">(</span><span class="str">"wss://javascript.info/chat"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="str">"soap"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"wamp"</span><span class="pun">]);</span></pre>

<p>
	سيستجيب الخادم بقائمة من البروتوكولات والموسّعات التي يوافق على استخدامها، وإليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_25" style="">
<span class="pln">GET </span><span class="pun">/</span><span class="pln">chat
</span><span class="typ">Host</span><span class="pun">:</span><span class="pln"> javascript</span><span class="pun">.</span><span class="pln">info
</span><span class="typ">Upgrade</span><span class="pun">:</span><span class="pln"> websocket
</span><span class="typ">Connection</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Upgrade</span><span class="pln">
</span><span class="typ">Origin</span><span class="pun">:</span><span class="pln"> https</span><span class="pun">:</span><span class="com">//javascript.info</span><span class="pln">
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Key</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Iv8io</span><span class="pun">/</span><span class="lit">9s</span><span class="pun">+</span><span class="pln">lYFgZWcXczP8Q</span><span class="pun">==</span><span class="pln">
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Version</span><span class="pun">:</span><span class="pln"> </span><span class="lit">13</span><span class="pln">
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Extensions</span><span class="pun">:</span><span class="pln"> deflate</span><span class="pun">-</span><span class="pln">frame
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Protocol</span><span class="pun">:</span><span class="pln"> soap</span><span class="pun">,</span><span class="pln"> wamp</span></pre>

<p>
	وستكون الاستجابة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_27" style="">
<span class="lit">101</span><span class="pln"> </span><span class="typ">Switching</span><span class="pln"> </span><span class="typ">Protocols</span><span class="pln">
</span><span class="typ">Upgrade</span><span class="pun">:</span><span class="pln"> websocket
</span><span class="typ">Connection</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Upgrade</span><span class="pln">
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Accept</span><span class="pun">:</span><span class="pln"> hsBlbuDTkk24srzEOTBUlZAlC2g</span><span class="pun">=</span><span class="pln">
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Extensions</span><span class="pun">:</span><span class="pln"> deflate</span><span class="pun">-</span><span class="pln">frame
</span><span class="typ">Sec</span><span class="pun">-</span><span class="typ">WebSocket</span><span class="pun">-</span><span class="typ">Protocol</span><span class="pun">:</span><span class="pln"> soap</span></pre>

<p>
	أي أن الخادم سيدعم الموسّع "deflate-frame" والبروتوكول الفرعي SOAP فقط.
</p>

<h3>
	نقل البيانات
</h3>

<p>
	يتكون الاتصال من إطارات "frames"، وهي أجزاء من البيانات يمكن إرسالها من كلا الطرفين وقد تكون من الأنواع التالية:
</p>

<ul>
<li>
		"text": يحتوي بيانات نصيةً ترسلها الأطراف إلى بعضها.
	</li>
	<li>
		"binary data": يحتوي بيانات ثنائيةً تُرسلها الأطراف إلى بعضها.
	</li>
	<li>
		"ping/pong": تستخدَم للتحقق من الاتصال، ويرسلها الخادم إلى المتصفح الذي يرد عليها تلقائيًا.
	</li>
	<li>
		ستجد أيضًا "connection close" وبعض الإطارات الأخرى.
	</li>
</ul>
<p>
	ونعمل في بيئة المتصفح مع الإطارات النصية والثنائية.
</p>

<p>
	يمكن للتابع <code>()send.</code> إرسال بيانات نصية أو ثنائية.، ويسمح استدعاء التابع <code>(socket.send(body</code> بأن تكون قيمة جسم الطلب<code>body</code> بالتنسيق النصي أو الثنائي، بما في ذلك الكائن <code>Blob</code> و<code>ArrayBuffer</code> وغيرهما، ولا حاجة لإجراء ضبط خاص بل تُرسَل البيانات كما هي.
</p>

<p>
	تأتي البيانات النصية على شكل "string" عندما نستقبل البيانات، ومع ذلك يمكننا الاختيار بين <code>Blob</code>و<code>ArrayBuffer</code> للبيانات الثنائية.** وكذا ضبط ذلك باستخدام الخاصية <code>socket.binaryType</code>، التي تأخذ القيمة <code>"blob"</code> افتراضيًا، لذلك تأتي البيانات الثنائية ضمن الكائن <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://academy.hsoub.com/programming/javascript/%D9%83%D8%A7%D8%A6%D9%86-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D8%AB%D9%86%D8%A7%D8%A6%D9%8A%D8%A9-blob-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1285/" rel="">Blob</a>، الذي يمثل كائن بيانات ثنائية عالي المستوى ويتكامل مباشرةً مع المعرّفين <code>&lt;a&gt;</code>و<code>&lt;img&gt;</code> وغيرهما، ويمكن الانتقال إلى استخدام الكائن <code>ArrayBuffer</code> عند معالجة البيانات الثنائية والعمل مع البايتات بشكل منفصل.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_29" style="">
<span class="pln">socket</span><span class="pun">.</span><span class="pln">binaryType </span><span class="pun">=</span><span class="pln"> </span><span class="str">"arraybuffer"</span><span class="pun">;</span><span class="pln">
socket</span><span class="pun">.</span><span class="pln">onmessage </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">//أما أن يكون نصًا أو بيانات ثنائيةً   event.data </span><span class="pln">
</span><span class="pun">};</span></pre>

<h3>
	محدودية معدل النقل Rate limiting
</h3>

<p>
	لنتخيل أن تطبيقنا قادر على توليد كمية كبيرة من البيانات، لكن المستخدم يعاني من اتصال بطيء بالانترنت. يمكن استدعاء التابع <code>(socket.send(data</code> مرات عدةً، لكن البيانات ستُخزّن مؤقتًا في الذاكرة وستُرسَل بالسرعة التي تسمح بها الشبكة فقط، عندها يمكن استخدام الخاصية <code>socket.bufferedAmount</code> التي تُخزّن حجم البيانات الموجودة في المخزن المؤقت في هذه اللحظة وتنتظر الظروف المناسبة لنقلها، ويمكن فحص الحجم المتبقي من البيانات للتأكد من أن المقبس Socket لا يزال متاحًا لمتابعة نقل البيانات.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_31" style="">
<span class="com">// افحص المقبس كل 100 ثانية وحاول إرسال البيانات</span><span class="pln">
</span><span class="com">// عندما يكون حجم البيانات المخزّنة التي لم تصل صفرًا</span><span class="pln">
setInterval</span><span class="pun">(()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">socket</span><span class="pun">.</span><span class="pln">bufferedAmount </span><span class="pun">==</span><span class="pln"> </span><span class="lit">0</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    socket</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">moreData</span><span class="pun">());</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">},</span><span class="pln"> </span><span class="lit">100</span><span class="pun">);</span></pre>

<h3>
	إغلاق الاتصال
</h3>

<p>
	عندما يحاول أحد الأطراف إغلاق الاتصال (للمخدم والمتصفح الحق نفسه في ذلك)، يرسَل عادةً إطار إغلاق الاتصال "connection close" الذي يحتوي قيمةً عدديةً وسببًا نصيًا للإغلاق.
</p>

<p>
	إليك الصيغة اللازمة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_33" style="">
<span class="pln">socket</span><span class="pun">.</span><span class="pln">close</span><span class="pun">([</span><span class="pln">code</span><span class="pun">],</span><span class="pln"> </span><span class="pun">[</span><span class="pln">reason</span><span class="pun">]);</span></pre>

<ul>
<li>
		<code>code</code>: رمز خاص لإغلاق اتصال WebSocket، وهو اختياري.
	</li>
	<li>
		<code>reason</code>: نص يصف سبب الإغلاق، وهو اختياري.
	</li>
</ul>
<p>
	يستقبل معالج الحدث <code>close</code> في الطرف الآخر الرمز والسبب:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_35" style="">
<span class="com">// الطرف الذي سيغلق الاتصال:</span><span class="pln">
socket</span><span class="pun">.</span><span class="pln">close</span><span class="pun">(</span><span class="lit">1000</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Work complete"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// الطرف الآخر</span><span class="pln">
socket</span><span class="pun">.</span><span class="pln">onclose </span><span class="pun">=</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// event.code === 1000</span><span class="pln">
  </span><span class="com">// event.reason === "Work complete"</span><span class="pln">
  </span><span class="com">// event.wasClean === true (clean close)</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	إليك القيم الأكثر شيوعًا للرموز:
</p>

<ul>
<li>
		<code>1000</code>: وهي القيمة الافتراضية، وتستخدم للإغلاق العادي (تستخدم إن لم يكن هناك رمز <code>code</code>).
	</li>
	<li>
		<code>1006</code>: لا يمكن ضبط هذا الرمز يدويًا ويشير إلى فقد الاتصال.
	</li>
	<li>
		<code>1001</code>: عند مغادرة أحد الأطراف، مثل: إغلاق الخادم أو مغادرة المتصفح للصفحة.
	</li>
	<li>
		<code>1009</code>: الرسائل أضخم من أن تُعالج.
	</li>
	<li>
		<code>1011</code>: خطأ في الخادم.
	</li>
</ul>
<p>
	يمكن الاطلاع على القائمة الكاملة ضمن المعيار <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://tools.ietf.org/html/rfc6455#section-7.4.1" rel="external nofollow">RFC6455, §7.4.1</a>.
</p>

<p>
	إنّ رموز WebSocket مشابهة نوعًا ما لرموز HTTP وتختلف في بعض النواحي، فأي رمز أقل من 1000 سيكون محجوزًا، وسيتولد خطأ عند استخدامه.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_37" style="">
<span class="com">// في حال فشل الاتصال</span><span class="pln">
socket</span><span class="pun">.</span><span class="pln">onclose </span><span class="pun">=</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// event.code === 1006</span><span class="pln">
  </span><span class="com">// event.reason === ""</span><span class="pln">
  </span><span class="com">// event.wasClean === false (no closing frame)</span><span class="pln">
</span><span class="pun">};</span></pre>

<h3>
	حالة الاتصال
</h3>

<p>
	تتيح الخاصية <code>socket.readyState</code> إمكانية معرفة الوضع الحالي للاتصال وفقًا للقيم التي تأخذها:
</p>

<ul>
<li>
		<strong><code>0</code></strong> متصل "CONNECTING": أي أن الاتصال يجري، ولم يُؤسَّس بعد.
	</li>
	<li>
		<strong><code>1</code></strong> مفتوح "OPEN": التواصل قائم.
	</li>
	<li>
		<strong><code>2</code></strong> يُغلق "CLOSING": يجري إغلاق الاتصال.
	</li>
	<li>
		<strong><code>3</code></strong> مُغلق "CLOSED": الاتصال مغلق.
	</li>
</ul>
<h3>
	مثال تطبيق محادثة آنية
</h3>

<p>
	لنراجع مثال تطبيق المحادثة باستخدام الواجهة البرمجية WebSocket <abbr title="Application Programming Interface | واجهة برمجية">API</abbr> والوحدة البرمجية <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://github.com/websockets/ws" rel="external nofollow">Node.js WebSocket module</a>، سنركز على طرف العميل، وسيكون الخادم بسيطًا أيضًا.
</p>

<p>
	نحتاج إلى نموذج <code>&lt;form&gt;</code> لإرسال الرسائل ومعرِّف <code>&lt;div&gt;</code> لاستقبالها.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_39" style="">
<span class="pun">&lt;!--</span><span class="pln"> message form </span><span class="pun">--&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">form name</span><span class="pun">=</span><span class="str">"publish"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"text"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"message"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Send"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;!--</span><span class="pln"> div </span><span class="kwd">with</span><span class="pln"> messages </span><span class="pun">--&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">div id</span><span class="pun">=</span><span class="str">"messages"</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span></pre>

<p>
	كما سنستخدم في جافاسكربت:
</p>

<ol>
<li>
		فتح الاتصال.
	</li>
	<li>
		إرسال رسالة عن طريق النموذج <code>(socket.send(message</code>.
	</li>
	<li>
		وضع الرسالة المستقبلة ضمن <code>div#messages</code>.
	</li>
</ol>
<p>
	وإليك الشيفرة اللازمة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_41" style="">
<span class="pln">let socket </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">WebSocket</span><span class="pun">(</span><span class="str">"wss://javascript.info/article/websocket/chat/ws"</span><span class="pun">);</span><span class="pln">

</span><span class="com">// إرسال رسالة إلى النموذج</span><span class="pln">
document</span><span class="pun">.</span><span class="pln">forms</span><span class="pun">.</span><span class="pln">publish</span><span class="pun">.</span><span class="pln">onsubmit </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let outgoingMessage </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">message</span><span class="pun">.</span><span class="pln">value</span><span class="pun">;</span><span class="pln">

  socket</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">outgoingMessage</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">return</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">;</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="com">// div#messagesإظهار الرسالة بعد وصولها في </span><span class="pln">
socket</span><span class="pun">.</span><span class="pln">onmessage </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let message </span><span class="pun">=</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">;</span><span class="pln">

  let messageElem </span><span class="pun">=</span><span class="pln"> document</span><span class="pun">.</span><span class="pln">createElement</span><span class="pun">(</span><span class="str">'div'</span><span class="pun">);</span><span class="pln">
  messageElem</span><span class="pun">.</span><span class="pln">textContent </span><span class="pun">=</span><span class="pln"> message</span><span class="pun">;</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">'messages'</span><span class="pun">).</span><span class="pln">prepend</span><span class="pun">(</span><span class="pln">messageElem</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	لن نتعمق بشيفرة الواجهة الخلفية، فهي خارج نطاق مقالتنا، لكننا سنستخدم هنا Node.js، ومع ذلك فالأمر ليس إلزاميًا، إذ أن للعديد من المنصات طرقها للتعامل مع WebSocket.
</p>

<p>
	تمثل الخطوات التالية خوارزمية عمل الواجهة الخلفية:
</p>

<ol>
<li>
		إنشاء مجموعة من مقابس الاتصال <code>()clients = new Set</code>.
	</li>
	<li>
		إضافة كل مقبس مقبول إلى المجموعة <code>clients.add(socket)</code>، وتهيئة مستمع <code>message</code> للحدث للحصول على الرسائل المتعلقة بالمقبس.
	</li>
	<li>
		عندما استقبال رسالة كرر عملية إرسالها إلى كل عميل.
	</li>
	<li>
		احذف مقابس العملاء عند قطع الاتصال <code>(clients.delete(socket</code>.
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_43" style="">
<span class="kwd">const</span><span class="pln"> ws </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'ws'</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">const</span><span class="pln"> wss </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> ws</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">({</span><span class="pln">noServer</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">const</span><span class="pln"> clients </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Set</span><span class="pun">();</span><span class="pln">

http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">((</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// websocket سنعالج فقط اتصالات </span><span class="pln">
  </span><span class="com">// websocket في مشروعنا الحقيقي ستوجد هنا شيفرة تعالج الطلبات بطرق أخرى غير</span><span class="pln">
  wss</span><span class="pun">.</span><span class="pln">handleUpgrade</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">socket</span><span class="pun">,</span><span class="pln"> </span><span class="typ">Buffer</span><span class="pun">.</span><span class="pln">alloc</span><span class="pun">(</span><span class="lit">0</span><span class="pun">),</span><span class="pln"> onSocketConnect</span><span class="pun">);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> onSocketConnect</span><span class="pun">(</span><span class="pln">ws</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  clients</span><span class="pun">.</span><span class="pln">add</span><span class="pun">(</span><span class="pln">ws</span><span class="pun">);</span><span class="pln">

  ws</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'message'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">message</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    message </span><span class="pun">=</span><span class="pln"> message</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="lit">0</span><span class="pun">,</span><span class="pln"> </span><span class="lit">50</span><span class="pun">);</span><span class="pln"> </span><span class="com">// 50سيكون الحجم الأكبر للرسالة هو </span><span class="pln">

    </span><span class="kwd">for</span><span class="pun">(</span><span class="pln">let client of clients</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      client</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">message</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  ws</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'close'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    clients</span><span class="pun">.</span><span class="kwd">delete</span><span class="pun">(</span><span class="pln">ws</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إليك النتيجة:
</p>

<p>
	<iframe __idm_frm__="142" class="code-result__iframe" data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" data-trusted="1" src="https://en.js.cx/article/websocket/chat/" style="display: block; border: 0px; height: 200px;"></iframe>
</p>

<p>
	يمكن تنزيل التطبيق وتجربته محليًا على جهازك، ولا تنس تثبيت <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://nodejs.org/en/" rel="external nofollow">Node.js</a>، ثم تثبيت WebSocket باستخدام الأمر <code>npm install ws</code> قبل تشغيل التطبيق.
</p>

<h2>
	الأحداث المرسلة من قبل الخادم
</h2>

<p>
	تشرح التوصيفات <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://html.spec.whatwg.org/multipage/comms.html#the-eventsource-interface" rel="external nofollow">Server-Sent Events</a> صنفًا مدمجًا هو <code>EventSource</code>، حيث يحافظ على الاتصال مع الخادم ويسمح باستقبال أحداث منه، وهو اتصال ثابت مثل <code>WebSocket</code> تمامًا لكن مع بعض الاختلافات بينهما:
</p>
<style type="text/css">
table {
    width: 100%;
}

thead {
    vertical-align: middle;
    text-align: center;
} 

td, th {
    border: 1px solid #dddddd;
    text-align: right;
    padding: 8px;
    text-align: inherit;

}
tr:nth-child(even) {
    background-color: #dddddd;
}</style>
<table>
<thead><tr>
<th style="text-align:center">
				<code>WebSocket</code>
			</th>
			<th style="text-align:center">
				<code>EventSource</code>
			</th>
		</tr></thead>
<tbody>
<tr>
<td style="text-align:center">
				ثنائي الاتجاه: إذ يمكن أن يتبادل الخادم والعميل الرسائل
			</td>
			<td style="text-align:center">
				وحيد الاتجاه: فالخادم هو من يرسل الرسائل إلى العميل
			</td>
		</tr>
<tr>
<td style="text-align:center">
				نصوص وبيانات ثنائية
			</td>
			<td style="text-align:center">
				نصوص فقط
			</td>
		</tr>
<tr>
<td style="text-align:center">
				بروتوكول websocket
			</td>
			<td style="text-align:center">
				بروتوكول HTTP النظامي.
			</td>
		</tr>
</tbody>
</table>
<p>
	يمثل <code>EventSource</code> طريقةً أقل قدرةً للاتصال مع الخادم بالموازنة مع <code>WebSocket</code>، إذًا لماذا نستخدمها؟
</p>

<p>
	يعود السبب الرئيسي لاستخدامه في كونه أبسط، إذ لا نحتاج إلى القدرة الكبيرة لبروتوكول WebSocket في بعض التطبيقات، فتلقي بيانات من الخادم مثل رسائل محادثة أو لوائح أسعار أو أي شيء مشابه هو مجال <code>EventSource</code>، كما أنه يدعم إعادة الاتصال، وهو أمر لا بد من إنجازه يدويًا في <code>WebSocket</code>، ويستخدم كذلك بروتوكول HTTP وليس أي بروتوكول جديد.
</p>

<h3>
	الحصول على الرسائل
</h3>

<p>
	لا بد من إنشاء كائن جديد من الصنف <code>EventSource</code> للبدء باستقبال الرسائل، وذلك بتنفيذ الأمر <code>(new EventSource(url</code>، حيث سيتصل المتصفح بالعنوان <code>url</code> ويبقي الاتصال مفتوحًا ومنتظرًا الأحداث، وهنا ينبغي أن يستجيب الخادم برمز الحالة 200 وبالترويسة <code>Content-Type: text/event-stream</code>، وسيبقي الخادم بعدها قناة الاتصال مفتوحةً، وسيرسل الرسائل ضمنها بتنسيق خاص بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_45" style="">
<span class="pln">data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Message</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Message</span><span class="pln"> </span><span class="lit">2</span><span class="pln">

data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Message</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
data</span><span class="pun">:</span><span class="pln"> of two lines</span></pre>

<ul>
<li>
		تأتي الرسالة بعد الكلمة <code>:data</code> بينما يعَد الفراغ الموجود بعد النقطتين اختياريًا.
	</li>
	<li>
		يفصَل بين الرسائل برمز السطر الجديد مضاعفًا <code>n\n\</code>
	</li>
	<li>
		يمكننا عند الحاجة إلى إرسال رمز سطر جديد <code>n\</code> أن نرسل مباشرةً الكلمة <code>:data</code> (كما في الرسالة الثالثة في المثال السابق)، لكن عمليًاP تًرسل الرسائل المركّبة مرمزةً بتنسيق JSON، وترمّز محارف الانتقال إلى سطر جديد بالشكل <code>n\</code> ضمنها، فلا حاجة عندها للرسائل <code>:data</code> متعددة السطر.
	</li>
</ul>
<p>
	فمثلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_47" style="">
<span class="pln">data</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="str">"user"</span><span class="pun">:</span><span class="str">"John"</span><span class="pun">,</span><span class="str">"message"</span><span class="pun">:</span><span class="str">"First line\n Second line"</span><span class="pun">}</span></pre>

<p>
	وهكذا يمكن أن نعد أن العبارة <code>data:</code> ستليها رسالة واحدة تمامًا.
</p>

<p>
	يولَّد الحدث <code>message</code> لكل من تلك الرسائل بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_49" style="">
<span class="pln">let eventSource </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">EventSource</span><span class="pun">(</span><span class="str">"/events/subscribe"</span><span class="pun">);</span><span class="pln">

eventSource</span><span class="pun">.</span><span class="pln">onmessage </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"New message"</span><span class="pun">,</span><span class="pln"> event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">);</span><span class="pln">
  </span><span class="com">// سيطبع ثلاث رسائل في المثال السابق</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="com">// or eventSource.addEventListener('message', ...)</span></pre>

<h3>
	الطلبات ذات الأصل المختلط
</h3>

<p>
	يدعم الصنف <code>EventSource</code> الطلبات ذات الأصل المختلط cross-origin requests كما يفعل <code>fetch</code>، إذ يمكننا استخدام أي عنوان URL:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_51" style="">
<span class="pln">let source </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">EventSource</span><span class="pun">(</span><span class="str">"https://another-site.com/events"</span><span class="pun">);</span></pre>

<p>
	سيستقبل الخادم البعيد الترويسة <code>Origin</code> وينبغي عليه الإجابة بالترويسة <code>Access-Control-Allow-Origin</code> للمتابعة، ولتمرير الثبوتيات credentials لا بد من ضبط خيار إضافي هو <code>withCredentials</code> بالشكل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_53" style="">
<span class="pln">let source </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">EventSource</span><span class="pun">(</span><span class="str">"https://another-site.com/events"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  withCredentials</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">true</span><span class="pln">
</span><span class="pun">});</span></pre>

<p>
	راجع فصل استخدام Fetch مع الطلبات ذات الأصول المختلطة للاطلاع على تفاصيل أكثر حول ترويسات الأصول المختلطة.
</p>

<h3>
	إعادة الاتصال
</h3>

<p>
	بعد إنشاء الكائن <code>EventSource</code> فسيتصل بالخادم مباشرةً ويعيد الاتصال في حال انقطاعه، وبالتالي لا حاجة لإجراء تدابير خاصة بهذا الأمر، وبطبيعة الحال يوجد تأخير زمني صغير لثوان معدودة افتراضيًا بين كل محاولتين لإعادة الاتصال، وهنا يمكن للخادم أن يضبط التأخير اللازم باستخدام الخاصية <code>:retry</code> في الاستجابة (مقدرًا بالميلي ثانية).
</p>

<pre class="ipsCode">
retry: 15000
data: Hello, I set the reconnection delay to 15 seconds
</pre>

<p>
	يمكن أن تأتي <code>:retry</code> مصحوبةً ببيانات أخرى أو برسالة وحيدة، وهكذا سيكون على الخادم انتظار الفترة المحددة قبل إعادة الاتصال، وقد ينتظر فترة أطول، فإذا علم المتصفح -عن طريق نظام التشغيل- أن الشبكة غير متصلة حاليًا مثلًا، فسينتظر حتى يعود الاتصال ثم يحاول ثانيةً.
</p>

<ul>
<li>
		إذا أراد الخادم من المتصفح التوقف عن إعادة الاتصال، فعليه أن يرد برمز الحالة 204 (رمز HTTP).
	</li>
	<li>
		إذا أراد المتصفح إغلاق الاتصال، فعليه استدعاء التابع <code>eventSource.close</code>.
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_55" style="">
<span class="pln">let eventSource </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">EventSource</span><span class="pun">(...);</span><span class="pln">

eventSource</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span></pre>

<p>
	إذا احتوت الاستجابة على ترويسة <code>Content-Type</code> خاطئةً فلن يعاد الاتصال، وكذلك إذا اختلف رمز حالة HTTP عن القيم 301 أو 307 أو 200 أو 204، فعندها سيُولد الحدث <code>"error"</code> ولن يعيد المتصفح الاتصال.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		لاحظ أنه عندما يُغلق الاتصال نهائيًا، فلا وسيلة لإعادته، فإن أردنا الاتصال ثانيةً فلا بد من إنشاء كائن <code>EventSource</code> جديد.
	</p>
</blockquote>

<h3>
	معرف الرسالة الفريد Message id
</h3>

<p>
	عندما ينقطع الاتصال جراء خلل في الشبكة، tلن يتأكد كلا الطرفين من كون الرسائل استلمَت أم لا، وحتى يُستأنَف الاتصال بشكل صحيح، فينبغي أن تحمل كل رسالة مُعرِّفًا مميزًا <code>id</code> مثل التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_57" style="">
<span class="pln">data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Message</span><span class="pln"> </span><span class="lit">1</span><span class="pln">
id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">1</span><span class="pln">

data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Message</span><span class="pln"> </span><span class="lit">2</span><span class="pln">
id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">2</span><span class="pln">

data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Message</span><span class="pln"> </span><span class="lit">3</span><span class="pln">
data</span><span class="pun">:</span><span class="pln"> of two lines
id</span><span class="pun">:</span><span class="pln"> </span><span class="lit">3</span></pre>

<p>
	عندما يتلقى المتصفح الرسالة مع معرّفها <code>:id</code> فإنه:
</p>

<ul>
<li>
		يضبط قيمة الخاصية <code>eventSource.lastEventId</code> على قيمة المعرِّف.
	</li>
	<li>
		يعيد الترويسة <code>Last-Event-ID</code> أثناء إعادة الاتصال مع قيمة المعرّف <code>id</code>، ليتمكن الخادم من إرسال الرسائل التالية إن وجدت.
	</li>
</ul>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		تأتي <code>:id</code> بعد <code>data</code>، حيث يضع الخادم المعرّف <code>id</code> تحت العبارة <code>data</code> للتأكد من تحديث قيمة الخاصية <code>lastEventId</code> قبل تلقي الرسالة.
	</p>
</blockquote>

<h3>
	حالة الاتصال: الخاصية ReadyState
</h3>

<p>
	يمتلك الكائن <code>EventSource</code> الخاصية <code>readyState</code> التي تحمل إحدى القيم الثلاث التالية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_59" style="">
<span class="typ">EventSource</span><span class="pun">.</span><span class="pln">CONNECTING </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> </span><span class="com">// يتصل أو يعيد الاتصال</span><span class="pln">
</span><span class="typ">EventSource</span><span class="pun">.</span><span class="pln">OPEN </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">       </span><span class="com">// متصل</span><span class="pln">
</span><span class="typ">EventSource</span><span class="pun">.</span><span class="pln">CLOSED </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln">     </span><span class="com">// الاتصال مغلق</span></pre>

<p>
	تكون قيمة هذه الخاصية عند إنشاء كائن جديد أو انقطاع الاتصال هي <code>EventSource.CONNECTING</code> دومًا (وهي تعادل 0)، ويمكن الاستعلام عن هذه الخاصية لمعرفة حالة الكائن <code>EventSource</code>.
</p>

<h3>
	أنواع الأحداث
</h3>

<p>
	يوّلِّد الكائن <code>EventSource</code> افتراضيًا ثلاثة أحداث:
</p>

<ul>
<li>
		<code>message</code>: عند استلام رسالة، وتكون متاحةً باستخدام <code>event.data</code>.
	</li>
	<li>
		<code>open</code>: عندما يكون الاتصال مفتوحًا.
	</li>
	<li>
		<code>error</code>: عند عدم إمكانية تأسيس الاتصال، كأن يعيد الخادم رمز الحالة 500.
	</li>
</ul>
<p>
	فمثلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_61" style="">
<span class="pln">event</span><span class="pun">:</span><span class="pln"> join
data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Bob</span><span class="pln">

data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Hello</span><span class="pln">

event</span><span class="pun">:</span><span class="pln"> leave
data</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Bob</span></pre>

<p>
	لمعالجة الأحداث السابقة لابد من استخدام مستمع الحدث <code>addEventListener</code> وليس <code>onmessage</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_63" style="">
<span class="pln">eventSource</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'join'</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Joined</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

eventSource</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'message'</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Said</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

eventSource</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'leave'</span><span class="pun">,</span><span class="pln"> event </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Left</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">data</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">});</span></pre>

<h3>
	مثال نموذجي كامل
</h3>

<p>
	يرسل الخادم في هذا المثال الرسائل <code>1</code> و<code>2</code> و<code>3</code> ثم <code>bye</code> ويقطع الاتصال، بعدها يعيد المتصفح الاتصال تلقائيًا.
</p>

<p>
	شيفرة الخادم "server.js":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_65" style="">
<span class="pln">let http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">);</span><span class="pln">
let url </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'url'</span><span class="pun">);</span><span class="pln">
let querystring </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'querystring'</span><span class="pun">);</span><span class="pln">
let </span><span class="kwd">static</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'node-static'</span><span class="pun">);</span><span class="pln">
let fileServer </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="kwd">static</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> onDigits</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  res</span><span class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'Content-Type'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'text/event-stream; charset=utf-8'</span><span class="pun">,</span><span class="pln">
    </span><span class="str">'Cache-Control'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'no-cache'</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  let i </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">

  let timer </span><span class="pun">=</span><span class="pln"> setInterval</span><span class="pun">(</span><span class="pln">write</span><span class="pun">,</span><span class="pln"> </span><span class="lit">1000</span><span class="pun">);</span><span class="pln">
  write</span><span class="pun">();</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> write</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    i</span><span class="pun">++;</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">i </span><span class="pun">==</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'event: bye\ndata: bye-bye\n\n'</span><span class="pun">);</span><span class="pln">
      clearInterval</span><span class="pun">(</span><span class="pln">timer</span><span class="pun">);</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">();</span><span class="pln">
      </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    res</span><span class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span class="str">'data: '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> i </span><span class="pun">+</span><span class="pln"> </span><span class="str">'\n\n'</span><span class="pun">);</span><span class="pln">

  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> accept</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">url </span><span class="pun">==</span><span class="pln"> </span><span class="str">'/digits'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    onDigits</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  fileServer</span><span class="pun">.</span><span class="pln">serve</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">


</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">module</span><span class="pun">.</span><span class="pln">parent</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">accept</span><span class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">8080</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  exports</span><span class="pun">.</span><span class="pln">accept </span><span class="pun">=</span><span class="pln"> accept</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شيفرة الملف "index.html":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_67" style="">
<span class="pun">&lt;!</span><span class="pln">DOCTYPE html</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
let eventSource</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> start</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// "Start" عند ضغط الزر</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">window</span><span class="pun">.</span><span class="typ">EventSource</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// متصفح إنترنت إكسبلورر أو أي متصفح قديم</span><span class="pln">
    alert</span><span class="pun">(</span><span class="str">"The browser doesn't support EventSource."</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  eventSource </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">EventSource</span><span class="pun">(</span><span class="str">'digits'</span><span class="pun">);</span><span class="pln">

  eventSource</span><span class="pun">.</span><span class="pln">onopen </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">(</span><span class="str">"Event: open"</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">

  eventSource</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">(</span><span class="str">"Event: error"</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">readyState </span><span class="pun">==</span><span class="pln"> </span><span class="typ">EventSource</span><span class="pun">.</span><span class="pln">CONNECTING</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      log</span><span class="pun">(`</span><span class="typ">Reconnecting</span><span class="pln"> </span><span class="pun">(</span><span class="pln">readyState</span><span class="pun">=</span><span class="pln">$</span><span class="pun">{</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">readyState</span><span class="pun">})...`);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      log</span><span class="pun">(</span><span class="str">"Error has occurred."</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">

  eventSource</span><span class="pun">.</span><span class="pln">addEventListener</span><span class="pun">(</span><span class="str">'bye'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">(</span><span class="str">"Event: bye, data: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">data</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  eventSource</span><span class="pun">.</span><span class="pln">onmessage </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">(</span><span class="str">"Event: message, data: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">data</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> stop</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// when "Stop" button pressed</span><span class="pln">
  eventSource</span><span class="pun">.</span><span class="pln">close</span><span class="pun">();</span><span class="pln">
  log</span><span class="pun">(</span><span class="str">"eventSource.close()"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> log</span><span class="pun">(</span><span class="pln">msg</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  logElem</span><span class="pun">.</span><span class="pln">innerHTML </span><span class="pun">+=</span><span class="pln"> msg </span><span class="pun">+</span><span class="pln"> </span><span class="str">"&lt;br&gt;"</span><span class="pun">;</span><span class="pln">
  document</span><span class="pun">.</span><span class="pln">documentElement</span><span class="pun">.</span><span class="pln">scrollTop </span><span class="pun">=</span><span class="pln"> </span><span class="lit">99999999</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">button onclick</span><span class="pun">=</span><span class="str">"start()"</span><span class="pun">&gt;</span><span class="typ">Start</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln"> </span><span class="typ">Press</span><span class="pln"> the </span><span class="str">"Start"</span><span class="pln"> to begin</span><span class="pun">.</span><span class="pln">
</span><span class="pun">&lt;</span><span class="pln">div id</span><span class="pun">=</span><span class="str">"logElem"</span><span class="pln"> style</span><span class="pun">=</span><span class="str">"margin: 6px 0"</span><span class="pun">&gt;&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">button onclick</span><span class="pun">=</span><span class="str">"stop()"</span><span class="pun">&gt;</span><span class="typ">Stop</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln"> </span><span class="str">"Stop"</span><span class="pln"> to finish</span><span class="pun">.</span></pre>

<p>
	وستظهر النتيجة بالشكل التالي:
</p>

<p>
	<iframe __idm_frm__="143" class="code-tabs__result" data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" src="https://javascript.info/article/server-sent-events/eventsource/" style="display: block; border: 0px; height: 200px;"></iframe>
</p>

<h2>
	خلاصة
</h2>

<p>
	تعرفنا في هذا الفصل على آليات إنشاء إتصال مستمر مع الخادم، فبدأ أولًا بعرض آلية الاستطلاع والجس وهي الطريقة الأبسط للمحافظة على اتصال ثابت مع الخادم، ولا تتطلب أي بروتوكولات أو أي أحداث خاصة من طرف الخادم، وهي طريقة سهلة الإنجاز وكافية في الكثير من الحالات.
</p>

<p>
	انتقلنا بعدها إلى شرح بروتوكول WebSocket والذي يُقدم طريقةً عصريةً لتأسيس اتصال ثابت بين المتصفح والخادم، له ميزات منها:
</p>

<ul>
<li>
		لا توجد تقييدات للأصول المختلطة.
	</li>
	<li>
		مدعوم جيدًا من قبل المتصفحات.
	</li>
	<li>
		يمكنه إرسال نصوص وبيانات ثنائية واستقبالها.
	</li>
	<li>
		واجهته البرمجية بسيطة.
	</li>
</ul>
<p>
	التوابع المستعملة في هذا البروتوكول:
</p>

<ol>
<li>
		socket.send(data)‎
	</li>
	<li>
		socket.close([ code ], [reason])‎
	</li>
</ol>
<p>
	أما الأحداث:
</p>

<ol>
<li>
		<code>open</code>
	</li>
	<li>
		<code>message</code>
	</li>
	<li>
		<code>error</code>
	</li>
	<li>
		<code>close</code>
	</li>
</ol>
<p>
	لا تقدّم WebSocket بذاتها تقنيات لإعادة الاتصال أو الاستيثاق وغيرها من الآليات عالية المستوى، لذلك ستجد مكتبات للعميل والخادم تهتم بهذه النقاط، ويمكنك أيضًا تنفيذ شيفرتها يدويًا بنفسك. ولكي تتكامل WebSocket مع مشروع جاهز، جرت العادة أن يشغل المستخدم خادم WebSocket بالتوازي مع خادم HTTP الرئيسي ليتشاركا بقاعدة بيانات واحدة، حيث تُرسل طلبات WebSocket إلى النطاق الفرعي <code>wss://ws.site.com</code> الذي يقود إلى خادم WebSocket، بينما يقود العنوان <code><a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://site.com" ipsnoembed="true" rel="external nofollow">https://site.com</a></code> إلى خادم HTTP الرئيسي، كما توجد طرق أخرى للتكامل.
</p>

<p>
	عرضنا أخيرًا الآلية الثالثة للاتصال وهي عبر الكائن EventSource الذي يؤسس اتصالًا ثابتًا مع الخادم ويسمح له بإرسال الرسائل عبر قناة الاتصال، ويقدم:
</p>

<ul>
<li>
		إعادة الاتصال التلقائي مع قيمة زمنية <code>retry</code> لإعادة الاتصال بعدها.
	</li>
	<li>
		معرّفات فريدة للرسائل "ids" لاستئناف الأحداث من آخر معرّف أرسِل مع الترويسة <code>Last-Event-ID</code> أثناء إعادة الاتصال.
	</li>
	<li>
		حالة الاتصال من خلال الخاصية <code>readyState</code>.
	</li>
</ul>
<p>
	تجعل هذه المزايا الكائن <code>EventSource</code> بديًلا قيمًا للكائن <code>WebSocket</code>، الذي يعمل في مستوىً أدنى ويفتقر إلى الميزات السابقة، والتي يمكن إنجازها يدويًا بالطبع.
</p>

<p>
	يعَد الكائن <code>EventSource</code> في الكثير من التطبيقات الحقيقية قادرًا كفاية على إنجاز المطلوب، وتدعمه جميع المتصفحات الحديثة، بينما لا يدعمه IE، وإليك صيغة استخدامه:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6611_69" style="">
<span class="pln">let source </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">EventSource</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">credentials</span><span class="pun">]);</span></pre>

<p>
	للوسيط الثاني خيار وحيد ممكن هو: <code>{ withCredentials: true }</code>، ويسمح بإرسال ثبوتيات طلبات الأصول المختلطة.
</p>

<p>
	إن عوامل الأمان المتعلقة باستخدام طلبات الأصول المختلطة للكائن <code>EventSource</code>مشابهة للاتصال <code>fetch</code> وغيره من طرق الاتصال عبر الشبكات.
</p>

<p>
	خاصيات الكائن EventSource:
</p>

<ul>
<li>
		<code>readyState</code>: حالة الاتصال القائم

		<ul>
<li>
				<code>EventSource.CONNECTING (=0)‎</code>
			</li>
			<li>
				<code>EventSource.OPEN (=1)‎</code>
			</li>
			<li>
				<code>EventSource.CLOSED (=2)‎</code>
			</li>
		</ul>
</li>
	<li>
		<code>lastEventId</code>: آخر معرّف استقبله المتصفح، وسيرسله أثناء إعادة الاتصال مع الترويسة <code>Last-Event-ID</code>.
	</li>
</ul>
<p>
	وتوابع EventSource فتتمثل بالتابع:
</p>

<ul>
<li>
		<code>()close</code>: يغلق الاتصال.
	</li>
</ul>
<p>
	أما أحداث EventSource فهي:
</p>

<ul>
<li>
		<code>message</code>: عند استلام رسالة، وتكون متاحةً باستخدام <code>event.data</code>.
	</li>
	<li>
		<code>open</code>: عندما يكون الاتصال مفتوحًا.
	</li>
	<li>
		<code>error</code>: في حال وقع خطأ، بما في ذلك فقدان الاتصال أو الأخطاء القاتلة، يمكننا التحقق من الخاصية <code>readyState</code> للتأكد من وجود محاولات لإعادة الاتصال.
	</li>
</ul>
<p>
	يمكن للخادم اختيار اسم مخصص لحدث ما من خلال <code>:event</code>، وينبغي معالجة هذه الأحداث من خلال مستمع حدث <code>addEventListener</code> وليس باستخدام <code>&lt;on&lt;event</code>.
</p>

<p>
	رأينا من أجل تنسيق استجابة الخادم أن الخادم يرسل الرسائل مفصولةً عن بعضها باستخدام<code>n\n\</code>، ويمكن أن تحتوي الرسالة الحقول التالية:
</p>

<ul>
<li>
		<code>:data</code> ويمثل جسم الرسالة، وتُفسَّر سلسلة من الحقول <code>data</code> كرسالة واحدة يفصل بين أجزائها الكتلة <code>n\</code>.
	</li>
	<li>
		<code>:id</code> تُجدِّد قيمة <code>lastEventId</code> وتُرسَل ضمن الترويسة <code>Last-Event-ID</code> عند إعادة الاتصال.
	</li>
	<li>
		<code>:retry</code> تحدد فترة التأخير الزمني بين كل محاولتين لإعادة الاتصال بالميلي ثانية، ولا يمكن ضبطها باستخدام JavaScript.
	</li>
	<li>
		<code>:event</code> اسم الحدث وينبغي أن تسبق الحقل <code>data</code>.
	</li>
</ul>
<p>
	يمكن أن تحتوي الرسالة على أحد الحقول السابقة أو أكثر وبأي ترتيب، لكن الحقل <code>:id</code> يأتي في النهاية عادةً.
</p>

<p>
	ترجمة -وبتصرف- للفصول <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://javascript.info/long-polling" rel="external nofollow">Long polling</a> و<a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://javascript.info/websocket" rel="external nofollow">WebSocket</a> و<a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://javascript.info/server-sent-events" rel="external nofollow">Server sent events</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D8%B3%D8%AA%D8%A6%D9%86%D8%A7%D9%81-%D8%B1%D9%81%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1300/" rel="">استئناف رفع الملفات في جافاسكريبت</a>
	</li>
	<li>
		<a data-ss1634994626="1" data-ss1634995814="1" data-ss1634995994="1" data-ss1634996197="1" data-ss1634996321="1" href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B7%D9%88%D9%8A%D8%B9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1271/" rel="">تطويع البيانات في جافاسكربت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1301</guid><pubDate>Tue, 07 Sep 2021 15:00:00 +0000</pubDate></item><item><title>&#x639;&#x644;&#x627;&#x642;&#x629; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x64A;&#x628;&#x62A; &#x628;&#x62A;&#x637;&#x648;&#x631; &#x627;&#x644;&#x625;&#x646;&#x62A;&#x631;&#x646;&#x62A; &#x648;&#x627;&#x644;&#x645;&#x62A;&#x635;&#x641;&#x62D;&#x627;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%B9%D9%84%D8%A7%D9%82%D8%A9-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-%D8%A8%D8%AA%D8%B7%D9%88%D8%B1-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-r1310/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_09/61388f738843e_----.png.cc6d7a6714848d4772c6d12b04114aa0.png" /></p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		يُعَدّ توفير فضاء مشترك للمعلومات نتواصل فيه من خلال مشاركة هذه المعلومات حلمًا بُنيت عليه الشبكة العنكبوتية، كما تُعَدّ عمومية هذه الشبكة لازمةً وضروريةً، إذ يستطيع الرابط الفائق hypertext link الإشارة إلى أي شيء سواءً كان شخصيًا أو محليًا أو عالميًا، مسودةً كان أو نسخةً منقحةً.
	</p>

	<p>
		ـــ تِم بِرنَرز لي Tim Berners-Lee، الشبكة العنكبوتية العالمية: تاريخ شخصي قصير للغاية.
	</p>
</blockquote>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="76397" href="https://academy.hsoub.com/uploads/monthly_2021_09/61388f76055a4_Pictureofatelephoneswitchboard.jpg.76fe9712ac3b9c12193a9de6bb95ea8a.jpg" rel=""><img alt="Picture of a telephone switchboard.jpg" class="ipsImage ipsImage_thumbnailed" data-fileid="76397" data-unique="rejjpm5q3" src="https://academy.hsoub.com/uploads/monthly_2021_09/61388f76055a4_Pictureofatelephoneswitchboard.jpg.76fe9712ac3b9c12193a9de6bb95ea8a.jpg"></a>
</p>

<p>
	سنتحدث في المقالات الباقية من هذه السلسلة عن متصفحات الويب، فبدونها لم تكن جافاسكربت لتكون أصلًا، وإذا وُجدت لسبب ما فلم يكن أحد ليلتفت إليها.
</p>

<p>
	بُنيت تقنية الشبكات لتكون غير مركزية من البداية، سواءً على الصعيد الفني أو بالنسبة للطريقة التي تطورت بها، فقد أضافت العديد من الجهات الموفِّرة للمتصفحات وظائفًا جديدةً لأغراض محددة بعينها أحيانًا، وبدون تفكير أحيانًا أخرى لتعطينا وظائف ومزايا جديدة في صور سيئة، ثم يدور الزمن بهذه وتلك إلى أن يتبناها أحد ما ثم تصير معيارًا قياسيًا في النهاية.
</p>

<p>
	ونرى أنّ هذا قد يكون نعمةً ونقمةً في الوقت نفسه، فمن الجيد ألا يكون لديك تحكم مركزي في نظام مثل الإنترنت ويكون تطوره على يد جهات مختلفة تتعاون أحيانًا فيما بينها بصورة طفيفة، وتكاد تناوش بعضها أحيانًا أخرى؛. لكن من الناحية الأخرى، تعني الطريقة العفوية التي تطور بها الإنترنت أنّ النظام الناتج لن يكون هو الآلية التي يتوقعها البعض من حيث الاتساق النظامي الداخلي، فبعض أجزاء هذا النظام مشوشة إلى حد الحيرة.
</p>

<h2>
	الشبكات والإنترنت
</h2>

<p>
	وُجِدت شبكات الحواسيب منذ خمسينات القرن الماضي، ذلك أنك إذا وصلت حاسوبين أو أكثر بسلك لنقل البيانات فيما بينها، فستستطيع فعل الأعاجيب بهذه الشبكة الصغيرة، وعليه فهذه الأعاجيب تزداد عجبًا حين نوصل جميع الحواسيب في العالم ببعضها البعض.
</p>

<p>
	وقد بدأت التقنية التي تطبق هذه الرؤية في التطوير في الثمانينيات، ثم حصلنا على الشبكة التي تسمى بالإنترنت، وقد كانت كما حلمنا بها بالضبط.
</p>

<p>
	يستخدِم الحاسوب هذه الشبكة ليرسل بِتَّات من البيانات إلى حاسوب آخر، ويجب على كلا الحاسوبين معرفة ماذا يفترض لهذه البِتَّات أن تكون وماذا تمثل من أجل تحقيق تواصل فعال، ويتوقف معنى أي تسلسل من البتات على نوع الشيء المراد التعبير عنه وعلى آلية الترميز encoding mechanism المستخدَمة.
</p>

<p>
	يصف بروتوكول الشبكة network protocol أسلوبًا من التواصل عبر أي شبكة، فهناك بروتوكولات لإرسال البريد الإلكتروني وجلبه ومشاركة الملفات، وحتى التحكم في الحواسيب التي قد تكون مصابةً ببرمجيات خبيثة، فيُستخدَم بروتوكول نقل النصوص الفائقة HTTP مثلًا -وهو اختصار لـ Hypertext Transfer Protocol- لجلب الموارد المسمّاة وكتل المعلومات مثل صفحات الويب أو الصور، كما يشترط على الجزء الذي سينشئ الطلب البدء بسطر يشبه السطر التالي مسميًا المورد وإصدار البروتوكول الذي يريد استخدامه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6965_15" style="">
<span class="pln">GET /index.html HTTP/1.1</span></pre>

<p>
	هناك قواعد كثيرة تحكم الطريقة التي يمكن للطالب requester فيها إدخال بيانات أو معلومات في الطلب، والطريقة التي يحزِّم الطرف الآخر بها الموارد المطلوبة، وهو مستقبِل الطلب الذي يعيد هذه الموارد إلى طالبها..
</p>

<p>
	تُبنى أغلب البروتوكولات على بروتوكولات أخرى، حيث يتعامل بروتوكول HTTP مع الشبكة كما لو كانت أداة يضع فيها البِتّات ويتوقع منها الوصول إلى الوجهة الصحيحة بالترتيب المتوقع لها، لكن الواقع المشاهَد يقول عكس ذلك كما رأينا في مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%BA%D9%8A%D8%B1-%D8%A7%D9%84%D9%85%D8%AA%D8%B2%D8%A7%D9%85%D9%86%D8%A9-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1304/" rel="">البرمجة غير المتزامنة في جافاسكريبت</a>. ويعالِج بروتوكول التحكم في النقل TCP -اختصارًا لـ Transmission Control Protocol- هذه المشكلة بما أن كل الحواسيب المتصلة بالإنترنت تفهمه، وقد بُنيت أغلب عمليات التواصل في الإنترنت عليه أصلًا.
</p>

<p>
	فكرة عمل بروتوكول TCP هي أنّ الحاسوب يجب أن يكون في وضع انتظار أو استماع للحواسيب الأخرى حتى تكلمه أو تراسله، كما يجب أن يكون لكل مستمِع رقمًا يسمى منفَذًا port ويرتبط به من أجل أن تستطيع استماع عمليات تواصل مختلفة في الوقت نفسه على آلة واحدة، كما تحدد أغلب البروتوكولات المنفَذ الذي يجب استخدامه افتراضيًا، فحين نريد إرسال بريد إلكتروني باستخدام بروتوكول SMTP مثلًا، فيجب أن تستمع الآلة التي نرسله من خلالها إلى المنفَذ 25، ثم سينشئ حاسوب آخر حينئذ اتصالًا من خلال التوصيل بالآلة الهدف مستخدمًا رقم المنفَذ الصحيح، فإذا استطاع الوصول إلى الآلة الهدف وكانت تستمع لهذا المنفَذ، فسنقول أنّ الاتصال تحقق بنجاح، كما يُسمى الحاسوب المستمع هنا بالخادم server والحاسوب المتصَل به بالعميل client.
</p>

<p>
	يعمل مثل ذلك الاتصال على أساس خط أنابيب ثنائي الاتجاه يمكن للبِتّات أن تتدفق فيه من كلا الاتجاهين، مما يعني استطاعة كلا الحاسوبين إرسال البيانات واستلامها عبره، وبمجرد أن تُنقل البِتّات بنجاح، فسيمكن للآلة التي على الطرف الآخر قراءتها مرةً أخرى، وهذا النموذج مريح جدًا، بل يمكن القول أنّ TCP هو النموذج المجرَّد للشبكة.
</p>

<h2>
	الشبكة العنكبوتية العالمية The Web
</h2>

<p>
	تُعَدّ الشبكة العنكبوتية العالمية -أو الويب- مجموعةً من البروتوكولات والصيغ التي تسمح لنا بزيارة صفحات الويب داخل متصفح ما، وتشير كلمة الشبكة العنكبوتية فيها إلى حقيقة إشارة هذه الصفحات إلى بعضها بعضًا وارتباطها ببعضها، وبالتالي تكوّن شبكةً عملاقةً يستطيع المستخدِم التنقل خلالها بحُرية، كما يجدر بنا الإشارة هنا إلى أنّ هذه الشبكة ليست هي كل الإنترنت، فالأخير أوسع وأشمل.
</p>

<p>
	لكي تكون جزءًا من هذه الشبكة، لا تحتاج إلا إلى توصيل آلة بالإنترنت وتجعلها تستمع إلى المنفَذ 80 ببروتوكول HTTP كي تستطيع الحواسيب الأخرى طلب مستندات وملفات منها، ويُسمى كل مستند على الشبكة بمحدِّد موقع الموارد الموحَّد Uniform Resource Locator -أو URL اختصارًا-، حيث يكون أشبه بما يلي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6965_17" style="">
<span class="pln">  http</span><span class="pun">:</span><span class="com">//eloquentjavascript.net/13_browser.html</span><span class="pln">
 </span><span class="pun">|</span><span class="pln">      </span><span class="pun">|</span><span class="pln">                      </span><span class="pun">|</span><span class="pln">               </span><span class="pun">|</span><span class="pln">
 protocol       server               path</span></pre>

<p>
	يخبرنا أول جزء أنّ هذا الرابط يستخدم بروتوكول HTTP على خلاف بروتوكول HTTP المشفَّر مثلًا التي سيكون هكذا: <code>https://‎</code>، ثم يعرِّف الجزءُ الذي يليه الخادمَ الذي نطلب منه المستند، ولدينا في النهاية مسار من سلسلة نصية يعرِّف المستند المحدَّد أو المورد الذي نريد.
</p>

<p>
	تحصل الآلات المتصلة بالإنترنت على عنوان IP يكون رقمًا يمكن استخدامه لإرسال رسائل إلى هذه الآلة، حيث يبدو هكذا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_2720_6" style="">
<span class="lit">149.210</span><span class="pun">.</span><span class="lit">142.219</span><span class="pln"> </span><span class="pun">أو</span><span class="pln"> </span><span class="lit">2001</span><span class="pun">:</span><span class="lit">4860</span><span class="pun">:</span><span class="lit">4860</span><span class="pun">‎::‎</span><span class="lit">8888</span><span class="pln">
</span></pre>

<p>
	غير أن هذه الأرقام المتتالية يصعب حفظها وكتابتها، لهذا تستطيع تسجيل اسم نطاق لأحد تلك العناوين أو بعضها، وقد سجلنا eloquentjavascript.net مثلًا لنشير إلى عنوان IP لآلة نتحكم فيها ونستطيع استخدام اسم النطاق حينئذ لإرسال صفحات الويب لمن يطلبها.
</p>

<p>
	إذا كتبتَ محدِّد المواقع ذاك في شريط عنوان المتصفح، فسيحاول المتصفح جلب المستند الذي في ذلك الرابط وعرضه، فيبدأ بالنظر في العنوان الذي يشير إليه eloquentjavascript.net، ثم يستخدِم بروتوكول HTTP ليحقق اتصالًا بالخادم الذي في ذلك العنوان ويطلب المورِد <code>‎/13_browser.html</code>، وإذا تم ذلك كله فسيرسل الخادم حينئذ مستندًا يعرضه لك المتصفح على الشاشة.
</p>

<h2>
	HTML
</h2>

<p>
	تُهيأ صفحات الويب التي تراها أمامك على الشاشة بتنسيق يتكون من تراكيب محددة، واللغة التي نكتب بها تلك التراكيب اسمها HTML أو Hypertext Markup Language وتعني لغة ترميز النصوص الفائقة، ويحتوي المستند العادي المكتوب بهذه اللغة على نصوص ووسوم tags، وتحدد التركيب البنائي لتلك النصوص كما تصف الأنواع المختلفة لها سواءً كانت روابط أو فقرات أو عناوين، وتبدو هذه اللغة هكذا:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6965_19" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">
</span><span class="tag">&lt;html&gt;</span><span class="pln">
  </span><span class="tag">&lt;head&gt;</span><span class="pln">
    </span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">
    </span><span class="tag">&lt;title&gt;</span><span class="pln">My home page</span><span class="tag">&lt;/title&gt;</span><span class="pln">
  </span><span class="tag">&lt;/head&gt;</span><span class="pln">
  </span><span class="tag">&lt;body&gt;</span><span class="pln">
    </span><span class="tag">&lt;h1&gt;</span><span class="pln">My home page</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;</span><span class="pln">Hello, I am Marijn and this is my home page.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
    </span><span class="tag">&lt;p&gt;</span><span class="pln">I also wrote a book! Read it
      </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">"http://eloquentjavascript.net"</span><span class="tag">&gt;</span><span class="pln">here</span><span class="tag">&lt;/a&gt;</span><span class="pln">.</span><span class="tag">&lt;/p&gt;</span><span class="pln">
  </span><span class="tag">&lt;/body&gt;</span><span class="pln">
</span><span class="tag">&lt;/html&gt;</span></pre>

<p>
	توفِّر الوسوم معلومات عن بنية المستند وتحاط بقوسين محددين هما رمزا "أصغر من" و"أكبر من" في الرياضيات كما ترى في المثال أعلاه؛ أما بقية النص فيكون نصًا عاديًا، كما يبدأ المستند بوسم <code>‎&lt;!doctype html&gt;‎</code> الذي يخبر المتصفح أن يفسر الصفحة وفق لغة HTML الحديثة وليس الإصدارات القديمة المختلفة منها والتي تكون عادةً قبل HTML5.
</p>

<p>
	تمتلك مستندات HTML في الغالب ترويسةً head ومتن body، كما تحتوي الترويسة على معلومات عن المستند؛ أما المتن فيحتوي على النصوص التي في المستند نفسه، وفي هذه الحالة تقول الترويسة أنّ عنوان هذه المستند هو "My home page" وأنها تستخدِم ترميز UTF-8 الذي هو أسلوب نتبعه لترميز نصوص اليونيكود على أساس بيانات ثنائية.
</p>

<p>
	يحتوي متن المستند على ترويسة واحدة، وهي <code>&lt;h1&gt;</code> التي تعني الترويسة الأولى، و<code>&lt;h2&gt;</code> حتى <code>&lt;h6&gt;</code> لتشير إلى ترويسات فرعية، كما يحتوي على فقرتين اثنتين محدَّدتين بوسم <code>&lt;p&gt;</code>.
</p>

<p>
	تأتي الوسوم في أشكال كثيرة، فيبدأ العنصر مثل المتن أو الفقرة أو الرابط بوسم افتتاحي مثل <code>&lt;p&gt;</code>وينتهي بوسم غالق مثل <code>‎&lt;/p&gt;‎</code>، وقد تكون بعض الوسوم الافتتاحية فيها معلومات أكثر مثل وسم <code>&lt;a&gt;</code> في صورة أزواج من <code>name="value"‎</code>، وتسمى هذه المعلومات بالسمات attributes، وفي حالتنا فإن وجهة الرابط توضَّح بالآتي: <br>
	 
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6965_22" style="">
<span class="pln">href</span><span class="pun">=</span><span class="str">"http://eloquentjavascript.net"</span><span class="pun">‎</span><span class="pln">
</span></pre>

<p>
	 وتشير <code>href</code> هنا إلى مرجع لنص فائق hypertext reference.
</p>

<p>
	قد لا تغلف بعض الوسوم أيّ شيء ولا تحتاج إلى إغلاقها، ويُعَدّ وسم البيانات الوصفية metadata الذي يأتي بصورة الآتية مثالًأ على ذلك:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_2720_8" style="">
<span class="pln"> ‎</span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">"utf-8"</span><span class="tag">&gt;</span><span class="pln">‎
</span></pre>

<p>
	إذا أردنا كتابة أقواس محددة <code>&lt;&gt;</code> داخل نص مستند لتظهر كما هي من غير تفسيرها على أنها محددات وسوم، فيجب كتابتها بصيغة خاصة بها، بحيث يُكتب القوس الابتدائي <code>‎&lt;‎</code> الذي يمثل علامة "أقل من" في الرياضيات على الصورة <code>‎&amp;lt;‎</code>، وكذلك القوس الغالق <code>‎&gt;‎</code> الذي يمثل علامة أكبر من في الرياضيات على الصورة <code>‎&amp;gt;‎</code>. وهكذا، نكتب محرف آمبرساند <code>&amp;</code> ثم اسم المحرف أو رمزه ثم فاصلة منقوطة <code>;</code>، وتُعرف هذه الصيغة باسم الوحدة entity، حيث تُستبدَل بالمحرف الذي تعبِّر عنه، وذلك مشابه للطريقة التي تُستخدَم بها الشرطة المائلة العكسية في سلاسل جافاسكربت النصية.
</p>

<p>
	بما أنّ هذه الآلية تعطي معنى خاصًا لمحارف <code>&amp;</code>، فإنها بذاتها تحتاج إلى تهريب بأن تُكتب <code>‎&amp;amp;‎</code> لتظهر بصورتها إذا احتجنا إلى إظهارها في النص، وبالمثل لما بين قيم السمات المغلفة بعلامات تنصيص مزدوجة، إذ يمكن استخدام <code>‎&amp;quot;‎</code> لإدخال محرف علامة التنصيص ليظهر بصورته.
</p>

<p>
	تُحلَّل HTML بأسلوب يتسامح كثيرًا مع الأخطاء، فإذا كان أحد الوسوم مفقودًا من موضع ما أو نسينا كتابته، فسيكتبه المتصفح ليعالج الخلل، وقد اعتُمِد الأسلوب الذي يحدث به ذلك بحيث تستطيع الاعتماد على جميع المتصفحات الحديثة التي تفعل الشيء نفسه لمعالجة أخطاء الكتابة.
</p>

<p>
	انظر المستند التالي الذي سيعامَل على أنه المستند الذي كتبناه في المثال أعلاه رغم أخطاء الصياغة والبناء التي فيه:
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6965_24" style="">
<span class="dec">&lt;!doctype html&gt;</span><span class="pln">

</span><span class="tag">&lt;meta</span><span class="pln"> </span><span class="atn">charset</span><span class="pun">=</span><span class="atv">utf-8</span><span class="tag">&gt;</span><span class="pln">
</span><span class="tag">&lt;title&gt;</span><span class="pln">My home page</span><span class="tag">&lt;/title&gt;</span><span class="pln">

</span><span class="tag">&lt;h1&gt;</span><span class="pln">My home page</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
</span><span class="tag">&lt;p&gt;</span><span class="pln">Hello, I am Marijn and this is my home page.
</span><span class="tag">&lt;p&gt;</span><span class="pln">I also wrote a book! Read it
  </span><span class="tag">&lt;a</span><span class="pln"> </span><span class="atn">href</span><span class="pun">=</span><span class="atv">http://eloquentjavascript.net</span><span class="tag">&gt;</span><span class="pln">here</span><span class="tag">&lt;/a&gt;</span><span class="pln">.</span></pre>

<p>
	عرف المتصفح قد أنّ وسوم <code>&lt;html&gt;</code> و<code>&lt;head&gt;</code> و<code>&lt;body&gt;</code> ليست موجودة، وعرف أنّ الوسمين <code>&lt;meta&gt;</code> و<code>&lt;title&gt;</code> ينتميان إلى الترويسة، والوسم <code>&lt;h1&gt;</code> تعني أنّ متن المستند بدايته من هنا.
</p>

<p>
	كما لم يعد هناك حاجة إلى إغلاق الفقرات بصورة صريحة بما أنّ بدء فقرة جديدة أو إنهاء المستند سيغلقها ضمنيًا، ولعلك لاحظت إن كنت منتبهًا إلى أن علامات التنصيص التي حول قيم السمات ليست موجودة أيضًا.
</p>

<p>
	سنهمِل في هذه السلسلة وسوم <code>&lt;html&gt;</code> و<code>&lt;head&gt;</code> و<code>&lt;body&gt;</code> من الأمثلة للاختصار ولإبقاء الأمثلة بسيطة، لكن سنغلق الوسوم ونضيف علامات التنصيص حول السمات، كما قد نهمِل تصريح <code>doctype</code> و<code>charset</code>، لكن لا يعني هذا أننا نشجعك على ذلك، فقد تجد المتصفح يتصرف بغرابة ويفعل أشياءً سخيفةً إذا أهملتها، لذلك نريدك أن تتصرف كما لو كانت البيانات الوصفية الخاصة بهما موجودة في الأمثلة حتى لو لم تكن ظاهرة فعلًا في النص أمامك.
</p>

<h2>
	HTML وجافاسكربت
</h2>

<p>
	ما يهمنا في HTML فيما يتعلق بهذه السلسلة هو وسم <code>&lt;script&gt;</code>، إذ يسمح لنا بإدخال شيفرة جافاسكربت داخل المستند.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6965_26" style="">
<span class="tag">&lt;h1&gt;</span><span class="pln">Testing alert</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
</span><span class="tag">&lt;script&gt;</span><span class="pln">alert</span><span class="pun">(</span><span class="str">"hello!"</span><span class="pun">);</span><span class="tag">&lt;/script&gt;</span></pre>

<p>
	ستعمل مثل تلك الشيفرة عندما يرى المتصفح وسم <code>&lt;script&gt;</code> أثناء قراءة HTML، وستُظهر الصفحة نافذة منبثقة حين تُفتح، كما تُشبه دالة <code>alert</code> في المثال أعلاه <code>prompt</code> إلا أنها تخرج نافذة منبثقة تعرض رسالةً ما دون طلب إدخال أي بيانات.
</p>

<p>
	يمثِّل المثال السابق برنامجًا صغير الحجم؛ أما إدخال برامج كبيرة مباشرةً في HTML فهو غير عملي، وبدلًا من ذلك يمكن تزويد وسم <code>&lt;script&gt;</code> بسِمة <code>src</code> لجلب ملف سكربت خارجي من رابط URL ما، وهو ملف نصي يحتوي على برنامج جافاسكربت.
</p>

<p>
	انظر المثال التالي حيث يحتوي ملف code/hello.js على البرنامج السابق نفسه <code>alert("hello!")‎</code>.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6965_28" style="">
<span class="tag">&lt;h1&gt;</span><span class="pln">Testing alert</span><span class="tag">&lt;/h1&gt;</span><span class="pln">
</span><span class="tag">&lt;script</span><span class="pln"> </span><span class="atn">src</span><span class="pun">=</span><span class="atv">"code/hello.js"</span><span class="tag">&gt;&lt;/script&gt;</span></pre>

<p>
	حين تشير صفحة HTML إلى روابط أخرى لتكون أجزاء منها مثل صورة أو ملف سكربت، فستجلبها المتصفحات مباشرةً أثناء تحميل الصفحة لتعرضها لك مع الصفحة نفسها في الأماكن التي حددها ملف HTML.
</p>

<p>
	يجب إغلاق وسم <code>&lt;script&gt;</code> دومًا بوسمه الغالق ‎‏<code>‎&lt;‏‏‏‎/‎script‎‏‏‎‎‏‎‏‏‏&gt;‎</code> حتى لو كان يشير إلى ملف سكربت لا يحتوي أي شيفرة، فإذا نسيت ذلك، فسستُفسَّر بقية الصفحة على أنها جزء من السكربت.
</p>

<p>
	تستطيع تحميل وحدات ES -التي وردت في مقال <a href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%AD%D8%AF%D8%A7%D8%AA-modules-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1286/" rel="">الوحدات Modules في جافاسكريبت</a>- في المتصفح بتزويد وسم script بسِمة <code>type="Module"‎</code>، كما يمكن أن تعتمد مثل هذه الوحدات على وحدات أخرى باستخدام روابط متعلقة بها على أساس أسماء وحدات في تصريحات <code>import</code>.
</p>

<p>
	كذلك يمكن لبعض السمات أن تحتوي على برنامج جافاسكربت، فالوسم <code>&lt;button&gt;</code> الذي في المثال التالي والذي سيُظهر زرًا به سمة <code>onclick</code>، كما ستعمل قيمة السمة في كل مرة نضغط على الزر فيها.
</p>

<pre class="ipsCode prettyprint lang-html prettyprinted" id="ips_uid_6965_30" style="">
<span class="tag">&lt;button</span><span class="pln"> </span><span class="atn">onclick</span><span class="pun">=</span><span class="atv">"</span><span class="pln">alert</span><span class="pun">(</span><span class="str">'Boom!'</span><span class="pun">);</span><span class="atv">"</span><span class="tag">&gt;</span><span class="pln">لا تضغط هنا</span><span class="tag">&lt;/button&gt;</span></pre>

<p>
	لاحظ أننا اضطررنا لاستخدام علامات تنصيص مفردة للسلسلة النصية التي في سمة <code>onclick</code> لأن العلامات المزدوجة كانت مستخدِمة سلفًا للسمة ككل، لكن كان بإمكاني أن أستخدم <code>;quot‏&amp;‎</code> كذلك.
</p>

<h2>
	داخل صندوق الاختبارات sandbox
</h2>

<p>
	لا شك أنّ تشغيل البرامج المحمَّلة من الإنترنت قد يكون خطيرًا، إذ أنك لا تدري مَن وراء أغلب المواقع التي تزورها، وقد يكون فيهم الخبيث واللئيم، كما أنه لا نريد اختراق حواسيبنا بسبب إغفالنا لأمر مثل هذا، لكن الجميل في الموضوع هنا أنك تستطيع تصفح الويب دون الحاجة إلى توكيد الثقة في كل صفحة تزورها، لهذا فإنّ المتصفحات تحدّ كثيرًا من الصلاحيات التي تكون لبرامج جافاسكربت، فلا تستطيع النظر في الملفات التي على حاسوبك أو تعديل أي شيء غير متعلق بالصفحة التي هي فيها.
</p>

<p>
	ويسمى هذا العزل لتلك البرامج بصندوق الرمل أو صندوق الاختبار Sandbox، وإن كانت الترجمة الحرفية له هي صندوق الرمل إلا أنّ وظيفته هي عزل البرمجيات التي لا نثق فيها إلى حين البَت في أمرها أو تنتفي الحاجة إليها، وتعمل هذه البرامج كما تشاء داخل هذا الصندوق دون أذى للحاسوب نفسه أو لبياناتك، فلو كان هذا الصندوق قفصًا حديديًا سميك الجدران، فستكون البرامج حينها هي الكائنات التي تعبث فيه كما تشاء دون أن تؤذي من هو خارج القفص، ودون أن تستطيع هي نفسها الخروج.
</p>

<p>
	لكن الأمر الصعب عند التعامل مع هذا الصندوق هو السماح لهذه البرامج بفسحة تكفي لتكون مفيدةً مع حجزها عن التسبب في أذى للحاسوب والبيانات التي عليه في الوقت نفسه، لكن احذر أن تشمل هذه الفسحة أمورًا مثل التواصل مع الخوادم الأخرى وقراءة محتويات حافظة النسخ واللصق، فهنا قد يتطور الأمر إلى منحى خطير على الخصوصية.
</p>

<p>
	قد يخرج علينا أحد بين الحين والآخر بطريقة جديدة للتحايل على حدود المتصفح والتسبب في اختراق وأذى للمستخدم من تسريب معلومات طفيفة عنه حتى السيطرة التامة على الجهاز المخترَق، كما يستجيب مطورو المتصفحات لهذه التهديدات بإصلاح الثغرات التي تتسبب فيها إلى حين ظهور مشكلة جديدة، لكن مكمن الخطر هنا هو عدم إدراك المطورين للمشكلة إذا لم تُفضَح وتنتشر، فإذا استغلها أحد أو شركة أو منظمة أو جهة حكومية دون الإبلاغ عنها فستكون هنا الخسائر المحتملة أكبر.
</p>

<h2>
	التوافقية وحروب المتصفحات
</h2>

<p>
	كان المتصفح المسيطر على الساحة في بدايات الويب يسمى موزايك Mosaic، ثم انتقل السوق إلى متصفح نِت سكيب Netscape الذي ما لبث أن أفرغ الساحة قهرًا لمتصفح إنترنت إكسبلورر Internet Explorer الشهير الخاص بمايكروسوفت.
</p>

<p>
	كانت الجهة التي تقف خلف المتصفح في كل مرة تشعر أنّ لها الحق في اختراع المزايا الجديدة التي تشاء للويب، وبما أن أغلب المستخدِمين كانوا يستخدِمون متصفحها، فستضطر المواقع إلى استخدام تلك المزايا مع غض النظر عن باقي المتصفحات، وهذا سبب الكثير من مشاكل التوافقية للمواقع مع المتصفحات المختلفة. نستطيع القول أنّ هذا كان هو العصر المظلم للتوافقية، والذي كان يُعرف بعصر حروب المتصفحات، كما كان المطورون يجدون أنفسهم بين منصتين أو ثلاث لا تتوافق أي منها مع الأخرى بدلًا من ويب واحدة، وزاد الطين بلة امتلاء المتصفحات التي كانت في السوق في بداية الألفية الثالثة -نحو 2003- بالزلات البرمجية Bugs والمشاكل، وكل متصفح له مشاكله، وهكذا كانت حياة المطورين أشبه بالحرب فعليًا.
</p>

<p>
	ظهر بعد ذلك متصفح موزيلا فاير فوكس Mozilla Firefox، وهو فرع لا يهدف للربح من نِت سكيب ليبارز إنترنت إكسبلورر في السوق في أواخر عام 2002، وقد سيطر على حصة كبيرة من السوق لعدم اهتمام مايكروسوفت بمتابعة التطوير وزهدها في مجال المتصفحات حينها، ثم خرجت جوجل بمتصفح خاص بها هو جوجل كروم Chrome، وكذلك شركة آبل بمتصفح سفاري Safari الذي اكتسب شهرةً بسبب أنه يأتي مع أجهزتها تلقائيًا، وبالتالي صار لدينا أربعة متصفحات رئيسية على الساحة بدلًا من واحد فقط.
</p>

<p>
	اتجهت هذه الهيئات الأربعة نحو وضع معايير موحَّدة وأساليب هندسية أفضل لتصميم هذه المتصفحات، لتعطينا متصفحات لا تعاني كثيرًا من الزلات البرمجية ولا مشاكل التوافقية، وقد رأت مايكروسوفت مؤخرًا أنّ حصتها في هذا السوق قد تهاوت كثيرًا، فتبنّت هذا الرأي في النهاية في متصفح إيدج Edge الخاص بها واستبدلت به متصفحها القديم.
</p>

<p>
	يصب كل هذا في مصلحة من يقرِّر تعلم تطوير الويب هذه الأيام، إذ أن الإصدارات الأخيرة من هذه المتصفحات تكاد تتصرف بالأسلوب نفسه تقريبًا وليس لديها الكثير من الزلات البرمجية.
</p>

<p>
	ترجمة -بتصرف- <a href="https://eloquentjavascript.net/13_browser.html" rel="external nofollow">للفصل الثالث عشر من كتاب Elequent Javascript</a> لصاحبه Marijn Haverbeke.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a href="https://academy.hsoub.com/programming/advance/%D9%85%D8%B4%D8%B1%D9%88%D8%B9-%D8%A8%D9%86%D8%A7%D8%A1-%D9%84%D8%BA%D8%A9-%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-r1305/" rel="">مشروع بناء لغة برمجة خاصة</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/general/%D9%85%D8%A7-%D9%87%D9%8A-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%88%D9%85%D8%AA%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-%D8%AA%D8%B9%D9%84%D9%85%D9%87%D8%A7%D8%9F-r1279/" rel="">ما هي البرمجة ومتطلبات تعلمها؟</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/javascript/%D9%87%D9%8A%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D8%AC-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1232/" rel="">هيكل البرنامج في جافاسكريبت</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/html/html5/%D8%A7%D9%83%D8%AA%D8%B4%D8%A7%D9%81-%D8%AF%D8%B9%D9%85-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-%D9%84%D9%85%D9%8A%D8%B2%D8%A7%D8%AA-html5-r340/" rel="">اكتشاف دعم المتصفحات لميزات HTML5</a>
	</li>
	<li>
		<a href="https://academy.hsoub.com/programming/css/%D8%AA%D9%86%D8%B3%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D8%AE%D8%B5%D8%B5%D8%A9-%D9%88%D8%AF%D8%B9%D9%85%D9%87%D8%A7-%D9%88%D8%A3%D8%AF%D8%A7%D8%A1%D9%87%D8%A7-%D9%81%D9%8A-css-r1061/" rel="">تنسيقات المتصفحات المخصصة ودعمها وأداءها في CSS</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1310</guid><pubDate>Sat, 04 Sep 2021 15:00:00 +0000</pubDate></item><item><title>&#x627;&#x633;&#x62A;&#x626;&#x646;&#x627;&#x641; &#x631;&#x641;&#x639; &#x627;&#x644;&#x645;&#x644;&#x641;&#x627;&#x62A; &#x628;&#x639;&#x62F; &#x641;&#x642;&#x62F;&#x627;&#x646; &#x627;&#x644;&#x627;&#x62A;&#x635;&#x627;&#x644; &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A7%D8%B3%D8%AA%D8%A6%D9%86%D8%A7%D9%81-%D8%B1%D9%81%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D8%A8%D8%B9%D8%AF-%D9%81%D9%82%D8%AF%D8%A7%D9%86-%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1300/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_08/6119500cf410e_--.png.3ef21eefac0720a0619da3cdda0114e2.png" /></p>

<p>
	من السهل رفع ملف باستخدام <code>fetch</code>، لكن كيف سنستأنف عملية الرفع بعد فقدان الاتصال؟ لا توجد خيارات مدمجة لتنفيذ هذا الأمر، لكن لدينا كل الأجزاء التي تمكننا من تنفيذ ذلك.
</p>

<p>
	ينبغي أن تأتي عمليات الرفع القابلة للاستئناف مع مؤشرات على تقدم العملية، خاصةً عندما يكون الملف ضخمًا -إن أردنا استئناف الرفع-، وطالما لن تسمح <code>fetch</code> بتعقب عملية الرفع؛ فسنحتاج إلى الكائن <a data-ss1630666641="1" data-ss1634994412="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86-xmlhttprequest-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1299/" rel="">XMLHttpRequest</a>.
</p>

<h2>
	حدث تتبع غير مفيد كفاية
</h2>

<p>
	لا بدّ من معرفة الكمية التي جرى رفعها قبل انقطاع الاتصال لاستئناف رفع الملف، حيث يمتلك الكائن <code>xhr</code> الحدث <code>xhr.upload.onprogress</code> لتتبع تقدم عملية الرفع، لكنه لن يفيدنا في استئناف الرفع -لسوء الحظ-، لأنه سيقع عند انتهاء إرسال البيانات، لكن لا يمكن للمتصفح تحديد هل استقبلها الخادم فعلًا أم لا، فمن الممكن أن تكون هذه البيانات قد خُزّنت مؤقتًا من قبل شبكة محلية وكيلة، وربما تكون العملية انتهت على الخادم البعيد قبل أن يتمكن من معالجة البيانات، أو أنها ببساطة قد فُقدت في مرحلة ما ولم تصل إلى المستقبل النهائي، لذلك لن تتعدى فائدة هذا الحدث إظهار شريط تقدم جميل على الصفحة.
</p>

<p>
	إذًا لا بدّ من معرفة عدد البايتات التي استقبلها الخادم بدقة حتى نتمكن من استئناف الرفع، والخادم فقط هو من يمتلك هذه المعلومة، لذا فلا بدّ من إرسال طلب إضافي.
</p>

<h2>
	الخوارزمية
</h2>

<ol>
<li>
		ننشئ في البداية معرّفًا مميزًا id للملف الذي سنرفعه، بالشكل التالي:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6915_11" style="">
<span class="pln">let fileId </span><span class="pun">=</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">name </span><span class="pun">+</span><span class="pln"> </span><span class="str">'-'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">size </span><span class="pun">+</span><span class="pln"> </span><span class="str">'-'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">lastModified</span><span class="pun">;</span></pre>

<p>
	سنحتاج إليه لاستئناف الرفع، إذ لا بدّ من إبلاغ الخادم عن الملف الذي سنستأنف رفعه، حيث إذا تغير اسم أو حجم أو تاريخ آخر تعديل للبيانات، فسيتغيّر معرّف الملف <code>fileId</code>.
</p>

<ol start="2">
<li>
		ثم نرسل إلى الخادم طلبًا نسأله عن حجم البيانات التي وصلت إليه (مقدرًا بالبايت)، بالشكل التالي:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6915_13" style="">
<span class="pln">let response </span><span class="pun">=</span><span class="pln"> await fetch</span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  headers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="str">'X-File-Id'</span><span class="pun">:</span><span class="pln"> fileId
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

</span><span class="com">// تلقى الخادم هذا العدد من البايتات</span><span class="pln">
let startByte </span><span class="pun">=</span><span class="pln"> </span><span class="pun">+</span><span class="pln">await response</span><span class="pun">.</span><span class="pln">text</span><span class="pun">();</span></pre>

<p>
	هذا يفترض قدرة الخادم على تتبع تقدم عملية رفع الملف باستخدام الترويسة <code>X-File-Id</code>، وينبغي إنجاز ذلك في الواجهة الخلفية (من جانب الخادم)، فإذا لم يكن الملف موجودًا بعد على الخادم، فيستجيب الخادم بالرمز <code>0</code>.
</p>

<ol start="3">
<li>
		يمكننا استخدام التابع <code>slice</code> العائد لكائن البيانات الثنائية <code>Blob</code> لإرسال الملف ابتداءً من البايت الذي سنتابع بعده <code>startByte</code>، بالشكل التالي:
	</li>
</ol>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6915_15" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"POST"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"upload"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">);</span><span class="pln">

</span><span class="com">// معرّف الملف ليعلم الخادم اسم الملف الذي يُرفع</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'X-File-Id'</span><span class="pun">,</span><span class="pln"> fileId</span><span class="pun">);</span><span class="pln">

</span><span class="com">// البايت الذي سنستأنف منه الرفع</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'X-Start-Byte'</span><span class="pun">,</span><span class="pln"> startByte</span><span class="pun">);</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">onprogress </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Uploaded</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">startByte </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">}</span><span class="pln"> of $</span><span class="pun">{</span><span class="pln">startByte </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">total</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

</span><span class="com">//أو أي مصدر input.files[0] قد يكون مصدر الملف من  </span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">file</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="pln">startByte</span><span class="pun">));</span></pre>

<p>
	وهكذا سنرسل إلى الخادم معرّف الملف عبر الترويسة <code>X-File-Id</code> ليعرف الملف الذي نرفعه، كما سنرسل بايت البداية عبر الترويسة <code>X-Start-Byte</code> ليعرف الخادم بأننا لا نرفع الملف من البداية بل نستأنف عملية رفع سابقة، ولا بدّ أن يتحقق الخادم من سجلاته. فإذا جرت عملية رفع سابقة لهذا الملف وكان حجمه الحالي مساويًا تمامًا لقيمة <code>X-Start-Byte</code>؛ فسيُلحِق البيانات التي يستقبلها تاليًا به.
</p>

<p>
	إليك تطبيقًا نموذجيًا يضم شيفرتي الخادم والعميل وقد كتبت باستخدام Node.js، ويمكنك تنزيله وتشغيله محليًا لترى آلية عمله.
</p>

<p>
	شيفرة الخادم "server.js":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6915_17" style="">
<span class="pln">let http </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'http'</span><span class="pun">);</span><span class="pln">
let </span><span class="kwd">static</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'node-static'</span><span class="pun">);</span><span class="pln">
let fileServer </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="kwd">static</span><span class="pun">.</span><span class="typ">Server</span><span class="pun">(</span><span class="str">'.'</span><span class="pun">);</span><span class="pln">
let path </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'path'</span><span class="pun">);</span><span class="pln">
let fs </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'fs'</span><span class="pun">);</span><span class="pln">
let debug </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">'debug'</span><span class="pun">)(</span><span class="str">'example:resume-upload'</span><span class="pun">);</span><span class="pln">

let uploads </span><span class="pun">=</span><span class="pln"> </span><span class="typ">Object</span><span class="pun">.</span><span class="pln">create</span><span class="pun">(</span><span class="kwd">null</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> onUpload</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  let fileId </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">'x-file-id'</span><span class="pun">];</span><span class="pln">
  let startByte </span><span class="pun">=</span><span class="pln"> </span><span class="pun">+</span><span class="pln">req</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">'x-start-byte'</span><span class="pun">];</span><span class="pln">

  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">fileId</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span class="lit">400</span><span class="pun">,</span><span class="pln"> </span><span class="str">"No file id"</span><span class="pun">);</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">// we'll files "nowhere"</span><span class="pln">
  let filePath </span><span class="pun">=</span><span class="pln"> </span><span class="str">'/dev/null'</span><span class="pun">;</span><span class="pln">
  </span><span class="com">// يمكن استخدام مسار حقيقي عوضًا عنه، مثل: </span><span class="pln">
  </span><span class="com">// let filePath = path.join('/tmp', fileId);</span><span class="pln">

  debug</span><span class="pun">(</span><span class="str">"onUpload fileId: "</span><span class="pun">,</span><span class="pln"> fileId</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// تهيئة عملية رفع جديدة</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">uploads</span><span class="pun">[</span><span class="pln">fileId</span><span class="pun">])</span><span class="pln"> uploads</span><span class="pun">[</span><span class="pln">fileId</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="pun">{};</span><span class="pln">
  let upload </span><span class="pun">=</span><span class="pln"> uploads</span><span class="pun">[</span><span class="pln">fileId</span><span class="pun">];</span><span class="pln">

  debug</span><span class="pun">(</span><span class="str">"bytesReceived:"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> upload</span><span class="pun">.</span><span class="pln">bytesReceived </span><span class="pun">+</span><span class="pln"> </span><span class="str">" startByte:"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> startByte</span><span class="pun">)</span><span class="pln">

  let fileStream</span><span class="pun">;</span><span class="pln">

  </span><span class="com">// صفرًا أو غير مهيئ فسننشئ ملفًا جديدًا، وإلا فسيختبر الحجم ويضمه إلى الملف الموجود startByte إذا كان </span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">startByte</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    upload</span><span class="pun">.</span><span class="pln">bytesReceived </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
    fileStream </span><span class="pun">=</span><span class="pln"> fs</span><span class="pun">.</span><span class="pln">createWriteStream</span><span class="pun">(</span><span class="pln">filePath</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      flags</span><span class="pun">:</span><span class="pln"> </span><span class="str">'w'</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    debug</span><span class="pun">(</span><span class="str">"New file created: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> filePath</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// we can check on-disk file size as well to be sure</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">bytesReceived </span><span class="pun">!=</span><span class="pln"> startByte</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span class="lit">400</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Wrong start byte"</span><span class="pun">);</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">bytesReceived</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
    </span><span class="com">// ضمه إلى الملف الموجود</span><span class="pln">
    fileStream </span><span class="pun">=</span><span class="pln"> fs</span><span class="pun">.</span><span class="pln">createWriteStream</span><span class="pun">(</span><span class="pln">filePath</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      flags</span><span class="pun">:</span><span class="pln"> </span><span class="str">'a'</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">
    debug</span><span class="pun">(</span><span class="str">"File reopened: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> filePath</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">


  req</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'data'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">data</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    debug</span><span class="pun">(</span><span class="str">"bytes received"</span><span class="pun">,</span><span class="pln"> upload</span><span class="pun">.</span><span class="pln">bytesReceived</span><span class="pun">);</span><span class="pln">
    upload</span><span class="pun">.</span><span class="pln">bytesReceived </span><span class="pun">+=</span><span class="pln"> data</span><span class="pun">.</span><span class="pln">length</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  </span><span class="com">//  إرسال جسم الطلب إلى الملف</span><span class="pln">
  req</span><span class="pun">.</span><span class="pln">pipe</span><span class="pun">(</span><span class="pln">fileStream</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// عندما ينتهي الطلب وتكتَب المعلومات كلها</span><span class="pln">
  fileStream</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'close'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">bytesReceived </span><span class="pun">==</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">'x-file-size'</span><span class="pun">])</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      debug</span><span class="pun">(</span><span class="str">"Upload finished"</span><span class="pun">);</span><span class="pln">
      </span><span class="kwd">delete</span><span class="pln"> uploads</span><span class="pun">[</span><span class="pln">fileId</span><span class="pun">];</span><span class="pln">

      </span><span class="com">// يمكننا فعل شيء آخر بالملف المرفوع</span><span class="pln">

      res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="str">"Success "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> upload</span><span class="pun">.</span><span class="pln">bytesReceived</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="com">// فقد الاتصال، نترك الملف غير المكتمل</span><span class="pln">
      debug</span><span class="pun">(</span><span class="str">"File unfinished, stopped at "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> upload</span><span class="pun">.</span><span class="pln">bytesReceived</span><span class="pun">);</span><span class="pln">
      res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

  </span><span class="com">// I/O أنهِ الطلب في حال خطأ</span><span class="pln">
  fileStream</span><span class="pun">.</span><span class="pln">on</span><span class="pun">(</span><span class="str">'error'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    debug</span><span class="pun">(</span><span class="str">"fileStream error"</span><span class="pun">);</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span class="lit">500</span><span class="pun">,</span><span class="pln"> </span><span class="str">"File error"</span><span class="pun">);</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">();</span><span class="pln">
  </span><span class="pun">});</span><span class="pln">

</span><span class="pun">}</span><span class="pln">

</span><span class="kwd">function</span><span class="pln"> onStatus</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let fileId </span><span class="pun">=</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">headers</span><span class="pun">[</span><span class="str">'x-file-id'</span><span class="pun">];</span><span class="pln">
  let upload </span><span class="pun">=</span><span class="pln"> uploads</span><span class="pun">[</span><span class="pln">fileId</span><span class="pun">];</span><span class="pln">
  debug</span><span class="pun">(</span><span class="str">"onStatus fileId:"</span><span class="pun">,</span><span class="pln"> fileId</span><span class="pun">,</span><span class="pln"> </span><span class="str">" upload:"</span><span class="pun">,</span><span class="pln"> upload</span><span class="pun">);</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">upload</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="str">"0"</span><span class="pun">)</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    res</span><span class="pun">.</span><span class="pln">end</span><span class="pun">(</span><span class="typ">String</span><span class="pun">(</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">bytesReceived</span><span class="pun">));</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln">


</span><span class="kwd">function</span><span class="pln"> accept</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">url </span><span class="pun">==</span><span class="pln"> </span><span class="str">'/status'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    onStatus</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">req</span><span class="pun">.</span><span class="pln">url </span><span class="pun">==</span><span class="pln"> </span><span class="str">'/upload'</span><span class="pln"> </span><span class="pun">&amp;&amp;</span><span class="pln"> req</span><span class="pun">.</span><span class="pln">method </span><span class="pun">==</span><span class="pln"> </span><span class="str">'POST'</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    onUpload</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    fileServer</span><span class="pun">.</span><span class="pln">serve</span><span class="pun">(</span><span class="pln">req</span><span class="pun">,</span><span class="pln"> res</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span><span class="pln">

</span><span class="com">// -----------------------------------</span><span class="pln">

</span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">module</span><span class="pun">.</span><span class="pln">parent</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  http</span><span class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">accept</span><span class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span class="lit">8080</span><span class="pun">);</span><span class="pln">
  console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">'Server listening at port 8080'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  exports</span><span class="pun">.</span><span class="pln">accept </span><span class="pun">=</span><span class="pln"> accept</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	شيفرة العميل "uploader.js":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6915_19" style="">
<span class="kwd">class</span><span class="pln"> </span><span class="typ">Uploader</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

  constructor</span><span class="pun">({</span><span class="pln">file</span><span class="pun">,</span><span class="pln"> onProgress</span><span class="pun">})</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">file </span><span class="pun">=</span><span class="pln"> file</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">onProgress </span><span class="pun">=</span><span class="pln"> onProgress</span><span class="pun">;</span><span class="pln">

    </span><span class="com">// أنشئ معرّف الملف</span><span class="pln">
    </span><span class="com">// يمكننا إضافة معرّف لجلسة المستخدم (عند وجوده ) لجعله فريدًا أكثر</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">fileId </span><span class="pun">=</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">name </span><span class="pun">+</span><span class="pln"> </span><span class="str">'-'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">size </span><span class="pun">+</span><span class="pln"> </span><span class="str">'-'</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> file</span><span class="pun">.</span><span class="pln">lastModified</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  async getUploadedBytes</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    let response </span><span class="pun">=</span><span class="pln"> await fetch</span><span class="pun">(</span><span class="str">'status'</span><span class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      headers</span><span class="pun">:</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        </span><span class="str">'X-File-Id'</span><span class="pun">:</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">fileId
      </span><span class="pun">}</span><span class="pln">
    </span><span class="pun">});</span><span class="pln">

    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">response</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">throw</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">(</span><span class="str">"Can't get uploaded bytes: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> response</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">

    let text </span><span class="pun">=</span><span class="pln"> await response</span><span class="pun">.</span><span class="pln">text</span><span class="pun">();</span><span class="pln">

    </span><span class="kwd">return</span><span class="pln"> </span><span class="pun">+</span><span class="pln">text</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  async upload</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startByte </span><span class="pun">=</span><span class="pln"> await </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">getUploadedBytes</span><span class="pun">();</span><span class="pln">

    let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">
    xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"POST"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"upload"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">);</span><span class="pln">

    </span><span class="com">// send file id, so that the server knows which file to resume</span><span class="pln">
    xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'X-File-Id'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">fileId</span><span class="pun">);</span><span class="pln">
    </span><span class="com">// send the byte we're resuming from, so the server knows we're resuming</span><span class="pln">
    xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'X-Start-Byte'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startByte</span><span class="pun">);</span><span class="pln">

    xhr</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">onprogress </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">onProgress</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startByte </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startByte </span><span class="pun">+</span><span class="pln"> e</span><span class="pun">.</span><span class="pln">total</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">};</span><span class="pln">

    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"send the file, starting from"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startByte</span><span class="pun">);</span><span class="pln">
    xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">file</span><span class="pun">.</span><span class="pln">slice</span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">startByte</span><span class="pun">));</span><span class="pln">

    </span><span class="com">// return</span><span class="pln">
    </span><span class="com">//   true if upload was successful,</span><span class="pln">
    </span><span class="com">//   false if aborted</span><span class="pln">
    </span><span class="com">// throw in case of an error</span><span class="pln">
    </span><span class="kwd">return</span><span class="pln"> await </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Promise</span><span class="pun">((</span><span class="pln">resolve</span><span class="pun">,</span><span class="pln"> reject</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">

      xhr</span><span class="pun">.</span><span class="pln">onload </span><span class="pun">=</span><span class="pln"> xhr</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"upload end status:"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> xhr</span><span class="pun">.</span><span class="pln">status </span><span class="pun">+</span><span class="pln"> </span><span class="str">" text:"</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> xhr</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">);</span><span class="pln">

        </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          resolve</span><span class="pun">(</span><span class="kwd">true</span><span class="pun">);</span><span class="pln">
        </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
          reject</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">(</span><span class="str">"Upload failed: "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> xhr</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">));</span><span class="pln">
        </span><span class="pun">}</span><span class="pln">
      </span><span class="pun">};</span><span class="pln">

      </span><span class="com">//   xhr.abort() فقط عندما يُستدعى onabort يقع  </span><span class="pln">
      xhr</span><span class="pun">.</span><span class="pln">onabort </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> resolve</span><span class="pun">(</span><span class="kwd">false</span><span class="pun">);</span><span class="pln">

    </span><span class="pun">});</span><span class="pln">

  </span><span class="pun">}</span><span class="pln">

  stop</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="kwd">this</span><span class="pun">.</span><span class="pln">xhr</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">abort</span><span class="pun">();</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

</span><span class="pun">}</span></pre>

<p>
	الملف "index.js":
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6915_21" style="">
<span class="pun">&lt;!</span><span class="pln">DOCTYPE HTML</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script src</span><span class="pun">=</span><span class="str">"uploader.js"</span><span class="pun">&gt;&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">form name</span><span class="pun">=</span><span class="str">"upload"</span><span class="pln"> method</span><span class="pun">=</span><span class="str">"POST"</span><span class="pln"> enctype</span><span class="pun">=</span><span class="str">"multipart/form-data"</span><span class="pln"> action</span><span class="pun">=</span><span class="str">"/upload"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"file"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"myfile"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> name</span><span class="pun">=</span><span class="str">"submit"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Upload (Resumes automatically)"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">button onclick</span><span class="pun">=</span><span class="str">"uploader.stop()"</span><span class="pun">&gt;</span><span class="typ">Stop</span><span class="pln"> upload</span><span class="pun">&lt;/</span><span class="pln">button</span><span class="pun">&gt;</span><span class="pln">


</span><span class="pun">&lt;</span><span class="pln">div id</span><span class="pun">=</span><span class="str">"log"</span><span class="pun">&gt;</span><span class="typ">Progress</span><span class="pln"> indication</span><span class="pun">&lt;/</span><span class="pln">div</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="kwd">function</span><span class="pln"> log</span><span class="pun">(</span><span class="pln">html</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    document</span><span class="pun">.</span><span class="pln">getElementById</span><span class="pun">(</span><span class="str">'log'</span><span class="pun">).</span><span class="pln">innerHTML </span><span class="pun">=</span><span class="pln"> html</span><span class="pun">;</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="pln">html</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="kwd">function</span><span class="pln"> onProgress</span><span class="pun">(</span><span class="pln">loaded</span><span class="pun">,</span><span class="pln"> total</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    log</span><span class="pun">(</span><span class="str">"progress "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> loaded </span><span class="pun">+</span><span class="pln"> </span><span class="str">' / '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> total</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  let uploader</span><span class="pun">;</span><span class="pln">

  document</span><span class="pun">.</span><span class="pln">forms</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">onsubmit </span><span class="pun">=</span><span class="pln"> async </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">e</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    e</span><span class="pun">.</span><span class="pln">preventDefault</span><span class="pun">();</span><span class="pln">

    let file </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">elements</span><span class="pun">.</span><span class="pln">myfile</span><span class="pun">.</span><span class="pln">files</span><span class="pun">[</span><span class="lit">0</span><span class="pun">];</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(!</span><span class="pln">file</span><span class="pun">)</span><span class="pln"> </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">

    uploader </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">Uploader</span><span class="pun">({</span><span class="pln">file</span><span class="pun">,</span><span class="pln"> onProgress</span><span class="pun">});</span><span class="pln">

    </span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      let uploaded </span><span class="pun">=</span><span class="pln"> await uploader</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">();</span><span class="pln">

      </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">uploaded</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">(</span><span class="str">'success'</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
        log</span><span class="pun">(</span><span class="str">'stopped'</span><span class="pun">);</span><span class="pln">
      </span><span class="pun">}</span><span class="pln">

    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">error</span><span class="pun">(</span><span class="pln">err</span><span class="pun">);</span><span class="pln">
      log</span><span class="pun">(</span><span class="str">'error'</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">

</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	وستكون النتيجة بالشكل التالي:
</p>

<p>
	<iframe class="code-tabs__result" data-ss1630666641="1" data-ss1634994412="1" src="https://javascript.info/article/resume-upload/upload-resume/" style="display: block; border: 0px; width: 650px; height: 100px;"></iframe>تُعَد الأساليب الجديدة لإرسال الطلبات عبر الشبكة أقرب في إمكانياتها إلى إدارة الملفات كما رأينا، مثل التحكم بالترويسات ومؤشرات تقدم العمليات وإرسال الملفات وغير ذلك، وهذا ما مكّننا من تنفيذ شيفرات لاستئناف رفع الملفات وغيرها الكثير.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1630666641="1" data-ss1634994412="1" href="https://javascript.info/resume-upload" rel="external nofollow">Resumable file upload</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		<a data-ss1630666641="1" data-ss1634994412="1" href="https://academy.hsoub.com/programming/javascript/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1234/" rel="">هياكل البيانات: الكائنات والمصفوفات في جافاسكريبت</a>
	</li>
	<li>
		<a data-ss1630666641="1" data-ss1634994412="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AD%D9%8A%D8%A7%D8%A9-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%A9-%D9%84%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1243/" rel="">الحياة السرية للكائنات في جافاسكريبت</a>
	</li>
	<li>
		<a data-ss1630666641="1" data-ss1634994412="1" href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1283/" rel="">ترميز النصوص والتعامل مع كائنات الملفات في جافاسكريبت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1300</guid><pubDate>Fri, 03 Sep 2021 15:03:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x643;&#x627;&#x626;&#x646; XMLHttpRequest &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86-xmlhttprequest-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1299/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_08/61194ae5911e9_-XMLHttpRequest.png.2a83681ddd0a5fd43c53ad7d9f904ccc.png" /></p>

<p>
	هو كائن مدمَج في المتصفح، يسمح بإرسال طلبات HTTP في جافاسكريبت JavaScript، ويمكنه العمل مع أي نوع من البيانات، وليس فقط بيانات "XML" على الرغم من وجود الكلمة "XML" في تسميته. يساعدنا هذا الكائن في عمليات رفع وتنزيل الملفات وتتبع تقدمها وغير ذلك الكثير، ولا بدّ من الإشارة إلى وجود أسلوب أكثر حداثةً منه وهو استخدام <code>fetch</code> التي ألغت استخدامه بشكل أو بآخر.
</p>

<p>
	يُستخدم <code>XMLHttpRequest</code> في تقنيات تطوير الويب العصرية لثلاثة أسباب، هي الآتية:
</p>

<ol>
<li>
		أسباب تاريخية: عند الحاجة إلى دعم سكربت موجود يستخدم <code>XMLHttpRequest</code>.
	</li>
	<li>
		الحاجة إلى دعم المتصفحات القديمة دون استخدام شيفرة "polyfill".
	</li>
	<li>
		إنجاز شيء لا يمكن تنفيذه باستخدام <code>fetch</code> مثل تتبع تقدم رفع ملف.
	</li>
</ol>
<p>
	إن كنت تحتاج ما ذكرناه تابع قراءة المقال وإلا فتوجه إلى فصل استخدام Fetch.
</p>

<h2>
	الأساسيات
</h2>

<p>
	يعمل الكائن <code>XMLHttpRequest</code> وفق النمطين المتزامن وغير المتزامن، سنتحدث بدايةً عن الوضع غير المتزامن، لأنه يستخدم في أغلب الحالات.
</p>

<p>
	يُنجز طلب HTTP وفق 4 خطوات:
</p>

<p>
	<strong>الخطوة الأولى</strong>، إنشاء الكائن <code>XMLHttpRequest</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_12" style="">
<span class="pln">let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span></pre>

<p>
	ليس للدالة البانية أي وسطاء.
</p>

<p>
	<strong>الخطوة الثانية</strong>، تهيئة الكائن، وتكون بعد إنشائه عادةً:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_14" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="pln">method</span><span class="pun">,</span><span class="pln"> URL</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">async</span><span class="pun">,</span><span class="pln"> user</span><span class="pun">,</span><span class="pln"> password</span><span class="pun">])</span></pre>

<p>
	يؤمن التابع <code>open</code> المعاملات الرئيسية للطلب، وهي:
</p>

<ul>
<li>
		<code>method</code>: تحدد نوع طلب HTTP، عادةً يكون <code>GET</code> أو <code>POST</code>.
	</li>
	<li>
		<code>URL</code>: عنوان الموقع الذي نرسل الطلب إليه، ويمكن استخدام الكائن <a data-ss1634994018="1" data-ss1634994331="1" href="https://javascript.info/url" rel="external nofollow">URL</a>.
	</li>
	<li>
		<code>async</code>: تكون العملية متزامنةً إذا أسندت لها القيمة <code>false</code> صراحةً، وسنغطي ذلك لاحقًا.
	</li>
	<li>
		<code>user</code> و<code>password</code>: اسم المستخدم وكلمة المرور للاستيثاق، عند الحاجة.
	</li>
</ul>
<p>
	لا يفتح التابع <code>open</code> الاتصال على الرغم من اسمه، بل يهيئ الطلب فقط، ولا يبدأ الاتصال عبر الشبكة إلا باستدعاء <code>send</code>.
</p>

<p>
	<strong>الخطوة الثالثة</strong>، إرسال الطلب:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_16" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">([</span><span class="pln">body</span><span class="pun">])</span></pre>

<p>
	يفتح هذا التابع الاتصال ويرسل الطلب إلى الخادم، حيث يحتوي المعامل <code>body</code> على جسم الطلب.ولا تمتلك الطلبات مثل <code>GET</code> جسماً، وتستخدم طلبات أخرى مثل <code>POST</code> الجسم لإرسال البيانات إلى الخادم، وسنرى أمثلةً عن ذلك لاحقًا.
</p>

<p>
	<strong>الخطوة الرابعة</strong>، الاستماع إلى أحداث الكائن <code>xhr</code> لتلقي الاستجابة، وإليك أكثر الأحداث استخدامًا:
</p>

<ul>
<li>
		<code>load</code>: عندما يكتمل الطلب ونحصل على الاستجابة كاملةً، حتى لو أعاد الخادم رمز الحالة 500 أو 400 مثلًا.
	</li>
	<li>
		<code>error</code>: عندما يتعذر تنفيذ الطلب، كأن تكون الشبكة معطلةً أو العنوان خاطئًا.
	</li>
	<li>
		<code>progress</code>: ويقع دوريًا أثناء تنزيل الاستجابة، ويوضح الكمية التي نُزِّلت.
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_18" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">onload </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Loaded</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status</span><span class="pun">}</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">response</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// عندما لا تحدث الاستجابة إطلاقًا</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Network</span><span class="pln"> </span><span class="typ">Error</span><span class="pun">`);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">onprogress </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// يقع دوريًا</span><span class="pln">
  </span><span class="com">// event.loaded - الحجم الذي نزل بالبايت</span><span class="pln">
  </span><span class="com">// event.lengthComputable = true إن أرسل الخادم ترويسة طول المحتوى</span><span class="pln">
  </span><span class="com">// event.total - العدد الكلي للبايتات</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Received</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">}</span><span class="pln"> of $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">total</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	إليك مثالًا كاملًا، حيث تحمّل الشيفرة الموالية العنوان الآتي
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_22" style="">
<span class="pln"> article</span><span class="pun">/</span><span class="pln">xmlhttprequest</span><span class="pun">/</span><span class="pln">example</span><span class="pun">/</span><span class="pln">load</span><span class="pun">/</span><span class="pln">
</span></pre>

<p>
	من الخادم، وتطبع تقدم العملية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_20" style="">
<span class="com">// 1. إنشاء الكائن</span><span class="pln">
let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">

</span><span class="com">// 2. URL /article/.../load  تهيئته مع العنوان</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/article/xmlhttprequest/example/load'</span><span class="pun">);</span><span class="pln">

</span><span class="com">// 3. إرسال الطلب عبر الشبكة </span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">();</span><span class="pln">

</span><span class="com">// 4. سيستدعى هذا الجزء عند تلقي الاستجابة</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">onload </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// analyze HTTP status of the response</span><span class="pln">
    alert</span><span class="pun">(`</span><span class="typ">Error</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status</span><span class="pun">}:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">}`);</span><span class="pln"> </span><span class="com">// e.g. 404: Not Found</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// show the result</span><span class="pln">
    alert</span><span class="pun">(`</span><span class="typ">Done</span><span class="pun">,</span><span class="pln"> got $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">response</span><span class="pun">.</span><span class="pln">length</span><span class="pun">}</span><span class="pln"> bytes</span><span class="pun">`);</span><span class="pln"> </span><span class="com">// response is the server response</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">onprogress </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">event</span><span class="pun">.</span><span class="pln">lengthComputable</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    alert</span><span class="pun">(`</span><span class="typ">Received</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">}</span><span class="pln"> of $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">total</span><span class="pun">}</span><span class="pln"> bytes</span><span class="pun">`);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    alert</span><span class="pun">(`</span><span class="typ">Received</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">}</span><span class="pln"> bytes</span><span class="pun">`);</span><span class="pln"> </span><span class="com">// no Content-Length</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">"Request failed"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	نستقبل نتيجة الطلب باستخدام خصائص الكائن <code>xhr</code> التالية حالما يستجيب الخادم:
</p>

<ul>
<li>
		<code>status</code>: رمز حالة HTTP، عدد، مثل <code>200</code> و<code>404</code> و<code>403</code> وهكذا، كما يمكن أن يكون <code>0</code> عند حدوث خطأ لا علاقة له بالنقل وفق بروتوكول HTTP.
	</li>
	<li>
		<code>statusText</code>: رسالة حالة HTTP، نص، مثل <code>OK</code> لرمز الحالة <code>200</code> و<code>Not Found</code> لرمز الحالة <code>404</code>، و<code>Forbidden</code> لرمز الحالة <code>403</code>.
	</li>
	<li>
		<code>response</code>: قد تستخدم السكربتات القديمة <code>responseText</code>، وتمثل جسم الاستجابة التي يرسلها الخادم.
	</li>
</ul>
<p>
	يمكن أن نحدد أيضًا زمن انتهاء timeout مستخدمين الخاصية الموافقة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_25" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">timeout </span><span class="pun">=</span><span class="pln"> </span><span class="lit">10000</span><span class="pun">;</span><span class="pln"> </span><span class="com">// زمن الانتهاء بالميلي ثانية</span></pre>

<p>
	إن لم ينجح الطلب خلال الفترة الزمنية المحددة فسيُلغى وسيقع الحدث <code>timeout</code>.
</p>

<h3>
	إضافة معاملات بحث إلى العنوان URL
</h3>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		يمكننا استخدام الكائن <a data-ss1634994018="1" data-ss1634994331="1" href="https://javascript.info/url" rel="external nofollow">URL</a>، لإضافة معاملات بحث مثل: <code>name=value?</code>، والتأكد من ترميزها بطريقة صحيحة :
	</p>
</blockquote>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_27" style="">
<span class="pln">let url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://google.com/search'</span><span class="pun">);</span><span class="pln">
url</span><span class="pun">.</span><span class="pln">searchParams</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'q'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'test me!'</span><span class="pun">);</span><span class="pln">

</span><span class="com">// 'q' ترميز المعامل </span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://google.com/search?q=test+me%21</span></pre>

<h2>
	نوع الاستجابة
</h2>

<p>
	من الممكن استخدام الخاصية <code>ResponseType</code> لضبط تنسيق الاستجابة:
</p>

<ul>
<li>
		<code>""</code>: القيمة الافتراضية، الحصول على الاستجابة كنص.
	</li>
	<li>
		<code>"text"</code>: الحصول على الاستجابة كنص.
	</li>
	<li>
		<code>"arraybuffer"</code>: الحصول على الاستجابة على شكل كائن <code>ArrayBuffer</code>، للحصول على بيانات ثنائية.
	</li>
	<li>
		<code>"blob"</code>: الحصول على الاستجابة على شكل كائن <code>Blob</code>، للحصول على بيانات ثنائية.
	</li>
	<li>
		<code>"document"</code>: الحصول على مستند XML، باستخدام اللغة XPath أو غيرها.
	</li>
	<li>
		<code>"json"</code>: الحصول على الاستجابة بصيغة JSON، تفسر الاستجابة تلقائيًا.
	</li>
</ul>
<p>
	لنستقبل الاستجابة بصيغة JSON مثلًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_30" style="">
<span class="pln">let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/article/xmlhttprequest/example/json'</span><span class="pun">);</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">responseType </span><span class="pun">=</span><span class="pln"> </span><span class="str">'json'</span><span class="pun">;</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">();</span><span class="pln">

</span><span class="com">//  {"message": "Hello, world!"} الاستجابة هي </span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">onload </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let responseObj </span><span class="pun">=</span><span class="pln"> xhr</span><span class="pun">.</span><span class="pln">response</span><span class="pun">;</span><span class="pln">
  alert</span><span class="pun">(</span><span class="pln">responseObj</span><span class="pun">.</span><span class="pln">message</span><span class="pun">);</span><span class="pln"> </span><span class="com">// Hello, world!</span><span class="pln">
</span><span class="pun">};</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		قد ترى في السكربتات القديمة الخاصية <code>xhr.responseXML</code> وكذلك <code>xhr.responseText</code>، وهي طرق موجودة قديمًا للحصول على نص أو مستند XML، ينبغي حاليًا تحديد تنسيق الاستجابة باستخدام <code>xhr.responseType</code>، واستقبالها باستخدام <code>xhr.response</code> كما أشرنا سابقًا.
	</p>
</blockquote>

<h2>
	حالات الجاهزية Ready States
</h2>

<p>
	يمر الكائن بعدة حالات عند تقدم تنفيذ الطلب، ويمكن الوصول إلى الحالة من خلال <code>xhr.readyState</code>، وإليك الحالات جميعها كما وردت في <a data-ss1634994018="1" data-ss1634994331="1" href="https://xhr.spec.whatwg.org/#states" rel="external nofollow">التوصيفات</a>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_32" style="">
<span class="pln">UNSENT </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln"> </span><span class="com">// الحالة الأساسية</span><span class="pln">
OPENED </span><span class="pun">=</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln"> </span><span class="com">//open استدعاء التابع </span><span class="pln">
HEADERS_RECEIVED </span><span class="pun">=</span><span class="pln"> </span><span class="lit">2</span><span class="pun">;</span><span class="pln"> </span><span class="com">// استقبال ترويسة الاستجابة</span><span class="pln">
LOADING </span><span class="pun">=</span><span class="pln"> </span><span class="lit">3</span><span class="pun">;</span><span class="pln"> </span><span class="com">// تلقي جسم الاستجابة</span><span class="pln">
DONE </span><span class="pun">=</span><span class="pln"> </span><span class="lit">4</span><span class="pun">;</span><span class="pln"> </span><span class="com">// اكتمال الطلب</span></pre>

<p>
	ينتقل الكائن <code>XMLHttpRequest</code> بين الحالات السابقة بالترتيب <code>0</code> ثم <code>1</code> ثم <code>2</code> ثم <code>3</code> ثم <code>4</code>، وتتكرر الحالة <code>3</code> في كل مرة تُستقبل فيها حزمة بيانات عبر الشبكة، ويمكننا تتبع هذه الحالات باستخدام الحدث <code>readystatechange</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_34" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">onreadystatechange </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">readyState </span><span class="pun">==</span><span class="pln"> </span><span class="lit">3</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// تحميل</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">readyState </span><span class="pun">==</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="com">// انتهاء الطلب</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	قد تجد مستمعات للأحداث <code>readystatechange</code> حتى في الشيفرات القديمة، وذلك لأنّ أحداثًا مثل <code>load</code> وغيرها، لم تكن موجودةً في فترة ما، لكن معالجات الأحداث <code>load/error/progress</code> قد ألغت استخدامها.
</p>

<h2>
	طلب الإلغاء Aborting request
</h2>

<p>
	يمكن إلغاء الطلب في أي لحظة، وذلك باستدعاء التابع <code>()xhr.abort</code> :
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_36" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">abort</span><span class="pun">();</span><span class="pln"> </span><span class="com">// إلغاء الطلب</span></pre>

<p>
	يحرّض استدعاء التابع الحدث <code>abort</code> وتأخذ الخاصية <code>xhr.status</code> القيمة <code>0</code>.
</p>

<h2>
	الطلبات المتزامنة
</h2>

<p>
	عند إسناد القيمة <code>false</code> إلى المعامل الثالث <code>async</code> للتابع <code>open</code>، فسيُنفَّذ الطلب بتزامن Synchronously. وبعبارة أخرى، ستتوقف JavaScript مؤقتًا عن التنفيذ عند إرسال الطلب <code>()send</code> ثم تتابع عند تلقي الاستجابة، وهذا يشابه تعليمتي <code>alert</code> و<code>prompt</code>.
</p>

<p>
	إليك المثال السابق وقد أعيدت كتابته مع وجود المعامل الثالث للتابع <code>open</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_38" style="">
<span class="pln">let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/article/xmlhttprequest/hello.txt'</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">false</span><span class="pun">);</span><span class="pln">

</span><span class="kwd">try</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">();</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    alert</span><span class="pun">(`</span><span class="typ">Error</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status</span><span class="pun">}:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">statusText</span><span class="pun">}`);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    alert</span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">response</span><span class="pun">);</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> </span><span class="kwd">catch</span><span class="pun">(</span><span class="pln">err</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// onerror بدلًا من</span><span class="pln">
  alert</span><span class="pun">(</span><span class="str">"Request failed"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	قد يبدو الأمر جيدًا لكنها طريقة يندر استخدامها، لأنها تعيق تنفيذ شيفرة JavaScript حتى يكتمل التحميل، وقد يصبح من المستحيل تمرير الصفحة لعرض بقية المحتويات أثناء تنفيذها في بعض المتصفحات، وقد يقترح المتصفح أيضًا إغلاق الصفحة العالقة إذا تأخر تنفيذ الطلب المتزامن كثيرًا.
</p>

<p>
	ولا تتاح العديد من الإمكانيات المتقدمة للكائن <code>XMLHttpRequest</code>، مثل الطلب من نطاق آخر أو تحديد زمن الانتهاء من الطلب، عند إرسال الطلبات بتزامن، ولا مؤشرات أيضًا على تقدم العملية، لذلك لا تُنفّذ الطلبات المتزامنة إلا نادرًا.
</p>

<h2>
	ترويسات HTTP
</h2>

<p>
	يتيح الكائن <code>XMLHttpRequest</code> إرسال ترويسات مخصصة وقراءة ترويسات الاستجابة، وهنالك ثلاثة توابع للتعامل مع الترويسات:
</p>

<ul>
<li>
		<code>(setRequestHeader(name, value</code>: يعطي اسمًا <code>name</code> وقيمةً <code>value</code> لترويسة الطلب، وإليك مثالًا عن ذلك:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_41" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'Content-Type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'application/json'</span><span class="pun">)</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		<strong>محدودية الترويسات</strong>: تُدار بعض الترويسات حصريًا من قبل المتصفح مثل <code>Referer</code> و<code>Host</code> (ستجد القائمة الكاملة في <a data-ss1634994018="1" data-ss1634994331="1" href="https://xhr.spec.whatwg.org/#the-setrequestheader()-method" rel="external nofollow">التوصيفات</a>)، فلا يسمح للكائن <code>XMLHttpRequest</code> بتغييرها، وذلك لضمان أمن المستخدم وصحة الطلب المُرسَل.
	</p>

	<p>
		<strong>لا يمكن إزالة الترويسة</strong>: من ميزات الكائن <code>XMLHttpRequest</code> أيضًا عدم القدرة على إزالة الترويسة <code>setRequestHeader</code>، فحالما تضبط قيم الترويسة سينتهي الأمر، وستضيف بقية الاستدعاءات معلومات إلى الترويسة لكنها لن تلغي ما هو موجود.
	</p>
</blockquote>

<p>
	فيما يلي مثال لتوضيح ذلك:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_43" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'X-Auth'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'123'</span><span class="pun">);</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'X-Auth'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'456'</span><span class="pun">);</span><span class="pln">
</span><span class="com">// ستكون الترويسة:</span><span class="pln">
</span><span class="com">// X-Auth: 123, 456</span></pre>

<ul>
<li>
		<code>(getResponseHeader(name</code>: تعيد ترويسة الاستجابة ذات الاسم المحدد <code>name</code>، عدا <code>Set-Cookie</code> و<code>Set-Cookie2</code>، إليك مثالًا:
	</li>
</ul>
<pre class="ipsCode">
xhr.getResponseHeader('Content-Type')
</pre>

<ul>
<li>
		<code>()getAllResponseHeaders</code>: تعيد كل ترويسات الاستجابة، عدا <code>Set-Cookie</code> و<code>Set-Cookie2</code>، وتُعاد القيم ضمن سطر مفرد، حيث سيكون الفاصل هو <code>"\r\n"</code> (لا يتعلق بنظام تشغيل محدد) بين كل ترويستين، وبالتالي يمكن فصلها إلى ترويسات مفردة، وإليك مثالًا:
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_45" style="">
<span class="typ">Cache</span><span class="pun">-</span><span class="typ">Control</span><span class="pun">:</span><span class="pln"> max</span><span class="pun">-</span><span class="pln">age</span><span class="pun">=</span><span class="lit">31536000</span><span class="pln">
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Length</span><span class="pun">:</span><span class="pln"> </span><span class="lit">4260</span><span class="pln"> 
</span><span class="typ">Content</span><span class="pun">-</span><span class="typ">Type</span><span class="pun">:</span><span class="pln"> image</span><span class="pun">/</span><span class="pln">png 
</span><span class="typ">Date</span><span class="pun">:</span><span class="pln"> </span><span class="typ">Sat</span><span class="pun">,</span><span class="pln"> </span><span class="lit">08</span><span class="pln"> </span><span class="typ">Sep</span><span class="pln"> </span><span class="lit">2012</span><span class="pln"> </span><span class="lit">16</span><span class="pun">:</span><span class="lit">53</span><span class="pun">:</span><span class="lit">16</span><span class="pln"> GMT</span></pre>

<p>
	تفصل بين اسم الترويسة وقيمتها النقطتان المتعامدتان، يليها فراغ <code>" :"</code>، وهذا أمر ثابت في التوصيفات، فلو أردنا مثلًا الحصول على كائن له اسم وقيمةP فلا بد من تنفيذ الأمر بالشكل التالي، مفترضين أنه عند وجود ترويستين بنفس الاسم، وعندها ستحل الأخيرة منهما مكان الأولى:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_48" style="">
<span class="pln">let headers </span><span class="pun">=</span><span class="pln"> xhr  
   </span><span class="pun">.</span><span class="pln">getAllResponseHeaders</span><span class="pun">()</span><span class="pln">  
   </span><span class="pun">.</span><span class="pln">split</span><span class="pun">(</span><span class="str">'\r\n'</span><span class="pun">)</span><span class="pln">  
   </span><span class="pun">.</span><span class="pln">reduce</span><span class="pun">((</span><span class="pln">result</span><span class="pun">,</span><span class="pln"> current</span><span class="pun">)</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> </span><span class="pun">{</span><span class="pln">    
     let </span><span class="pun">[</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> current</span><span class="pun">.</span><span class="pln">split</span><span class="pun">(</span><span class="str">': '</span><span class="pun">);</span><span class="pln">
     result</span><span class="pun">[</span><span class="pln">name</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> value</span><span class="pun">;</span><span class="pln">    
     </span><span class="kwd">return</span><span class="pln"> result</span><span class="pun">;</span><span class="pln">  
    </span><span class="pun">},</span><span class="pln"> </span><span class="pun">{});</span><span class="pln">

 </span><span class="com">// headers['Content-Type'] = 'image/png'</span></pre>

<h2>
	الطلب POST والكائن FormData
</h2>

<p>
	يمكن استخدام الكائن <a data-ss1634994018="1" data-ss1634994331="1" href="https://developer.mozilla.org/en-US/docs/Web/API/FormData" rel="external nofollow">FormData</a> لإرسال طلب HTTP-POST:
</p>

<p>
	إليك الشيفرة اللازمة:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_50" style="">
<span class="pln">let formData </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FormData</span><span class="pun">([</span><span class="pln">form</span><span class="pun">]);</span><span class="pln"> </span><span class="com">//&lt;form&gt; إنشاء كائن يُملأ اختيارًا من  </span><span class="pln">
formData</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">);</span><span class="pln"> </span><span class="com">// ربط الحقل</span></pre>

<p>
	تنشئ الشيفرة السابقة الكائن <code>FormData</code> وتملؤه بقيم من نموذج form اختياريًا، وتضيف <code>append</code> حقولًا أخرى إن اقتضى الأمر، ومن ثم:
</p>

<ol>
<li>
		نستخدم الأمر <code>(...,'xhr.open('POST</code>: لنؤسس الطلب <code>POST</code>.
	</li>
	<li>
		نستخدم الأمر <code>(xhr.send(formData</code>: لإرسال النموذج إلى الخادم.
	</li>
</ol>
<p>
	إليك المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_52" style="">
<span class="pun">&lt;</span><span class="pln">form name</span><span class="pun">=</span><span class="str">"person"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input name</span><span class="pun">=</span><span class="str">"name"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"John"</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="pun">&lt;</span><span class="pln">input name</span><span class="pun">=</span><span class="str">"surname"</span><span class="pln"> value</span><span class="pun">=</span><span class="str">"Smith"</span><span class="pun">&gt;</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">form</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
  </span><span class="com">// form ملؤها من </span><span class="pln">
  let formData </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">FormData</span><span class="pun">(</span><span class="pln">document</span><span class="pun">.</span><span class="pln">forms</span><span class="pun">.</span><span class="pln">person</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// إضافة حقل جديد</span><span class="pln">
  formData</span><span class="pun">.</span><span class="pln">append</span><span class="pun">(</span><span class="str">"middle"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"Lee"</span><span class="pun">);</span><span class="pln">

  </span><span class="com">// إرساله</span><span class="pln">
  let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">
  xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"POST"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"/article/xmlhttprequest/post/user"</span><span class="pun">);</span><span class="pln">
  xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">formData</span><span class="pun">);</span><span class="pln">

  xhr</span><span class="pun">.</span><span class="pln">onload </span><span class="pun">=</span><span class="pln"> </span><span class="pun">()</span><span class="pln"> </span><span class="pun">=&gt;</span><span class="pln"> alert</span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">response</span><span class="pun">);</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<p>
	يُرسَل النموذج بترميز <code>multipart/form-data</code>، فإذا أردنا استخدام JSON فلا بدّ من تنفيذ الأمر <code>JSON.stringify</code> ثم إرساله في هيئة نص، إلى جانب ضبط الترويسة <code>Content-Type</code> على القيمة <code>application/json</code>، وتفكِّك العديد من إطارات العمل مع الخادم محتوى JSON تلقائيًا بهذه الطريقة.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_54" style="">
<span class="pln">let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">

let json </span><span class="pun">=</span><span class="pln"> JSON</span><span class="pun">.</span><span class="pln">stringify</span><span class="pun">({</span><span class="pln">
  name</span><span class="pun">:</span><span class="pln"> </span><span class="str">"John"</span><span class="pun">,</span><span class="pln">
  surname</span><span class="pun">:</span><span class="pln"> </span><span class="str">"Smith"</span><span class="pln">
</span><span class="pun">});</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"POST"</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/submit'</span><span class="pun">)</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">setRequestHeader</span><span class="pun">(</span><span class="str">'Content-type'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'application/json; charset=utf-8'</span><span class="pun">);</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">json</span><span class="pun">);</span></pre>

<p>
	ويمكن للتابع أن يُرسل أي جسم للطلب بما في ذلك كائنات <code>Blob</code> و<code>BufferSource</code>.
</p>

<h2>
	تقدم عمليات رفع البيانات
</h2>

<p>
	يقع الحدث <code>progress</code> في مرحلة التنزيل فقط، فلو نشرنا شيئًا ما باستخدام الطلب <code>POST</code>، فسيرفع الكائن <code>XMLHttpRequest</code> البيانات -جسم الطلب- أولًا ومن ثم ينزل الاستجابة.
</p>

<p>
	وسيكون تتبع تقدم عملية رفع البيانات خاصةً إن كانت ضخمة؛ أمرًا هامًا، لكن لن نستفيد من الحدث <code>xhr.onprogress</code> في حالتنا، يوجد كائن آخر لا يمتلك توابعًا، وهو مخصص حصرًا لتتبع أحداث رفع البيانات وهو <code>xhr.upload</code>، الذي يولِّد أحداثًا كما يفعل<code>xhr</code>، لكنها تقع فقط عند رفع البيانات:
</p>

<ul>
<li>
		<code>loadstart</code>: يقع عندما يبدأ رفع البيانات.
	</li>
	<li>
		<code>progress</code>: يقع دوريًا مع تقدم الرفع.
	</li>
	<li>
		<code>abort</code>: يقع عند إلغاء الرفع.
	</li>
	<li>
		<code>error</code>: يقع عند وقوع خطأ لا يتعلق بالبروتوكول HTTP.
	</li>
	<li>
		<code>load</code>: يقع عند نجاح عملية الرفع.
	</li>
	<li>
		<code>timeout</code>: يقع عند انتهاء الوقت المخصص لرفع البيانات، إذا ضُبطت الخاصية <code>timeout</code>.
	</li>
	<li>
		<code>loadend</code>: يقع عند انتهاء الرفع بنجاح أو بإخفاق.
	</li>
</ul>
<p>
	إليك أمثلةً عن معالجات هذه الأحداث:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_56" style="">
<span class="pln">xhr</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">onprogress </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Uploaded</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">}</span><span class="pln"> of $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">total</span><span class="pun">}</span><span class="pln"> bytes</span><span class="pun">`);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">onload </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Upload</span><span class="pln"> finished successfully</span><span class="pun">.`);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Error</span><span class="pln"> during the upload</span><span class="pun">:</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	إليك أيضًا مثالًا واقعيًا عن رفع ملف مع مؤشرات على تقدم العملية:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_58" style="">
<span class="pun">&lt;</span><span class="pln">input type</span><span class="pun">=</span><span class="str">"file"</span><span class="pln"> onchange</span><span class="pun">=</span><span class="str">"upload(this.files[0])"</span><span class="pun">&gt;</span><span class="pln">

</span><span class="pun">&lt;</span><span class="pln">script</span><span class="pun">&gt;</span><span class="pln">
</span><span class="kwd">function</span><span class="pln"> upload</span><span class="pun">(</span><span class="pln">file</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">

  </span><span class="com">//  تعقب تقدم عملية الرفع </span><span class="pln">
  xhr</span><span class="pun">.</span><span class="pln">upload</span><span class="pun">.</span><span class="pln">onprogress </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(`</span><span class="typ">Uploaded</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">}</span><span class="pln"> of $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">total</span><span class="pun">}`);</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">

  </span><span class="com">// تعقب الانتهاء، بنجاح أو إخفاق </span><span class="pln">
  xhr</span><span class="pun">.</span><span class="pln">onloadend </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
    </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status </span><span class="pun">==</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"success"</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
      console</span><span class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"error "</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="kwd">this</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">
    </span><span class="pun">}</span><span class="pln">
  </span><span class="pun">};</span><span class="pln">

  xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">"POST"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"/article/xmlhttprequest/post/upload"</span><span class="pun">);</span><span class="pln">
  xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">(</span><span class="pln">file</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">&lt;/</span><span class="pln">script</span><span class="pun">&gt;</span></pre>

<h2>
	الطلبات ذات الأصول المختلطة
</h2>

<p>
	يمكن للكائن <code>XMLHttpRequest</code> تنفيذ طلبات الأصل المختلط مستخدمًا سياسة CORS، تمامًا كما يفعل <a data-ss1634994018="1" data-ss1634994331="1" href="https://javascript.info/fetch-crossorigin" rel="external nofollow">fetch</a>، ولن يُرسل ملفات تعريف الارتباط cookies أو معلومات استيثاق إلى مواقع ذات أصل مختلف افتراضيًا. ولتمكين ذلك لا بدّ من ضبط الخاصية <code>xhr.withCredentials</code> على القيمة <code>true</code>.
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_60" style="">
<span class="pln">let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">
xhr</span><span class="pun">.</span><span class="pln">withCredentials </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">true</span><span class="pun">;</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'POST'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'http://anywhere.com/request'</span><span class="pun">);</span><span class="pln">
</span><span class="pun">...</span></pre>

<p>
	اطلع على فصل "<a data-ss1634994331="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-fetch-%D9%85%D8%B9-%D8%A7%D9%84%D8%B7%D9%84%D8%A8%D8%A7%D8%AA-%D8%B0%D8%A7%D8%AA-%D8%A7%D9%84%D8%A3%D8%B5%D9%84-%D8%A7%D9%84%D9%85%D8%AE%D8%AA%D9%84%D8%B7-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1296/" rel="">استخدام Fetch مع الطلبات ذات الأصل المختلط</a>" لمعلومات أكثر.
</p>

<h2>
	خلاصة
</h2>

<p>
	تمثل الشيفرة التالية، الشيفرة النموذجية لطلب GET باستخدام <code>XMLHttpRequest</code>
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_6157_63" style="">
<span class="pln">let xhr </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span class="typ">XMLHttpRequest</span><span class="pun">();</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">open</span><span class="pun">(</span><span class="str">'GET'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'/my/url'</span><span class="pun">);</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">send</span><span class="pun">();</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">onload </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="kwd">if</span><span class="pln"> </span><span class="pun">(</span><span class="pln">xhr</span><span class="pun">.</span><span class="pln">status </span><span class="pun">!=</span><span class="pln"> </span><span class="lit">200</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"> </span><span class="com">// HTTP error?</span><span class="pln">
    </span><span class="com">// معالجة الخطأ</span><span class="pln">
    alert</span><span class="pun">(</span><span class="pln"> </span><span class="str">'Error: '</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> xhr</span><span class="pun">.</span><span class="pln">status</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">return</span><span class="pun">;</span><span class="pln">
  </span><span class="pun">}</span><span class="pln">

  </span><span class="com">//  xhr.response الحصول على الاستجابة من  </span><span class="pln">
</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">onprogress </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">(</span><span class="pln">event</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// إعطاء تقرير عن التقدم </span><span class="pln">
  alert</span><span class="pun">(`</span><span class="typ">Loaded</span><span class="pln"> $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">loaded</span><span class="pun">}</span><span class="pln"> of $</span><span class="pun">{</span><span class="pln">event</span><span class="pun">.</span><span class="pln">total</span><span class="pun">}`);</span><span class="pln">
</span><span class="pun">};</span><span class="pln">

xhr</span><span class="pun">.</span><span class="pln">onerror </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">function</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  </span><span class="com">// handle non-HTTP error (e.g. network down)</span><span class="pln">
</span><span class="pun">};</span></pre>

<p>
	هنالك أحداث أكثر في <a data-ss1634994018="1" data-ss1634994331="1" href="https://xhr.spec.whatwg.org/#events" rel="external nofollow">التوصيفات الحديثة</a>، والتي وُضِعت في قائمة مرتبة وفق دورة حياة كل حدث:
</p>

<ul>
<li>
		<code>loadstart</code>: عندما يبدأ الطلب.
	</li>
	<li>
		<code>progress</code>: عند وصول حزمة بيانات، وتكون الاستجابة الكاملة في هذه اللحظة ضمن الاستجابة <code>response</code>.
	</li>
	<li>
		<code>()abort</code>: عند إلغاء الطلب باستدعاء التابع.
	</li>
	<li>
		<code>error</code>: عند وقوع خطأ في الاتصال، مثل اسم نطاق خاطئ ولم يحدث نتيجة خطأ HTTP مثل 404.
	</li>
	<li>
		<code>load</code>: عند انتهاء الطلب بنجاح.
	</li>
	<li>
		<code>timeout</code>: عند إلغاء الطلب نتيجة تجاوز الوقت المخصص، ويحدث عندما تُضبَط هذه الخاصية.
	</li>
	<li>
		<code>loadend</code>: ويقع بعد الأحداث <code>load</code> أو <code>error</code> أو <code>timeout</code> أو <code>abort</code>، وهذه الأحداث متنافية فيما بينها، أي لا يمكن وقوع سوى حدث واحد منها.
	</li>
</ul>
<p>
	إن أكثر الأحداث استخدامًا هما حدث إكمال التحميل <code>load</code> وحدث إخفاق التحميل <code>error</code>، كما يمكن استعمال معالج الحدث <code>loadend</code> والتحقق من خصائص الكائن <code>xhr</code> للتأكد من طبيعة الحدث الذي وقع.
</p>

<p>
	لقد تعرفنا في هذا المقال أيضًا على الحدث <code>readystatechange</code>، والذي ظهر منذ زمن بعيد قبل استقرار التوصيفات، ولا حاجة حاليًا لاستخدامه، حيث يُستبدل بالأحداث الأكثر عصريةً. لكن مع ذلك قد نصادفه في بعض السكربتات القديمة.
</p>

<p>
	إذا أردنا تعقب تقدم رفع البيانات، فلا بدّ من الاستماع إلى نفس أحداث التنزيل لكن باستخدام الكائن <code>xhr.upload</code>.
</p>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1634994018="1" data-ss1634994331="1" href="https://javascript.info/xmlhttprequest" rel="external nofollow">XMLHttpRequest</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a data-ss1634994018="1" data-ss1634994331="1" href="https://academy.hsoub.com/programming/javascript/%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-url-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1298/" rel="">كائنات URL في جافاسكريبت</a>
	</li>
	<li>
		<a data-ss1634994018="1" data-ss1634994331="1" href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1283/" rel="">ترميز النصوص والتعامل مع كائنات الملفات في جافاسكريبت</a>
	</li>
	<li>
		<a data-ss1634994018="1" data-ss1634994331="1" href="https://academy.hsoub.com/programming/javascript/%D9%81%D8%AD%D8%B5-%D8%A7%D9%84%D8%A3%D8%B5%D9%86%D8%A7%D9%81-%D8%B9%D8%A8%D8%B1-instanceof-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r906/" rel="">فحص الأصناف عبر instanceof في جافاسكربت</a>
	</li>
	<li>
		<a data-ss1634994018="1" data-ss1634994331="1" href="https://academy.hsoub.com/programming/javascript/%D9%87%D9%8A%D8%A7%D9%83%D9%84-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1234/" rel="">هياكل البيانات: الكائنات والمصفوفات في جافاسكريبت</a>
	</li>
	<li>
		<a data-ss1634994018="1" data-ss1634994331="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D8%AD%D9%8A%D8%A7%D8%A9-%D8%A7%D9%84%D8%B3%D8%B1%D9%8A%D8%A9-%D9%84%D9%84%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1243/" rel="">الحياة السرية للكائنات في جافاسكريبت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1299</guid><pubDate>Tue, 31 Aug 2021 15:09:00 +0000</pubDate></item><item><title>&#x643;&#x627;&#x626;&#x646;&#x627;&#x62A; URL &#x641;&#x64A; &#x62C;&#x627;&#x641;&#x627;&#x633;&#x643;&#x631;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/javascript/%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-url-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D8%A8%D8%AA-r1298/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2021_08/611946667f986_URL--.png.548ad003996f5e0b22897b8aef7b12cc.png" /></p>

<p>
	يقدم الصنف <a data-ss1634992550="1" data-ss1634992670="1" href="https://url.spec.whatwg.org/#api" rel="external nofollow">URL</a> المدمج واجهةً ملائمةً لإنشاء عناوين الموارد وروابط URL وتفسيرها، لا تحتاج الطلبات عبر الشبكة إلى هذا الكائن بالتحديد، فالقيم النصية التي يمكن أن تعبّر عن العناوين كافية، وبالتالي لن نحتاج إليه تقنيًا، لكننا سنجد أن استخدامه مفيد في مناسبات عدة.
</p>

<h2>
	إنشاء رابط URL
</h2>

<p>
	إليك الصيغة البرمجية التي تُنشئ كائن <code>URL</code> جديدًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_7" style="">
<span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="pln">url</span><span class="pun">,</span><span class="pln"> </span><span class="pun">[</span><span class="pln">base</span><span class="pun">])</span></pre>

<ul>
<li>
		<strong><code>url</code></strong>: عنوان URL الكامل، أو جزء منه عند إسناد قيمة إلى <code>base</code>.
	</li>
	<li>
		<strong><code>base</code></strong>: أساس اختياري للعنوان، فإذا أسندت قيمة لهذا الوسيط وكانت قيمة الوسيط الآخر <code>url</code> هي مسار فقط، فسيُولّد الكائن URL منسوبًا إلى القاعدة <code>base</code>.
	</li>
</ul>
<p>
	إليك مثالًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_9" style="">
<span class="pln">let url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://javascript.info/profile/admin'</span><span class="pun">);</span></pre>

<p>
	لاحظ أن كائني <code>URL</code> التاليين متطابقين تمامًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_11" style="">
<span class="pln">let url1 </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://javascript.info/profile/admin'</span><span class="pun">);</span><span class="pln">
let url2 </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'/profile/admin'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'https://javascript.info'</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">url1</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://javascript.info/profile/admin</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">url2</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://javascript.info/profile/admin</span></pre>

<p>
	يمكن بالطبع إنشاء كائن <code>URL</code> جديد مبني على مسار نسبي أساسه كائن <code>URL</code> موجود مسبقًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_13" style="">
<span class="pln">let url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://javascript.info/profile/admin'</span><span class="pun">);</span><span class="pln">
let newUrl </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'tester'</span><span class="pun">,</span><span class="pln"> url</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">newUrl</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://javascript.info/profile/tester</span></pre>

<p>
	نستطيع الوصول إلى مكوّنات الكائن <code>URL</code> مباشرةً، وبالتالي سيقدم طريقةً أنيقةً لتفسير العناوين:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_16" style="">
<span class="pln">let url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://javascript.info/url'</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">.</span><span class="pln">protocol</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https:</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">.</span><span class="pln">host</span><span class="pun">);</span><span class="pln">     </span><span class="com">// javascript.info</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">.</span><span class="pln">pathname</span><span class="pun">);</span><span class="pln"> </span><span class="com">// /url</span></pre>

<p style="text-align: center;">
	<a class="ipsAttachLink ipsAttachLink_image" data-fileid="74595" data-ss1634992550="1" data-ss1634992670="1" href="https://academy.hsoub.com/uploads/monthly_2021_08/URL_components_01.png.556c2ca9064ab03cf3e8a66113c5fa4d.png" rel=""><img alt="URL_components_01.png" class="ipsImage ipsImage_thumbnailed" data-fileid="74595" data-unique="ce9qbaon6" src="https://academy.hsoub.com/uploads/monthly_2021_08/URL_components_01.png.556c2ca9064ab03cf3e8a66113c5fa4d.png"></a>
</p>

<p>
	إليك لائحةً بمكوّنات <code>URL</code>:
</p>

<ul>
<li>
		<code>href</code>: ويعيد العنوان كاملًا، تمامًا كما يفعل التابع <code>()url.toString</code>.
	</li>
	<li>
		<code>protocol</code>: جزء من العنوان ينتهي بالنقطتين ":".
	</li>
	<li>
		<code>search</code>: سلسلة نصية من المعاملات يبدأ بإشارة الاستفهام "؟".
	</li>
	<li>
		<code>hash</code>: ويبدأ بالعلامة "#".
	</li>
	<li>
		كما يمكنك أن تجد الخاصيتين <code>user</code> و<code>password</code> عند استخدام استيثاق HTTP، مثل <code><a data-ss1634992550="1" data-ss1634992670="1" href="http://login:password@site.com" ipsnoembed="false" rel="external nofollow">http://login:password@site.com</a></code>، لكنه نادر الاستخدام.
	</li>
</ul>
<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		<strong>يمكن تمرير كائنات <code>URL</code> مثل وسيط إلى طلب عبر الشبكة، بدلًا من تمرير قيمة نصية.</strong> يمكن استخدام هذا الكائن مع <code>fetch</code> أو <code>XMLHttpRequest</code> أو في أي مكان يوجد فيه عنوان <code>URL</code> على شكل قيمة نصية، إذ تُجري معظم الطلبات عملية تحويل إلى نص، وبالتالي سيتحول الكائن <code>URL</code> إلى نص يحمل عنوان URL كاملًا.
	</p>
</blockquote>

<h2>
	معامل البحث "?"
</h2>

<p>
	لنفترض أننا سننشئ عنوان url له معاملات بحث محددة، مثل <code><a data-ss1634992550="1" data-ss1634992670="1" href="https://google.com/search?query=JavaScript" ipsnoembed="false" rel="external nofollow">https://google.com/search?query=JavaScript</a></code>، كما يمكننا وضع المعاملات عند إنشاء كائن <code>URL</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_18" style="">
<span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://google.com/search?query=JavaScript'</span><span class="pun">)</span></pre>

<p>
	ويجب ترميز المعاملات إذا احتوت على فراغات أو أحرف ليست لاتينيةً وما شابه (ستجد المزيد عن ذلك في الفقرات التالية)، لذلك توجد خاصية تتولى ذلك هي <code>url.searchParams</code>، وهي كائن من النوع <a data-ss1634992550="1" data-ss1634992670="1" href="https://url.spec.whatwg.org/#urlsearchparams" rel="external nofollow">URLSearchParams</a>، وتؤمن مجموعةً من التوابع التي تتعامل مع معاملات البحث:
</p>

<ul>
<li>
		<code>(append(name, value</code>: يضيف المعامل المحدد بالاسم <code>name</code>.
	</li>
	<li>
		<code>(delete(name</code>: يحذف المعامل المحدد بالاسم <code>name</code>.
	</li>
	<li>
		<code>(get(name</code>: يحضر المعامل المحدد بالاسم <code>name</code>.
	</li>
	<li>
		<code>(getAll(name</code>: يحضر كل المعاملات التي لها نفس الاسم <code>name</code>، وهو أمر ممكن، مثل <code>?user=John&amp;user=Pete</code>.
	</li>
	<li>
		<code>(has(name</code>: التحقق من وجود معامل بالاسم <code>name</code>.
	</li>
	<li>
		<code>(set(name, value</code>: لضبط معامل أو تغييره.
	</li>
	<li>
		<code>()sort</code>: فرز المعاملات بالاسم، وتُستخدّم نادرًا، وهي قابلة للمرور عليها Iterable بصورة مشابهة للترابط Map.
	</li>
</ul>
<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_20" style="">
<span class="pln">let url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://google.com/search'</span><span class="pun">);</span><span class="pln">

url</span><span class="pun">.</span><span class="pln">searchParams</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'q'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'test me!'</span><span class="pun">);</span><span class="pln"> </span><span class="com">//! يضيف معاملًا ضمنه فراغ وإشارة  </span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://google.com/search?q=test+me%21</span><span class="pln">

url</span><span class="pun">.</span><span class="pln">searchParams</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'tbs'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'qdr:y'</span><span class="pun">);</span><span class="pln"> </span><span class="com">// ":" يضيف معاملًا يحوي العلامة</span><span class="pln">

</span><span class="com">// تُرمز المعاملات تلقائيًا</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://google.com/search?q=test+me%21&amp;tbs=qdr%3Ay</span><span class="pln">

</span><span class="com">//(المرور على المعاملات (فك ترميز</span><span class="pln">
</span><span class="kwd">for</span><span class="pun">(</span><span class="pln">let </span><span class="pun">[</span><span class="pln">name</span><span class="pun">,</span><span class="pln"> value</span><span class="pun">]</span><span class="pln"> of url</span><span class="pun">.</span><span class="pln">searchParams</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
  alert</span><span class="pun">(`</span><span class="pln">$</span><span class="pun">{</span><span class="pln">name</span><span class="pun">}=</span><span class="pln">$</span><span class="pun">{</span><span class="pln">value</span><span class="pun">}`);</span><span class="pln"> </span><span class="com">// q=test me!, then tbs=qdr:y</span><span class="pln">
</span><span class="pun">}</span></pre>

<h2>
	الترميز Encoding
</h2>

<p>
	يحدد المعيار <a data-ss1634992550="1" data-ss1634992670="1" href="https://tools.ietf.org/html/rfc3986" rel="external nofollow">RFC3986</a> المحارف المسموحة في العناوين، والمحارف التي لا يُسمح باستخدامها. وينبغي ترميز المحارف التي لا يسمح بها، مثل الأحرف غير اللاتينية والفراغات، باستبدالها بمقابلاتها في ترميز UTF-8 مسبوقًا بالمحرف "%"، كأن نكتب 20%. ويمكن ترميز الفراغ بالمحرف "+" لأسباب تاريخية -وهذه حالة استثنائية-، لكن الجيد بالأمر هو أنّ الكائن <code>URL</code> ينجز كل ذلك تلقائيًا، وكل ما علينا فعله هو تزويده بالمعاملات دون ترميز، وسيحول العنوان إلى نص:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_22" style="">
<span class="com">// using some cyrillic characters for this example</span><span class="pln">

let url </span><span class="pun">=</span><span class="pln"> </span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="str">'https://ru.wikipedia.org/wiki/Тест'</span><span class="pun">);</span><span class="pln">

url</span><span class="pun">.</span><span class="pln">searchParams</span><span class="pun">.</span><span class="kwd">set</span><span class="pun">(</span><span class="str">'key'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'ъ'</span><span class="pun">);</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln"> </span><span class="com">//https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D1%82?key=%D1%8A</span></pre>

<p>
	لاحظ أن القيمتين <code>Тест</code> في مسار العنوان و<code>ъ</code> في المعامل قد رُمِّزا، سيغدو العنوان أطول لأن كل محرف سيُمثّل ببايتين في UTF-8، وبالتالي ستكون هناك كتلتان من الشكل <code>..%</code> لكل محرف.
</p>

<h2>
	ترميز القيم النصية
</h2>

<p>
	استخدم المبرمجون -في السابق وقبل ظهور الكائن <code>URL</code>- القيم النصية لتمثيل العناوين، لكن استخدام الكائن <code>URL</code> حاليًا أكثر ملاءمةً، ومع ذلك لا يزال استخدام القيم النصية شائعًا، فهو يجعل العنوان أقصر في الكثير من الأحيان، ولا بدّ عند استخدام القيم النصية من ترميز أو فك ترميز المحارف الخاصة يدويًا، باستخدام دوال مدمجة هي:
</p>

<ul>
<li>
		<a data-ss1634992550="1" data-ss1634992670="1" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI" rel="external nofollow">encodeURI</a>: يُرمِّز العنوان بالكامل.
	</li>
	<li>
		<a data-ss1634992550="1" data-ss1634992670="1" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI" rel="external nofollow">decodeURI</a>: يفك ترميز النص المرمَّز.
	</li>
	<li>
		<a data-ss1634992550="1" data-ss1634992670="1" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent" rel="external nofollow">encodeURIComponent</a>: يرمّز مكوّنًا من مكونات العنوان، مثل معاملات البحث أو المسار.
	</li>
	<li>
		<a data-ss1634992550="1" data-ss1634992670="1" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent" rel="external nofollow">decodeURIComponent</a>: يفك ترميز الجزء المُرمَز.
	</li>
</ul>
<p>
	لكن السؤال الطبيعي سيكون: "ما هو الفرق بين <code>encodeURIComponent</code> و<code>encodeURI</code>؟ ومتى سنستخدم كلًا منهما؟"
</p>

<p>
	من السهل استيعاب الفكرة عند النظر إلى الصورة السابقة التي تفصل العنوان التالي إلى مكوناته:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_24" style="">
<span class="pln">https</span><span class="pun">:</span><span class="com">//site.com:8080/path/page?p1=v1&amp;p2=v2#hash</span></pre>

<p>
	إذ يُسمح باستخدام المحارف التالية <code>:</code> و<code>?</code> و<code>=</code> و<code>&amp;</code> و<code>#</code> في عنوان URL، ومن جهة أخرى إذا نظرنا إلى أي مكوّن من مكوّنات العنوان بمفرده، مثل: المعاملات، فلا بدّ من ترميز هذه المحارف حتى لا تخِلّ بتنسيق العنوان.
</p>

<p>
	وهنا سنرى استخدام الدالتين السابقتين:
</p>

<ul>
<li>
		<code>encodeURI</code>: تُرمز المحارف المرفوضة كليًا في العناوين فقط.
	</li>
	<li>
		<code>encodeURIComponent</code>: ترمز نفس المحارف التي ترمزها الدالة السابقة بالإضافة إلى المحارف التالية: <code>#</code> و<code>$</code> و<code>&amp;</code> و<code>+</code> و<code>,</code> و<code>/</code> و<code>:</code> و<code>;</code> و<code>=</code> و<code>?</code> و<code>@</code>.
	</li>
</ul>
<p>
	لذلك يمكن استخدام الدالة <code>encodeURI</code> لترميز العنوان كاملًا:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_26" style="">
<span class="com">// using cyrillic characters in url path</span><span class="pln">
let url </span><span class="pun">=</span><span class="pln"> encodeURI</span><span class="pun">(</span><span class="str">'http://site.com/привет'</span><span class="pun">);</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln"> </span><span class="com">// http://site.com/%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82</span></pre>

<p>
	بينما ستستخدم الدالة <code>encodeURIComponent</code> في ترميز معاملات العنوان:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_29" style="">
<span class="pln">let music </span><span class="pun">=</span><span class="pln"> encodeURIComponent</span><span class="pun">(</span><span class="str">'Rock&amp;Roll'</span><span class="pun">);</span><span class="pln">

let url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">https</span><span class="pun">:</span><span class="com">//google.com/search?q=${music}`;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://google.com/search?q=Rock%26Roll</span></pre>

<p>
	لاحظ الفرق عند استخدام <code>encodeURI</code>:
</p>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_32" style="">
<span class="pln">let music </span><span class="pun">=</span><span class="pln"> encodeURI</span><span class="pun">(</span><span class="str">'Rock&amp;Roll'</span><span class="pun">);</span><span class="pln">

let url </span><span class="pun">=</span><span class="pln"> </span><span class="pun">`</span><span class="pln">https</span><span class="pun">:</span><span class="com">//google.com/search?q=${music}`;</span><span class="pln">
alert</span><span class="pun">(</span><span class="pln">url</span><span class="pun">);</span><span class="pln"> </span><span class="com">// https://google.com/search?q=Rock&amp;Roll</span></pre>

<p>
	حيث لا تُرمّز الدالة <code>encodeURI</code> المحرف <code>&amp;</code> لأنه محرف مقبول في عنوان URL الكامل، لكن لا بدّ من ترميزه عندما يكون ضمن معامل البحث، وإلا ستكون نتيجة <code>q=Rock&amp;Roll</code> هي <code>q=Rock</code>، بالإضافة إلى معامل غامض يمثله <code>Roll</code>، وهي ليست كما قصدنا.
</p>

<p>
	لذا لا بدّ من استخدام <code>encodeURIComponent</code> فقط عند تشفير كل معامل من معاملات البحث، لكي نضعه بشكله الصحيح في نص العنوان، وتبقى طريقة ترميز الاسم والعنوان معًا هي الأكثر أمانًا؛ إلا عندما نثق تمامًا بعدم احتواء أي منهما على محارف ممنوعة الاستخدام.
</p>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		<strong>اختلافات التشفير بالنسبة إلى العناوين URL</strong> بُني الصنفان <a data-ss1634992550="1" data-ss1634992670="1" href="https://url.spec.whatwg.org/#url-class" rel="external nofollow">URL</a> و<a data-ss1634992550="1" data-ss1634992670="1" href="https://url.spec.whatwg.org/#interface-urlsearchparams" rel="external nofollow">URLSearchParams</a> على التوصيفات الأخيرة للعناوين URI ضمن المعيار <a data-ss1634992550="1" data-ss1634992670="1" href="https://tools.ietf.org/html/rfc3986" rel="external nofollow">RFC3986</a>، بينما بُنيت الدوال <code>*encode</code> على النسخة <a data-ss1634992550="1" data-ss1634992670="1" href="https://www.ietf.org/rfc/rfc2396.txt" rel="external nofollow">RFC2396</a> المنتهية المفعول. ستجد بعض الاختلافات بينهما مثل عناوين IPv6 التي ستُرمَّز بشكل مختلف:
	</p>
</blockquote>

<pre class="ipsCode prettyprint lang-javascript prettyprinted" id="ips_uid_9703_35" style="">
<span class="com">// valid url with IPv6 address</span><span class="pln">
let url </span><span class="pun">=</span><span class="pln"> </span><span class="str">'http://[2607:f8b0:4005:802::1007]/'</span><span class="pun">;</span><span class="pln">

alert</span><span class="pun">(</span><span class="pln">encodeURI</span><span class="pun">(</span><span class="pln">url</span><span class="pun">));</span><span class="pln"> </span><span class="com">// http://%5B2607:f8b0:4005:802::1007%5D/</span><span class="pln">
alert</span><span class="pun">(</span><span class="kwd">new</span><span class="pln"> URL</span><span class="pun">(</span><span class="pln">url</span><span class="pun">));</span><span class="pln"> </span><span class="com">// http://[2607:f8b0:4005:802::1007]/</span></pre>

<blockquote class="ipsQuote" data-ipsquote="">
	<div class="ipsQuote_citation">
		اقتباس
	</div>

	<p>
		وكما نرى ستستبدل الدالة <code>encodeURI</code> محتوى الأقواس المربعة <code>[...]</code> غير الصحيحة كونها عنوان IPv6، فلم تكن هذه العناوين موجودةً في زمن المعيار (أغسطس "آب" 1998)، وهذه الحالات نادرة، لذلك ستعمل الدوال <code>*encode</code> جيدًا في معظم الأوقات.
	</p>
</blockquote>

<p>
	ترجمة -وبتصرف- للفصل <a data-ss1634992550="1" data-ss1634992670="1" href="https://javascript.info/url" rel="external nofollow">URL Objects</a> من سلسلة <a href="https://javascript.info/" rel="external nofollow">The Modern JavaScript Tutorial</a>.
</p>

<h2>
	اقرأ أيضًا
</h2>

<ul>
<li>
		المقال السابق: <a data-ss1634992550="1" data-ss1634992670="1" href="https://academy.hsoub.com/programming/javascript/%D8%A7%D9%84%D9%88%D8%A7%D8%AC%D9%87%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A9-fetch-api-r1297/" rel="">الواجهة البرمجية Fetch <abbr title="Application Programming Interface | واجهة برمجية">API</abbr></a>
	</li>
	<li>
		<a data-ss1634992550="1" data-ss1634992670="1" href="https://academy.hsoub.com/programming/javascript/%D8%AA%D8%B1%D9%85%D9%8A%D8%B2-%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D9%88%D8%A7%D9%84%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%85%D8%B9-%D9%83%D8%A7%D8%A6%D9%86%D8%A7%D8%AA-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-%D9%81%D9%8A-%D8%AC%D8%A7%D9%81%D8%A7%D8%B3%D9%83%D8%B1%D9%8A%D8%A8%D8%AA-r1283/" rel="">ترميز النصوص والتعامل مع كائنات الملفات في جافاسكريبت</a>
	</li>
</ul>
]]></description><guid isPermaLink="false">1298</guid><pubDate>Fri, 27 Aug 2021 15:04:00 +0000</pubDate></item></channel></rss>
