لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 01/14/22 في كل الموقع
-
يمكنك حذف مجلد gradle في المشروع و إعادة تحميله، سيتم ذلك تلقائيا في أندرويد استديو عند عمل المزامنة. و تأكدي في gradle wrapper.properties من أن بروتوكول الاتصال هو http بدل https في رابط تحميل gradle2 نقاط
-
بعد أن تعرفنا على أساسيات التعابير النمطية ثم تعرفنا على المجموعات ثم المحددات الكمية ثم تعلمنا كيفية التقاط عدة مجموعات عبر التعابير النمطية، سنقدم فيما يلي مجموعةً من الأفكار المتقدمة في بناء وتنفيذ تعابير نمطية أكثر كفاءةً في البحث عن التطابقات المطلوبة، مثل المراجع References، والمحرف البديل OR، والبحث قُدُمًا lookahead، والبحث إلى الخلف lookbehind، والبحث في موقع محدد باستخدام الراية y. المراجع إلى المجموعات بالأعداد والأسماء يمكن استخدام محتويات أقواس المجموعات الملتقطة (...) في النتيجة أو في النص البديل، وحتى في النمط نفسه. المراجع باستخدام الأعداد يمكن الإشارة إلى مجموعة ضمن نمط باستخدام N\، حيث N هو رقم المجموعة، ولتوضيح أهمية هذه الإشارة المرجعية لنتأمل المهمة التالية: نريد إيجاد ما داخل إشارتي التنصيص المفردتين '...' أو المزدوجتين "..." في نص ما، فكيف سننجز ذلك؟ يمكن وضع نوعي إشارة التنصيص في أقواس مربعة ['"](?*.)['"]، لكنه في هذه الحالة سيجد محتويات مختلطةً، مثل "...' و'..."، مما سيقودنا إلى تطابقات خاطئة عندما يظهر نوع من الإشارات ضمن آخر، مثل "!She's the one". let str = `He said: "She's the one!".`; let regexp = /['"](.*?)['"]/g; // النتيجة ليست كما نتوقع alert( str.match(regexp) ); // "She' وجد النمط -كما توقعنا- إشارة تنصيص البداية "، ثم استهلك النص بعدها حتى وجد إشارة تنصيص أخرى ' أنهت التطابق، وللتأكد من مطابقة إشارة التنصيص الختامية لإشارة تنصيص البداية، يمكن وضعها ضمن قوسي مجموعة، والإشارة إليها بعدد مرجعي 1\(?*.)(["']) إليك الشيفرة الصحيحة: let str = `He said: "She's the one!".`; let regexp = /(['"])(.*?)\1/g; alert( str.match(regexp) ); // "She's the one!" سيجد محرك التعبير النمطي إشارة تنصيص البداية (["'])، ويتذكر محتواها الذي يمثل المجموعة الملتقطة الأولى، حيث يعني العدد 1\ في النمط إيجاد نفس التطابق الموجود في المجموعة الأولى، وهي في حالتنا إشارة تنصيص تطابق تمامًا إشارة البداية. وسيعني العدد 2\ محتويات المجموعة الثانية، والعدد 3\ محتويات الثالثة، وهكذا. لاحظ، لن نتمكن من الإشارة إلى مجموعة إذا استخدمنا نمط الاستثناء :?، ولن يتذكر المحرك محتوى المجموعات المستثناة (...:?). انتبه، لا تخلط بين الإشارة 1\ في نمط ما، والإشارة 1$ في النص البديل، حيث نستخدم إشارة الدولار 1$ في الإشارة المرجعية ضن النص البديل، والشرطة المعكوسة 1\ ضمن الأنماط. المراجع باستخدام الأسماء إذا احتوى التعبير النمطي على عدة أقواس، فمن الأنسب استخدام الأسماء للدلالة عليها، نستخدم <k<name\ في الدلالة على القوس بالاسم، فإذا سُميت مجموعة ضمن التعبير النمطي بالاسم <quote>?، فسيكون الاسم المرجعي لها هو <k<quote\، وإليك مثالًا: let str = `He said: "She's the one!".`; let regexp = /(?<quote>['"])(.*?)\k<quote>/g; alert( str.match(regexp) ); // "She's the one!" البديل باستخدام OR في التعابير النمطية يعني مصطلح "البديل" Alternation في التعابير النمطية استخدام العملية المنطقية "OR"، ويرمز لها ضمن التعابير النمطية بالخط العمودي |، فلو أردنا مثلًا إيجاد لغات برمجة مثل HTML أو PHP أو Java أو JavaScript، فسيكون التعبير النمطي المناسب هو ?(html|php|java(script، وإليك مثالًا تطبيقيًا: let regexp = /html|php|css|java(script)?/gi; let str = "First HTML appeared, then CSS, then JavaScript"; alert( str.match(regexp) ); // 'HTML', 'CSS', 'JavaScript' لكننا رأينا سابقًا أنّ الأقواس المربعة تنفذ أمرًا مشابهًا، فهي تسمح باختيار أحد المحارف التي توضع ضمنها، فإذا استخدمنا النمط gr[ae]y مثلًا فسنحصل على التطابقين gray أو grey، إذ تسمح الأقواس المربعة باستخدام المحارف أو أصناف المحارف ضمنها، بينما يسمح البديل باستخدام أي عبارات، حيث يعني التعبير A|B|C أيًا من العبارات A أو B أو C، وإليك بعض الأمثلة: يماثل النمط gr(a|e)y النمط gr[ae]y. يعني النمط gra|ey أيًا من gra أو ey. ولتطبيق البديل على جزء محدد من نمط، يمكن وضع هذا الجزء داخل قوسين: يطابق النمط I love HTML|CSS كلًا من I love HTML أو CSS. يطابق النمط (I love (HTML|CSS كلًا من I love HTML أو I love CSS مثال: تعبير نمطي لإيجاد الوقت صادفنا في المقالات السابقة مهمة بناء تعبير نمطي يبحث عن الوقت وفق التنسيق hh:mm، مثل 12:00، لكن التعبير الذي استُخدم d\d:\d\d\ سطحي جدًا، إذ يقبل هذا النمط قيمًا خاطئة للتوقيت مثل 25:99، فكيف سننجز نمطًا أفضل؟ بالنسبة للساعات: إذا كان الرقم الأول 0 أو1 فيمكن أن يكون الثاني أي رقم. إذا كان الرقم الأول 2، فيجب أن يكون الثاني [0-3]. لا يسمح بأي رقم آخر غير ذلك. يمكن كتابة تعبير نمطي يضم الحالتين باستخدام البديل بالشكل التالي: [01]\d|2[0-3] أما بالنسبة للدقائق: ينبغي أن تكون الدقائق بين 00 و59، ويكتب هذا في التعبير النمطي بالشكل [0-5]\d، أي يمكن أن تكون الآحاد أي رقم، والعشرات من 1 إلى 5. وعند ضم الساعات والدقائق معًا سنحصل على التعبير: [01]\d|2[0-3]:[0-5]\d انتهينا تقريبًا لكن مع وجود مشكلة صغيرة، إذ وضعنا محرف البديل | بالشكل السابق فصل التعبير إلى قسمين منفصلين: [01]\d | 2[0-3]:[0-5]\d إليك ما سيحدث، سيبحث النمط عن أحد النمطين، لكن هذا خاطئ، لأنّ البديل سيستخدَم فقط في قسم الساعات من التعبير النمطي، وسنصلح ذلك بوضع قسم الساعات ضمن قوسين ([01]\d|2[0-3]):[0-5]\d إليك الحل النهائي: let regexp = /([01]\d|2[0-3]):[0-5]\d/g; alert("00:00 10:10 23:59 25:99 1:2".match(regexp)); // 00:00,10:10,23:59 التحقق مما يلي أو يسبق التعبير النمطي نحتاج في بعض الأحيان إلى إيجاد تطابقات بشرط أن يأتي بعدها أو قبلها تطابقًا أو نمطًا محددًا دون أن تدخل تلك التطابقات ضمن قيم النتيجة النهائية، ولهذه الغاية سنجد صيغتا تحقق تُدعيان "انظر أمام النمط" lookahead،" وانظر خلف النمط "lookbehind،" وقبل أن نبدأ موضوعنا، سنأخذ مثالًا نحاول فيه إيجاد السعر ضمن النص 1 turkey costs 30€، وهو عدد تتبعه الإشارة €. انظر أمام التعبير النمطي النمط (X(?=Y يعني "ابحث عن X، لكن أعد نتيجة التطابق فقط إذا كان بعدها Y"، ويمكن أن تجد أي نمط مكان X أو Y، فمن أجل عدد صحيح تتبعه الإشارة €، سيكون النمط المناسب هو (€=?)+d\: let str = "1 turkey costs 30€"; alert( str.match(/\d+(?=€)/) ); // 30, € يهمل الرقم 1 لأنه غير متبوع بالإشارة لاحظ أنّ مطابقة ما أمام النمط هو اختبار وعملية تحقيق فقط ببساطة، ولا يشكل محتوى ما بين القوسين جزءًا من النتيجة، فعندما نبحث عن النمط (X(?=Y، فسيجد المحرك X ومن ثم يتحقق من وجود Y بعدها مباشرةً إن وجده أعاد X فقط وإلا لم يعد شيئًا، ثم يتابع البحث. يمكن كتابة اختبارات أكثر تعقيدًا، مثل النمط (x(?=y)(?=z الذي يعني: جد X. تحقق من وجود Y مباشرةً بعد X، وتجاوز التطابق إن لم يتحقق ذلك. تحقق أيضًا من وجود Z مباشرةً بعد X، وتجاوز التطابق إن لم يتحقق ذلك. إذا نجح الاختباران السابقان فأعد قيمة X، وإلا فتابع البحث. أي أننا نبحث عن X التي تتبعها القيمتان Y وZ في نفس الوقت. وسيبحث النمط (d+(?=\s)(?=.*30\ عن عدد +d\ يتبعه فراغ (s\=?)، ثم العدد 30 في مكان ما من النص: let str = "1 turkey costs 30€"; alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1 التطابق الوحيد المناسب في المثال السابق هو 1. لنفترض بأننا سنحتاج إلى الكمية وليس السعر من النص نفسه، وبالتالي سنحتاج إلى عدد +d\ غير متبوع بالإشارة €، يمكن إنجاز ذلك باستعمال النمط الاختباري (x(?!y، أي "انظر أمام النمط، يجب أن لا يتحقق الشرط" بمعنى ابحث عن X التي لا يتبعها Y مباشرةً. let str = "2 turkeys cost 60€"; alert( str.match(/\d+\b(?!€)/g) ); // 2 (the price is not matched) انظر خلف التعبير النمطي يشابه هذا النمط نمط التحقق مما يلي التعبير، لكنه يضع شرطًا على ما قبل التعبير المتطابق ويمكن أن نستخدمه بالصيغ التالية: "التحقق من مطابقة ما يسبق التعبير" Positive lookbehind عبر الصيغة Y)X=>?): أوجد X إذا سبقتها Y. "التحقق من عدم مطابقة ما يسبق التعبير" Negative lookbehind عبر الصيغة Y)X!>?): أوجد X إذا لم تسبقها Y. لنغير على سبيل المثال السعر إلى دولار، حيث تكون إشارة الدولار $ عادةً قبل السعر، فللبحث عن 30$ سنستخدم النمط +d\($\=>?): let str = "1 turkey costs $30"; // \$ سنتجاوز الإشارة alert( str.match(/(?<=\$)\d+/) ); // 30 وإذا أردنا الكمية، فسنبحث عن عدد لا تسبقه الإشارة $، وبالتالي نستخدم البحث السلبي خلفًا +d\($\!>?): let str = "2 turkeys cost $60"; alert( str.match(/(?<!\$)\b\d+/g) ); // 2 إدخال الشرط ضمن نتيجة التطابق لا يظهر محتوى ما بين القوسين جزءًا من النتيجة عادةً، فلن تكون الإشارة € مثلًا ضمن نتيجة التطابق مع النمط (€=?)+d\. وهذا أمر طبيعي، لأننا نبحث عن العدد الذي قبلها، وهي مجرد اختبار، لكن قد نحتاج في بعض الحالات إلى ما حول التطابق أو إلى جزء منه، ويمكن إنجاز ذلك بوضع الجزء المطلوب ضمن قوسين إضافيين، سنعيد في المثال التالي السعر مع إشارة العملة (kr|€): let str = "1 turkey costs 30€"; let regexp = /\d+(?=(€|kr))/; // €|kr أقواس إضافية حول alert( str.match(regexp) ); // 30, € إليك مثالًا مع البحث خلفًا: let str = "1 turkey costs $30"; let regexp = /(?<=(\$|£))\d+/; alert( str.match(regexp) ); // 30, $ الراية y والبحث في موقع محدد تسمح الراية y بالبحث في موقع محدد ضمن النص الأصلي، ولفهم عمل هذه الراية وأساليب استخدامها في التعابير النمطية جيدًا سنطرح مشكلةً عمليةً، حيث تعتبر عملية التحليل التجريدي لنص lexical analysis من المهام الأساسية لمحركات التعابير النمطية، ففي نصوص الشيفرة في لغات البرمجة تلعب محركات التعابير الدور الأساسي في إيجاد العناصر البنيوية، مثل وسوم HTML والسمات التي في داخلها، وستساعد في JavaScript على إيجاد المتغيرات والدوال وغيرها، ولن ندخل في موضوع إنشاء المحللات التجريدية فهي خارج نطاق موضوعنا، لكن توجد مهمة مشتركة وهي إيجاد شيء ما في موقع محدد، لنفترض مثلًا أنّ لدينا الشيفرة التالية "let varName = "value في صيغة نص، ونحتاج إلى تحديد اسم المتغير الذي سيبدأ في الموقع الرابع، سنبحث عن اسم المتغير من خلال التعبير +w\، وطبعًا نحتاج إلى تعابير أكثر تعقيدًا للبحث الدقيق عن أسماء المتغيرات في JavaScript لكن لن نهتم بذلك حاليًا. سيجد التابع (/+str.match(/\w الكلمة الأولى فقط في السطر وهي (let)، وطبعًا ليست هي الكلمة المطلوبة. ولو استخدمنا الراية g فسيبحث التابع عن جميع الكلمات في النص، لكننا لا نحتاج سوى الكلمة الموجودة في الموقع الرابع. كيف سنبحث عن نمط محدد ابتداءً من موقع محدد؟ لنحاول استخدام التابع (regexp.exec(str، حيث سيبحث هذا التابع عن التطابق الأول بطريقة مشابهة للتابع (str.match(regexp عندما يُستخدم دون الرايتين g أو y، لكنه سيبحث في النص ابتداءً من الموقع المخزن في الخاصية regexp.lastIndex عندما نفعّل الراية g، فإذا وجد تطابقًا فسيخزن الموقع الذي يلي التطابق في الخاصية regexp.lastIndex، أي ستكون الخاصية regexp.lastIndex نقطة بداية للبحث، وسيغير كل استدعاء للتابع (regexp.exec(str قيمتها إلى القيمة الجديدة -بعد إيجاد تطابق-، وبالتالي سيعيد الاستدعاء المتكرر للتابع (regexp.exec(str التطابقات واحدًا تلو الآخر، وإليك مثالًا: let str = 'let varName'; // لنبحث عن كل الكلمات في هذه السلسلة let regexp = /\w+/g; alert(regexp.lastIndex); // 0 ( lastIndex=0) let word1 = regexp.exec(str); alert(word1[0]); // let (الكلمة الأولى) alert(regexp.lastIndex); // 3 (الموقع بعد التطابق) let word2 = regexp.exec(str); alert(word2[0]); // varName (الكلمة الثانية) alert(regexp.lastIndex); // 11 (الموقع بعد التطابق) let word3 = regexp.exec(str); alert(word3); // null (لا تطابقات) alert(regexp.lastIndex); // 0 (يصفر قيمة الخاصية) ويمكن الحصول على كل التطابقات من خلال حلقة: let str = 'let varName'; let regexp = /\w+/g; let result; while (result = regexp.exec(str)) { alert( `Found ${result[0]} at position ${result.index}` ); // Found let at position 0, then // Found varName at position 4 } يمثل استخدام التابع regexp.exec أسلوبًا بديلًا عن str.matchAll مع بعض السيطرة على مجرى العملية. بالعودة إلى مهمتنا، سنتمكن من تحديد الموقع بإسناد القيمة 4 إلى الخاصية lastIndex لنبدأ البحث من الموقع المطلوب بالشكل التالي: let str = 'let varName = "value"'; let regexp = /\w+/g; // "g" دون استخدام lastIndex يتجاهل المحرك الخاصية regexp.lastIndex = 4; let word = regexp.exec(str); alert(word); // varName لقد حُلِّت المشكلة! والنتيجة صحيحة، لكن ينبغي أن نتروّى قليلًا.. يبدأ التابع regexp.exec البحث انطلاقًا من الموقع lastIndex، ثم يستأنف البحث، فإذا لم يكن التطابق موجودًا في هذا الموقع بل في موقع يأتي بعده فسيجده أيضًا: let str = 'let varName = "value"'; let regexp = /\w+/g; // ابدأ البحث في الموقع 3 regexp.lastIndex = 3; let word = regexp.exec(str); // سيجد التطابق في الموقع 4 alert(word[0]); // varName alert(word.index); // 4 إنّ هذا الأمر خاطئ في العديد من المهام ومنها التحليل التجريدي، إذ يجب إيجاد التطابق في الموقع المحدد تمامًا وليس بعده، وهذا سبب وجود الراية y. حيث تجبر الراية y التابع regexp.exec على البحث في الموقع lastIndex بالتحديد وليس انطلاقًا منه. إليك مثالًا: let str = 'let varName = "value"'; let regexp = /\w+/y; regexp.lastIndex = 3; alert( regexp.exec(str) ); // null (يوجد فراغ في هذا الموقع وليس كلمة) regexp.lastIndex = 4; alert( regexp.exec(str) ); // varName (الكلمة في الموقع 4) لاحظ أننا لن نحصل على التطابق مع التعبير w+/y\/ في الموقع 3 (على عكس g) لكن في الموقع 4، وليس هذا فحسب، بل توجد فائدة أخرى لاستخدام الراية y، تخيل أن لدينا نصًا طويلًا ولا توجد فيه أية تطابقات للنمط الذي نبحث عنه، سيبحث المحرك عند تفعيل الراية g حتى نهاية النص ولن يجد نتيجةً، وهذا ما سيستهلك الوقت مقارنةً باستخدام الراية y التي تتحقق فقط في الموقع المحدد. سنواجه في المهام التي تشبه التحليل التجريدي عمليات بحث متعددةً في مواقع محددة، للتحقق مما هو موجود، وسيكون استخدام الراية y المفتاح لإنجاز مثل هذه العمليات بدقة وفعالية. خلاصة يفيدنا التحقق مما يلي التطابق ويسبقه في إيجاد تطابق بناءً على ما قبله أو بعده. يمكن استخدام تعابير نمطية بسيطة لإنجاز ذلك يدويًا، كأن نبحث عن كل التطابقات المطلوبة ونرشحها من خلال الحلقات وفق السياق المطلوب. تذكر أن التابع str.match (مع الراية g) والتابع str.match (دائمًا) سيعيدان مصفوفةً لها الخاصية index، وبالتالي سنعلم تمامًا موقع التطابق في النص، ثم نتحقق مما قبله أو بعده. إذا نفِّذ البحث خلفًا أو قدمًا آليًا فسيكون أفضل من الطريقة اليدوية، مثلما فعلنا في هذا المقال: 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; } النمط نوع البحث ماذا يُطابق؟ (X(?=Y بحث إيجابي قُدمًا X إذا جاءت بعدها Y (X(?!Y بحث سلبي قدمًا X إذا لم تكن بعدها Y y)x=>?) بحث إيجابي خلفًا X إذا جاءت بعد Y y)x!>?) بحث سلبي خلفًا X إذا لم تأت بعد Y تجبر الراية y التابع regexp.exec على البحث في الموقع lastIndex بالتحديد وليس انطلاقًا منه. مهام لإنجازها إيجاد لغات البرمجة اكتب تعبيرًا نمطيًا يبحث عن أسماء لغات البرمجة الموجودة في النص Java JavaScript PHP C++ C let regexp = /your regexp/g; alert("Java JavaScript PHP C++ C".match(regexp)); // Java JavaScript PHP C++ C الحل: تقتضي الفكرة الأولى بترتيب اللغات بحيث يفصل بينها المحرف|، لكن هذا الحل لن ينفع: let regexp = /Java|JavaScript|PHP|C|C\+\+/g; let str = "Java, JavaScript, PHP, C, C++"; alert( str.match(regexp) ); // Java,Java,PHP,C,C سيبحث محرك التعابير النمطية عن البدائل واحدًا تلو الآخر، فلو تحقق بداية من "Java" ووجدها، فلن يجد بالطبع "JavaScript" وكذلك الأمر بالنسبة للغتين "C" و "++C". هنالك حلين للمشكلة: غير ترتيب اللغات لتكون اللغة ذات الاسم الأطول أولًا: JavaScript|Java|C\+\+|C|PHP ادمج اللغات التي لها نفس البداية : Java(Script)?|C(\+\+)?|PHP إليك الشيفرة: let regexp = /Java(Script)?|C(\+\+)?|PHP/g; let str = "Java, JavaScript, PHP, C, C++"; alert( str.match(regexp) ); // Java,JavaScript,PHP,C,C++ إيجاد زوجي وسوم [tag]…[/tag] لنفترض وجود زوج الوسوم [tag]...[/tag]، حيث يمثّل tag أحد العناصر b أو url أو quote، إليك مثالًا: [b]text[/b] [url]http://google.com[/url] قد تكون هذه الوسوم متداخلةً، لكن لا يمكن أن يتداخل العنصر مع نفسه: Normal: [url] [b]http://google.com[/b] [/url] [quote] [b]text[/b] [/quote] Can't happen: [b][b]text[/b][/b] ليس بالضرورة أن تكون وسوم البداية والنهاية لعنصر على نفس السطر: [quote] [b]text[/b] [/quote] اكتب تعبيرًا نمطيًا للبحث عن تلك الوسوم ومحتوياتها، إليك مثالًا: let regexp = /your regexp/flags; let str = "..[url]http://google.com[/url].."; alert( str.match(regexp) ); // [url]http://google.com[/url] إذا كانت الوسوم متداخلةً فنريد فقط العنصر الخارجي: let regexp = /your regexp/flags; let str = "..[url][b]http://google.com[/b][/url].."; alert( str.match(regexp) ); // [url][b]http://google.com[/b][/url] الحل: يُعطى التعبير النمطي للبحث عن وسم البداية بالشكل: \[(b|url|quote)] ولإيجاد المحتوى كاملًا حتى بلوغ وسم النهاية سنستخدم النمط ?* مع الراية s لالتقاط أي محرف بما في ذلك محرف بداية السطر ثم سنضيف مرجعًا للخلف إلى وسم النهاية. \[(b|url|quote)\].*?\[/\1] إليك الشيفرة: let regexp = /\[(b|url|quote)].*?\[\/\1]/gs; let str = ` [b]hello![/b] [quote] [url]http://google.com[/url] [/quote] `; alert( str.match(regexp) ); // [b]hello![/b],[quote][url]http://google.com[/ur إيجاد ما بين إشارتي التنصيص "…." أنشئ تعبيرًا نمطيًا لإيجاد سلسلة نصية بين إشارتي تنصيص"….". يجب أن يدعم النص فكرة تجاوز المحارف تمامًا كما في جافا سكربت. أي يمكن أن نضع علامة التنصيص بالشكل "\ وأن نضع علامة السطر الجديد بالشكل n\ والشرطة المائلة بالشكل \\. let str = "Just like \"here\"."; وانتبه إلى أن علامة التنصيص التي يجري تجاوزها "\ لن تعتبر نهاية للسلسلة النصية. وبالتالي لا بدّ من البحث من إشارة تنصيص إلى أخرى متجاهلين علامات التنصيص التي يجري تجاوزها. أمثلة عن نصوص وتطابقاتها: .. "test me" .. .. "Say \"Hello\"!" ... (داخلها علامة تنصيص جرى تجاوزها) .. "\\" .. (داخلها شرطة مائلة مزدوجة) .. "\\ \"" .. (داخلها شرطة مائلة مزدوجة وعلامة تنصيص جرى تجاوزها) لا بد في جافاسكربت من مضاعفة الشرطات المائلة لتمريرها إلى النصوص كالتالي: let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; // the in-memory string alert(str); // .. "test me" .. "Say \"Hello\"!" .. "\\ \"" .. الحل: /"(\\.|[^"\\])*"/g نتبع الآتي خطوةً بخطوة: نبحث أولًا عن إشارة تنصيص البداية ". إن كان هناك شرطة عكسية \\ لا بد من مضاعفتها في التعبير لأنها محرف خاص. بعدها يمكن أن يأتي أي محرف. باستثناء الحالتين السابقتين يمكن أن نأخذ أية محارف ما عدا إشارة التنصيص (التي تعني نهاية النص) والشرطة العكسية (تستخدم الشرطة العكسية فقط مع رموز أخرى بعدها لمنع ظهورها مفردة): [^"\\] وهكذا حتى نصل إلى إشارة تنصيص نهاية النص. إليك الشيفرة: let regexp = /"(\\.|[^"\\])*"/g; let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; alert( str.match(regexp) ); // "test me","Say \"Hello\"!","\\ \"" إيجاد وسم بأكمله اكتب تعبيرًا نمطيًا لإيجاد الوسم <style> كاملًا. قد لا يضم الوسم أية صفات attributes أو عددًا منها: <style type="..." id="..."> لا يجب أن يطابق التعبير الوسم <styler> مثلًا: let regexp = /your regexp/g; alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test="..."> الحل: من المؤكد أن بداية النمط هي <style. لكن لا يمكن أن تكون بقية النمط ببساطة <?*.style> لأن وسمًا مثل <styler> سيكون نتيجة محتملة. نحتاج إلى فراغ بعد <style ثم محارف اختيارية بعده أو النهاية، وبالتالي سيكون النمط المطلوب: <style(>|\s.*?>) إليك الشيفرة: let regexp = /<style(>|\s.*?>)/g; alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test إيجاد أعداد صحيحة موجبة لدينا سلسلة نصية من الأعداد الصحيحة . أنشئ تعبيرًا نمطيًا يبحث عن الأعداد الموجبة فقط (يُسمح بالتقاط الصفر) مثال عن الاستخدام: let regexp = /your regexp/g; let str = "0 12 -5 123 -18"; alert( str.match(regexp) ); // 0, 12, 123 الحل: التعبير النمطي المناسب لإيجاد عدد صحيح هو : \d+ يمكن التخلص من الأعداد السالبة باستخدام النظر خلفًا إلى الإشارة السالبة : (?<!-)\d+ لو استخدمنا هذا النمط فقد نلاحظ ظهور نتيجة إضافية: let regexp = /(?<!-)\d+/g; let str = "0 12 -5 123 -18"; console.log( str.match(regexp) ); // 0, 12, 123, 8 كما ترى فقد وجد 8 بدلًا من -18. لحل المشكلة، لا بدّ من التحقق أن التعبير النمطي لن يبدأ المطابقة من منتصف رقم آخر غير مطابق. يمكن إنجاز الأمر بتخصيص عملية بحث أخرى للخلف عن الإشارة السالبة : (?<!-)(?<!\d)\d+ يتحقق النمط (?<!\d) أن المطابقة لن تبدأ بعد رقم آخر، بل فقط ما نحتاجه، ويمكن ضمهما أيضًا في عملية بحث للخلف واحدة: . let regexp = /(?<![-\d])\d+/g; let str = "0 12 -5 123 -18"; alert( str.match(regexp) ); // 0, 12, 123 إضافة شيفرة بعد الوسم Head لدينا نص ضمن مستند HTML. اكتب تعبيرًا نمطيًا يدخل الشيفرة <h1>Hello</h1> بعد الوسم <body> مباشرة ولا تنس أنّ هذا الوسم قد يحوي صفات داخله. إليك مثالًا: let regexp = /your regular expression/; let str = ` <html> <body style="height: 200px"> ... </body> </html> `; str = str.replace(regexp, `<h1>Hello</h1>`); يجب أن تكون قيمة str بعد ذلك : <html> <body style="height: 200px"><h1>Hello</h1> ... </body> </html> الحل: علينا أولًا إيجاد الوسم <body>وذلك باستخدام التعبير النمطي: <body.*?> لا حاجة لتعديل الوسم <body> بل فقط إضافة النص بعده. إليك ما يمكن فعله: let str = '...<body style="...">...'; str = str.replace(/<body.*?>/, '$&<h1>Hello</h1>'); alert(str); // ...<body style="..."><h1>Hello</h1>... يعني النمط في النص المُستبدل التطابق الذاتي أي الجزء من النص الأصل المرتبط بالتعبير النمطي <?*. body> حيث سيُستبدل بنفسه إضافة إلى "<h1>Hello</h1>". يقتضي الحل البديل استخدام البحث خلفًا: let str = '...<body style="...">...'; str = str.replace(/(?<=<body.*?>)/, `<h1>Hello</h1>`); alert(str); // ...<body style="..."><h1>Hello</h1>... لاحظ وجود جزء البحث خلفًا فقط في هذا التعبير النمطي حيث يعمل كالتالي: تحقق عند كل موقع في النص إن تبعه النمط <?*. body>. إن كان الأمر كذلك فقد حصلنا على التطابق. لن يُعاد الوسم <?*. body> وستكون النتيجة نصًا فارغًا يتطابق الموقع الذي يتبعه النمط <?*. body>. عندها سيُستبدل النص الفارغ بالنمط <?*. body> يليه النص <h1>Hello</h1>. يمكن الاستفادة من الرايات s و i في النمط كالتالي: /<body.*?>/si تدفع الراية s المحرف "." إلى مطابقة سطر جديد وتدفع الراية i النص <body> لمطابقة النص <BODY> (تحسس لحالة الأحرف). ترجمة -وبتصرف- للفصول Backreferences in Patten وAlternation OR وlookahead and lookbehind وsticky flag y, searching at position من سلسلة The Modern JavaScript Tutorial اقرأ أيضًا التعابير النمطية Regular Expressions في جافاسكريبت مقدمة في التعابير النمطية Regular Expressions التعابير النمطية (regexp/PCRE) في PHP1 نقطة
-
السلام عليكم ورحمة الله ، قمت بتعريب قالب وودربرس على سرفر محلي باستخدام الإضافة loco translate ثم قمت بتنزيل الملفات المتعلقة بالترجمة .po .mo ونقلتها إلى ملف language في المشروع ، وقد ظهرت الترجمة عندي كما ينبغي لكنني أرسلت القالب لشخص آخر فلم تظهر الترجمة عنده ، السؤال هو كيف يمكنني ربط القالب بملفات الترجمة بحيث تعمل على اي موقع آخر ؟ جزاكم الله خيرا1 نقطة
-
مرحبا, تظهر لي هذه المشكلة عند ارسال ايميل في لارافيل Connection could not be established with host smtp.zoho.com :stream_socket_client(): php_network_getaddresses: getaddrinfo failed: Name or service not known1 نقطة
-
أريد القيام بعمل تسجيل الدخول باستخدام تويتر وقمت باستخدام هذه المكتبة react-native-twitter-signin وقمت باتباع التعليمات المتعلقة بقا ولكن تظهرلى هذه المشكلة فى جهاز الاندرويد فقط * What went wrong: Execution failed for task ':app:mergeDebugAssets'. > Could not resolve all files for configuration ':app:debugRuntimeClasspath'. > Could not find :react-native-twitter-signin:. Required by: project :app > Could not find com.twitter.sdk.android:twitter-core:3.0.0. Searched in the following locations: - https://repo.maven.apache.org/maven2/com/twitter/sdk/android/twitter-core/3.0.0/twitter-core-3.0.0.pom - file:/C:/Users/ahmed/.m2/repository/com/twitter/sdk/android/twitter-core/3.0.0/twitter-core-3.0.0.pom - file:/D:/3-Work/now/rubemillion/node_modules/react-native/android/com/twitter/sdk/android/twitter-core/3.0.0/twitter-core-3.0.0.pom - file:/D:/3-Work/now/rubemillion/node_modules/jsc-android/dist/com/twitter/sdk/android/twitter-core/3.0.0/twitter-core-3.0.0.pom - https://dl.google.com/dl/android/maven2/com/twitter/sdk/android/twitter-core/3.0.0/twitter-core-3.0.0.pom - https://www.jitpack.io/com/twitter/sdk/android/twitter-core/3.0.0/twitter-core-3.0.0.pom Required by: project :app > project :react-native-twitter-signin * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. ماهو الحل .. اتذكر اننى قمت بحل هذه المشكلة من قبل حيث قمت بعمل العديد من التعديلات المتعلقة ب maven فى ملف gradle ولكنى اواجه تلك المشكلة مرة اخرى ولا اعرف ما الحل .. أرجو المساعدة ..1 نقطة
-
أقوم بتطوير مشروع مبني بإستخدام فلاسك Flask، وأثناء مرحلة التطوير قمت بعمل العديد من التغيرات، والآن أرغب بحذف الجلسة session الحالية، كيف أقوم بهذا الأمر في فلاسك Flask، وهل توجد طريقة لمسح الجلسة في كل مرة أقوم فيها بإغلاق الخادم أو إغلاق موقع الويب؟1 نقطة
-
لماذا لا تعمل الدالة toLocaleString مع الـ value الخاصة بـ input1 نقطة
-
هذا هو المثال الذي تم إنشائه لحساب قيمة الأوراق المطبوعة لأصحاب المكتبات1 نقطة
-
هذا لأن نوع المتغير test ليس Number حاول استخدام parseInt var test = parseInt (document.getElementById("test").value) الآن ستتمكن من تطبيق demo.innerHTML = test.toLocaleString();1 نقطة
-
مثلا يوجد حقل ادخال <input type="number" id="test"> <button id="action">action</button> <div id="demo"></div> var action = document.getElementById("action"); action.addEventListener("click",function(){ var test = document.getElementById("test").value, demo = document.getElementById("demo"); demo.innerHTML = test; }); لو استخدمت demo.innerHTML = test.toLocaleString(); يتم طباعة الرقم كما تم ادخاله في الحقل ولا تظهر العلامات العشرية1 نقطة
-
هل يمكنك توضيح الشيفرة من فضلك؟1 نقطة
-
يجب التأكد من بيانات الاتصال بsmtp الخاص بك مثل MAIL_MAILER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null // MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" وأيضا التأكد من حالة التشفير في ملف env. MAIL_ENCRYPTION=tls أو MAIL_ENCRYPTION=ssl , و التأكد من حذف الملفات المؤقتة من خلال الأمر التالي php artisan config:clear أو يمكنك استخدام الأمر التالي لحذف جميع الملفات المؤقتة php artisan optimize:clear1 نقطة
-
كيفية إضافة العلامات العشرية إذا كان الرقم مثلا 1000 يبقى الرقم هكذا 1,000 أو أكبر من ذلك var x = 50, y = 2000, z = x * y; console.log(z); // 100000 إذا أخلى الناتج يظهر معه علامة عشرية مثلا 100,0001 نقطة
-
تمام شكراً لك حصل تشتيت عندي بين الدالة toLocaleString و toString1 نقطة
-
يمكننا الاستفادة من الدالة Number.prototype.toLocaleString وهي موجودة built-in في لغة جافاسكربت لطباعة فاصلة بين كل 3 منازل عشري للعدد مثال: var n = 21978444.345; console.log( n.toLocaleString() ); // "21,978,444.345"1 نقطة
-
السلام عليكم ورحمه الله اود ان اعرف كيف يمكنني ان اجعل لعبة معينه او تطبيق معين يعمل عن طريق سيرفر و بمهام انا من ابرمجها له ان كان تطبيق اندرويد او غيره1 نقطة
-
يمكنك إنشاء مجلد جديد ضمن القالب لديك وليكن languages، وضمنه قم بوضع كافة ملفات الترجمة mo. وضمن ملف functions.php قم بوضع الشيفرة التالية لتحميل تلك الملفات بشكل صحيح مع استبدال my_theme باسم القالب الخاص بك: add_action('after_setup_theme', 'lang_theme_setup'); function lang_theme_setup(){ load_theme_textdomain('my_theme', get_template_directory() . '/languages'); }1 نقطة
-
عندما يُحمّل المتصفّح ملف HTML ويجد وسم <script>...</script>، فلا يمكنه مواصلة بناء DOM. يجب أن ينفّذ السكربت حالا. نفس الشيء بالنسبة للسكربتات الخارجيّة <script src="...">..</script>: يجب أن ينتظر المتصفّح تحميل السكربت، ثم ينفّذ السكربت المحمّل، وعندها فقط يمكنه معالجة بقيّة الصفحة. يرافق ذلك مشكلتان مهمّتان: لا يمكن أن تطّلع السكربتات على عناصر DOM التي تحتها، ولا يمكنها إذًا إضافة معالجات وما إلى ذلك. إذا كان هناك سكربت كبير في أعلى الصفحة، فإنّه "يحبس الصفحة". لا يمكن للمستخدمين رؤية محتوى الصفحة حتى يُحمّل السكربت ويُنفّذ: <p>...content before script...</p> <script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> <!-- هذا غير مرئيّ إلى أن يُحمّل السكربت --> <p>...content after script...</p> See the Pen JS-p2-script-async-defer -ex1 by Hsoub (@Hsoub) on CodePen. هناك طرق للالتفاف حول تلك المشاكل. على سبيل المثال، يمكننا وضع السكربت في أسفل الصفحة. وبذلك يمكنه الاطلاع على العناصر التي فوقه، ولا تُحبس محتويات الصفحة عن الظهور: <body> ...all content is above the script... <script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> </body> See the Pen JS-p2-script-async-defer -ex2 by Hsoub (@Hsoub) on CodePen. لكنّ هذه الطريقة بعيدة عن كونها مثاليّة. على سبيل المثال، لا يعلم المتصفّحُ بوجود السكربت (ولا يمكنه البدء بتحميله) إلا بعد تحميل كامل مستند HTML. بالنسبة لمستندات HTML الطويلة، قد يكون هناك تأخّر ملحوظ. لا تُلاحظ هذه الأمور لمن يملكون اتصالات سريعة جدا، لكن لا يزال الكثير من الناس حول العالم يملكون اتصالات بطيئة بالانترنت ويستخدمون اتصالات انترنت محمولة أبعد ما تكون عن المثاليّة. لحسن الحظّ، هناك سمتان للعنصر <script> يمكنها أن تحلّ لنا هذه المشكلة: defer وasync. السمة defer تطلب السمة defer من المتصفّح أن لا ينتظر السكربت. بل يواصل المتصفّح معالجة HTML وبناء DOM. يُحمّل السكربت في "الخلفيّة"، ثمّ يشتغل عندما يتمّ بناء DOM كاملا. إليك نفس المثال الذي في الأعلى لكن باستخدام defer: <p>...content before script...</p> <script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> <!-- يكون مرئيّا على الفور --> <p>...content after script...</p> See the Pen JS-p2-script-async-defer -ex3 by Hsoub (@Hsoub) on CodePen. بعبارة أخرى: لا تحبس السكربتات التي لها defer الصفحة أبدا. دائما ما تُنفّذ السكربتات التي لها defer عندما يكون DOM جاهزا (لكن قبل الحدث DOMContentLoaded). يوضّح هذا المثال النقطة الثانية: <p>...content before scripts...</p> <script> document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!")); </script> <script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script> <p>...content after scripts...</p> See the Pen JS-p2-script-async-defer -ex4 by Hsoub (@Hsoub) on CodePen. يظهر محتوى الصفحة مباشرة. ينتظر معالج الحدث DOMContentLoaded السكربت المُرجأ (deferred). لا يشتغل حتى يُحمّل السكربت ويُنفّذ. تحافظ السكربتات المُرجأة على الترتيب بينها، تماما كما هو الحال مع السكربتات العاديّة لنفترض أنّ لدينا سكربتين مُرجأين: الطويل long.jsوثمّ القصير small.js: <script defer src="https://javascript.info/article/script-async-defer/long.js"></script> <script defer src="https://javascript.info/article/script-async-defer/small.js"></script> يبحث المتصفّح في الصفحة على السكربتات ويُحمّلها معًا، لتحسين الأداء. ففي المثال أعلاه يُحمّل السكربتان بشكل متوازي. من المُرجّح أن ينتهي small.js أوّلا. لكنّ السمة defer، بالإضافة إلى طلبها من المتصفّح "عدم الحبس"، تضمن المحافظة على الترتيب بين السكربتات. فحتى لو حُمّل small.js أوّلا، فإنّ عليه أن ينتظر ويُنفّذ بعد تنفيذ long.js. قد يكون ذلك مهمّا في الحالات التي نحتاج فيها إلى تحميل مكتبة جافاسكربت أوّلا ثمّ سكربت آخر يعتمد عليها. ملاحظة: السمة defer هي للسكربتات الخارجيّة فقط تُتجاهل السمة defer إذا لم يكن الوسم <script> له src. async السمة async هي مثل defer نوعا ما. هي أيضا تجعل السكربت غير حابس. لكنّ بينهما فروقات مهمّة في السلوك. تعني السمة async أن السكربت مستقلّ تماما: لا يحبس المتصفّح عند السكربتات التي لها async (مثل defer). لا تنتظر السكربتات الأخرى السكربتات التي لها async، ولا تنتظر السكربتات التي لها async السكربتات الأخرى. لا ينتظرDOMContentLoaded والسكربتات غير المتزامنة بعضها البعض: قد يقع DOMContentLoaded سواء قبل السكربت غير المتزامن (عندما ينتهي تحميل السكربت غير المتزامن قبل تمام الصفحة) …أو بعد السكربت غير المتزامن (إذا كان السكربت غير المتزامن قصيرا أو كان في ذاكرة التخزين المؤقّت HTTP). بعبارة أخرى، تُحمّل السكربتات التي لها async في الخلفيّة وتُنفّذ عندما تجهز. لا ينتظرها DOM ولا السكربتات الأخرى، ولا تنتظر هي بدورها أيّ شيء. سكربت مستقلّ تماما يُنفّذ عندما يجهز. من أبسط ما يكون، صحيح؟ إليك مثالا مشابها للذي رأينا مع defer: سكربتان long.js وsmall.js، لكن الآن لها async بدل defer. لا ينتظر أحدهما الآخر. أيّا كان الذي يُحمّل أوّلا (small.js على الأرجح) فإنّه يُنفّذ أوّلا: <p>...content before scripts...</p> <script> document.addEventListener('DOMContentLoaded', () => alert("DOM ready!")); </script> <script async src="https://javascript.info/article/script-async-defer/long.js"></script> <script async src="https://javascript.info/article/script-async-defer/small.js"></script> <p>...content after scripts...</p> See the Pen JS-p2-script-async-defer -ex5 by Hsoub (@Hsoub) on CodePen. يظهر محتوى الصفحة فورا: لا يحبسه async. قد يقع DOMContentLoaded إمّا قبل async أو بعد، لا ضمانات هنا. يأتي السكربت الأقصر small.js ثانيا، لكنّه على الأرجح يُحمّل قبل long.js، وبالتالي فإنّ small.js يُنفّذ أوّلا. رغم ذلك، من الممكن أن يُحمّل long.js أوّلا، إذا كان في ذاكرة التخزين المؤقّتة، فينُفّذ بذلك أوّلا. بعبارة أخرى، تُنفّذ السكربتات غير المتزامنة حسب ترتيب "أوّليّة التحميل". تفيد السكربتات غير المتزامنة كثيرا عندما ندمج سكربت طرف ثالث مستقلّ في الصفحة، كالعدّادات والإعلانات وما إلى ذلك، إذ لا تعتمد على سكربتاتنا، وليس على سكربتاتنا أن تنتظرها. <!-- هكذا عادة Google Analytics تضاف--> <script async src="https://google-analytics.com/analytics.js"></script> السكربتات الديناميكيّة هناك طريقة مهمّة أخرى لإضافة سكربت إلى الصفحة. يمكننا إنشاء سكربت وإلحاقه بالمستند ديناميكيّا باستخدام جافاسكربت: let script = document.createElement('script'); script.src = "/article/script-async-defer/long.js"; document.body.append(script); // (*) يبدأ تحميل السكربت بعد إلحاقه بالمستند مباشرة (*). تتصرّف السكربتات الديناميكيّة افتراضيًّا وكأنّها "غير متزامنة"، يعني ذلك: لا تنتظر أيّ شيء، ولاشيء ينتظرها. السكربت الذي يُحمّل أوّلا يُنفّذ أوّلا (حسب ترتيب "أوّليّة التحميل"). يمكن تغيير ذلك إذا وضعنا script.async=false صراحة. عندها تُنفّذ السكربتات حسب ترتيبها في المستند، تماما مثل defer. في هذا المثال، تضيف الدالّة loadScript(src) سكربتا وتضبط أيضا async على false. وبذلك يُنفّذ long.js أوّلا على الدوام (إذ قد أُضيف أوّلا): function loadScript(src) { let script = document.createElement('script'); script.src = src; script.async = false; document.body.append(script); } // async=false أوّلا لأنّ long.js يُنفّذ loadScript("/article/script-async-defer/long.js"); loadScript("/article/script-async-defer/small.js"); بدون script.async=false، تُنفّذ السكربتات حسب الترتيب الافتراضيّ "أوّليّة التحميل" (small.js أوّلا على الأرجح). ثانيا، كما هو الأمر مع defer، يصير الترتيب مهمّا عندما نريد تحميل مكتبة ما ومن ثمّ سكربت يعتمد عليها. الملخص لكلّ من async وdefer أمر مشترك: لا يحبس تحميلُ هذه السكربتات تصييرَ (rendering) الصفحة. وبذلك يمكن للمستخدم الاطلاع على الصفحة وقراءة محتواها فورا. لكنّ هناك أيضا فروقات جوهريّة بينها: الترتيب DOMContentLoaded async الترتيب وفق أوليّة التحميل. لا يهمّ ترتيبها في المستند -- الذي يُحمّل أوّلا يُنفّذ أوّلا لا علاقة له. قد يُحمّل السكربت ويُنفّذ قبل أن يتمّ تحميل كامل المستند. يحصل هذا إذا كانت السكربتات صغيرة أو مخزّنة في الذاكرة المؤقّتة، والمستند طويل كفاية. defer الترتيب وفق المستند (حسب موضعها في المستند). تُنفّذ السكربتات بعد الانتهاء من تحميل المستند وتفسيره (تنتظر عند الحاجة لذلك)، مباشرة قبل DOMContentLoaded. 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; } عمليّا، تُستخدم defer في السكربتات التي تحتاج DOM كاملا و/أو التي يكون ترتيب التنفيذ فيما بينها مهمّا. وتُستخدم async في السكربتات المستقلّة، مثل العدّادات والإعلانات. ويكون ترتيب تنفيذها غير مهمّ. تنبيه: يجب أن تكون الصفحة قابلة للاستخدام دون السكربتات يُرجى التنبه: إذا استخدمت defer أو async، فسيرى المستخدم الصفحة قبل تحميل السكربت. في هذه الحالة، قد لا تكون بعض المكوّنات الرسوميّة تهيّئت بعد. لا تنس وضع إشارة "التحميل" وتعطيل الأزرار التي لا تعمل بعد. دع المستخدم يرى بوضوح ما الذي يمكن فعله على الصفحة، وما الذي لم يتجهّز بعد. ترجمة -وبتصرف- للمقال Scripts: async, defer من سلسلة Browser: Document, Events, Interfaces لصاحبها Ilya Kantor1 نقطة
-
1 نقطة
-
من المفترض أن تكون الcharset خاصية مضمنة للوسم meta وهي تحدد نوع الترميز الذي يتم قراءة الملف به من قِبل المتصفح , والترميز المستخدم هنا هو ترميز utf-8 وهو ترميز standard (معياري) مستخدم من قِبل جميع المتصفحات, ويكون كتابة الوسم على الشاكلة الاتية <meta charset="UTF-8"> بالنسبة للخلفية تحتاج أن تعطيها التنسيقات التالية background-position: center; background-repeat: no-repeat; background-size: cover; التنسيق الأول يحدد أن تكون الصورة تظهر في المنتصف بمعنى أن لا تكون منحرف لليسار أو اليمين, التنسيق الثاني يحدد أن الصورة تظهر مرة واحدة لا يتم تكرارها, التنسيق الثالث يحدد أن الصورة يجب أن تغطي جميع أجزاء الصفحة1 نقطة
-
اجل يمكنك استدعاء هذه الملفات من خلال السطور التالية في بداية الملف {% include "_modal.html" %} ويمكنك ايضا اختصار مشروعك لابعد مدي من خلال انشاء base.html تكون بمثابة header ,footer والقالب الرئيس لمشروعك, ويمكنك اضافتها في إلي ملفاتك من خلال {% extends 'base.html' %} سيتم اضافة القالب الرئيسي في الملف الحالي ولن تكون بحاجة الي تضمين وسوم html الاساسية ,ولا header فقط ستكتب <div> الخاص بك1 نقطة
-
1 نقطة
-
أرجو إرفاق صورة للمشكلة1 نقطة
-
تستخدم ملفات GPX في عمليات تتبع الأنشطة ووضعها على الخريطة، ويتم تمثيلها على شكل XML ويمكن رسم هذه البيانات في المتصفح عن طريق استخدام مكتبة Leaflet. سيكون على شكل Track segment فيه Track Points <trkseg> <trkpt lat="47.644548" lon="-122.326897"> .. </trkpt> <trkpt lat="47.644548" lon="-122.326897"> ... </trkpt> <trkpt lat="47.644548" lon="-122.326897"> .. </trkpt> </trkseg> بعد عمل parse للملف عن طريق gpxparser نستخلص منه مصفوفة إحداثيات points ويمكن المرور عليهم بحلقة و نضعهم ضمن Polyline لرسم الخط. تضمين gpxparser يتم عن طريق: npm install --save gpxparser والتضمين في المكون: let gpxParser = require('gpxparser'); أو <script src="./js/GPXParser.js"></script> import 'leaflet/dist/leaflet.css' import React from 'react' import { MapContainer, Polyline, TileLayer } from 'react-leaflet' import 'gpxParser' from 'gpxparser'; const GpxMap = () => { // تعريف غرض للتعامل مع الملف const gpxData = new gpxParser() // نمرر المسار لللمف gpxData.parse(OUR_GPX_CONTENT) // جلب الإحداثيات كمصفوفة نقاط const positions = gpxData.tracks[0].points.map(p => [p.lat, p.lon] return ( // مكون الخريطة <MapContainer // تحديد المركز كأول نقطة center={positions[0]} zoom={9} scrollWheelZoom={false} > // صورة الخرطية // X Y Z إحداثيات الموقع المطلوب <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> // رسم ال Tracks <Polyline pathOptions={{ fillColor: 'red', color: 'blue' }} positions={positions} /> </MapContainer> ) } لمعلومات أكثر عن TileLayer1 نقطة
-
حاول جعل التاريخ إجبارياً عند تعبئة النوذج، و يمكنك ملئ قاعدة البيانات مكان التاريخ الفارغ حاليا بأي تاريخ تريده أو خلال قراءة البيانات إن لم يكن هنالك تاريخ ضع تاريخ افتراضي قبل العرض. أو استبدله بنص مثلا التاريخ غير معروف..1 نقطة
-
حاول ترقية المكتبة التالية: implementation "androidx.browser:browser:1.3.0 implementation 'com.google.firebase:firebase-auth:20.0.1' ثم توجه إلى google cloud console ثم مشروعك ثم APIs & Services ثم Dashboard وقم بتفعيل android device verification ثم أضف المفاتيح Add SHA-1 and SHA 256 in firebase project settings.(debug and release both) ثم قم بتحميل و بتعديل ملف google-services.json في مشروعك ثم قم بالتجريب و أطلعنا على المستجدات من فضلك.1 نقطة
-
بل سنتابع إيجاد حل للمشكلة.. يوجد أكثر من أمر لإنشاء keyStore $ keytool -genkey -v -keystore debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 على كل حال يمكنك من أندرويد استديو إنشائه من شريط الأدوات build ثم sign app هذه الخطوة مشروحة في التوثيق الرسمي من هنا: android app-signing من Build > Generate Signed Bundle/APK. اختر Generate Signed Bundle or APK dialog, ثم اختر select Android App Bundle or APK and click Next. إنشاء مفتاح Below the field for Key store path, click Create new. ثم أرفق بياناتك1 نقطة
-
لجعل ال Background color تلائم حجم النص يمكنك إضافة هذه الخاصية إلى h1 h1{ width: fit-content; } ولإضافة حواف نستخدم الخاصية التالية : border: blue solid 1px; //نحدد اللون والنوع والسماكة للحواف1 نقطة
-
أرجو إرفاق صورة و الملف بعد التعديل1 نقطة
-
1 نقطة
-
تُعدّ الأسماء واحدة من أساسيات البرمجة حيث تُستخدَم بالبرامج للإشارة إلى كثير من الأشياء المختلفة. لابُدّ من فهم قواعد تَسْمية الأشياء وطريقة اِستخدَام الأسماء لكي تَتَمكَّن من اِستخدَام الأشياء أي تحتاج في العموم إلى فهم كُلًا من صيغة (syntax) الأسماء ودلالتها (semantics). وفقًا لقواعد الصيغة (syntax rules) بالجافا، المُعرّفات (identifiers) واحدة من أبسط الأسماء، وتُستخدَم لتسمية كُلًا من الأصناف (classes) والمُتْغيِّرات (variables) والبرامج الفرعية (subroutines). يَتَكوَّن أي مُعرّف من متتالية تتألف من محرف (character) واحد أو أكثر بشَّرْط أن يَكُون أول محرف حرفًا أبجديًا (letter) أو شرطة سفلية (underscore) _، وأن تَكوُن المتتالية بالكامل مُكوَّنة من حروف أبجدية وأرقام (digits) وشرط سفلية فقط. اُنظر الأمثلة التالية لأسماء مُعرّفات (identifiers) صالحة: N n rate x15 quite_a_long_name HelloWorld لا يُسمَح للمسافات الفارغة (spaces) بأن تَكُون جزءًا من اسم أي مُعرّف، فمثلًا، في حين تستطيع اِستخدَام HelloWorld كاسم مُعرّف، لا يُمكِنك أن تَستخدِم "Hello World". تختلف كذلك الحروف الكبيرة عن الحروف الصغيرة فيما يَتَعلَّق بالتسمية أي أن الأسماء HelloWorld و helloworld و HELLOWORLD و hElloWorLD هي أسماء مُعرّفات مختلفة. بالإضافة إلى ذلك، لا يُمكِنك اِستخدَام الكلمات المحجوزة (reserved) المُخصَّصة لأغراض خاصة بالجافا كأسماء مُعرِّفات مثل الكلمات class و public و static و if و else و while وعشرات من الكلمات الآخرى. لاحِظ أن الكلمات المحجوزة ليست مُعرّفات فهي لا تُستخدَم كأسماء تُشير إلى أشياء. تَستخدِم الجافا محارف اليونيكود (Unicode character set) والتي تَتَضمَّن آلافًا من المحارف والحروف الأبجدية (alphabets) من مختلف اللغات. تُعدّ كثير من تلك المحارف بمثابة حروف أبجدية أو أرقام. سنَقْتصِر عمومًا على اِستخدَام المحارف المُتوفِّرة بأي لوحة مفاتيح إنجليزية عادية فقط. هنالك بعض الاصطلاحات والقواعد الإرشادية التي ينبغي اتباعها في العموم عند تَسْمية (naming) الأشياء. أولًا، تبدأ أسماء الأصناف (classes) بحروف كبيرة (upper case) بعكس أسماء كُلًا من المُتْغيِّرات (variables) والبرامج الفرعية (subroutines) والتي تبدأ عادةً بحروف صغيرة (lower case). يُساعِد الالتزام بذلك التَقْليد (convention) على تَجنُّب أي خلط مُحتمَل. ثانيًا، لا يَستخدِم غالبية مُبرمجي الجافا الشُرط السُفليّة (underscores) بالأسماء مع أن البعض يلجأ إلى اِستخدَامها ببداية أسماء أنواع مُحدَّدة من المُتْغيِّرات (variables). ثالثًا، إذا كان الاسم مُكوَّن من عدة كلمات مثل HelloWorld أو interestRate، فيُعتاد تَكْبير الحرف الأول (capitalize) من كل كلمة باستثناء الأولى فيما يُعرَف باسم نَمْط سنام الجمل (camelCase) حيث تُشبه الحروف الأبجدية الكبيرة (upper case) بمنتصف الاسم سنام الجمل. بالإضافة إلى الأسماء البسيطة (simple names)، قد تَكُون الأسماء أيضًا مُركَّبة من عدة أسماء بسيطة يَفصِل بين كُلًا منها نقطة (period) مثل الاسم System.out.println، وتُعرَف باسم "الأسماء المُؤهلة (qualified names)". لأن الجافا تَسمَح بتَضْمِين الأشياء ضِمْن بعضها البعض، فإن الاسم المُركَّب يَعمَل كمسار إلى شيء عبر واحد أو أكثر من مستويات الاحتواء (containment level)، فمثلًا يُشير الاسم System.out.println إلى شيء اسمه System يحتوي على شيء آخر اسمه out والذي بدوره يحتوي على شيء اسمه printn. المتغيرات (variables) تُستخدَم البرامج في العموم لمُعالجة البيانات المُخزَّنة بذاكرة الحاسوب. إذا كنا نُبرمج باِستخدَام لغة الآلة (machine language)، فإننا نَكُون مُضطرّين لاِستخدَام العنوان العددي (numerical memory address) لمَوضِع الذاكرة لكي نَتَمكَّن من الإشارة إلى البيانات المُخزَّنة به أما باللغات عالية المستوى (high-level language) مثل الجافا، فإننا في العموم نَستخدِم أسماءً وليس أعدادًا للإشارة إلى بيانات الذاكرة أي لا يحتاج المبرمج إلى ما هو أكثر من تَذكُّر تلك الأسماء، والتي يُطلَق عليها اسم "المُتْغيِّرات (variables)"، وفي المقابل، يَتعقَب الحاسوب مواضع الذاكرة الفعليّة لتلك البيانات. لا يُعدّ المُتْغيِّر (variable) اسمًا للبيانات نفسها وإنما لمَوضِع الذاكرة الحامل لتلك البيانات أي أنه يَعمَل كصندوق أو كحاوي يُمكِنك أن تُخزِّن به بعض البيانات التي قد تحتاج إليها لاحقًا. يُشير أي مُتْغيِّر إذًا إلى صندوق بصورة مباشرة وإلى البيانات الموجودة بذلك الصندوق بصورة غير مباشرة. لمّا كانت البيانات المُخزَّنة بالصندوق قابلة للتَعْديل، فقد يشير مُتْغيِّر معين إلى بيانات مختلفة بلحظات مختلفة من تّنْفيذ البرنامج، ولكنه دائمًا ما سيُشير إلى نفس الصندوق. عندما نَستخدِم مُتْغيِّرًا ضِمْن برنامج، فإنه -وبحسب الطريقة التي اُستخدِم بها ذلك المُتْغيِّر- إما يُشير إلى صندوق وإما إلى البيانات المُخزَّنة بذلك الصندوق، وهو ما قد يَكُون مُربِكًا لبعض المبرمجين المبتدئين. سنَفْحَص أمثلة لكلا الحالتين بالأسفل. تُستخدَم تَعْليمَات الإسناد (assignment statement) بالجافا لتَخْزين بيانات معينة بمُتْغيِّر أي بصندوق، وتُكْتَب على النحو التالي: <variable> = <expression>; يُمثِل التعبير -بالأعلى- أي شيء طالما كان يُشير إلى قيمة بيانات أو يَحسِبها. عندما يُنفِّذ الحاسوب تَعْليمَة إسناد معينة، فإنه يُحصِّل (evaluates) قيمة ذلك التعبير ثم يُخزِّنها بالمُتْغيِّر . اُنظر تَعْليمَة الإسناد (assignment statement) التالية على سبيل المثال: rate = 0.07; بالتَعْليمَة السابقة، يُمثِل rate المُتْغيِّر أما القيمة 0.07 فتُمثِل التعبير . تُخزِّن تَعْليمَة الإسناد السابقة القيمة 0.07 بالمُتْغيِّر rate بحيث تحلّ تلك القيمة محلّ قيمته السابقة. سنَفْحَص الآن مثالًا آخر لتَعْليمَة إِسناد أكثر تعقيدًا بقليل، والتي سنحتاج إليها لاحقا ضِمْن البرنامج: interest = rate * principal; تُسنِد تَعْليمَة الإسناد (assignment statement) بالأعلى قيمة التعبير rate * principal إلى المُتْغيِّر interest. يُشير المحرف * الموجود بالتعبير إلى "عامِل حاصل الضرب (multiplication operator)" المسئول عن حساب حاصل ضرب rate في principal. لمّا كانت الأسماء rate و principal ضِمْن التعبير هي نفسها مُتْغيِّرات، يُحسَب حاصل ضرب القيم المُخزَّنة بتلك المُتْغيِّرات أي يُحسَب حاصل ضرب قيمة rate في قيمة principal ثم تُخزَّن الإجابة بالصندوق الذي يُشير إليه interest. نستطيع أن نقول إذًا أنه وفي العموم عندما نَستخدِم مُتْغيِّرًا ضِمْن تعبير (expression)، فإن القيمة المُخزَّنة بذلك المُتْغيِّر هي كل ما يُهِمّ، ويبدو المُتْغيِّر في تلك الحالة كما لو كان يُشير إلى بيانات الصندوق وليس الصندوق ذاته. في المقابل، عندما نَستخدِم مُتْغيِّرًا على الجانب الأيسر من تَعْليمَة إسناد، فإنه في تلك الحالة يُشير إلى الصندوق ذاته المُمثِل لذلك المُتْغيِّر. تَعْليمَات الإِسناد ليست تَعْليمَات خبرية، وإنما هي بمثابة أوامر يُنفِّذها الحاسوب بأوقات محددة. لنَفْترِض مثلً أن برنامجًا معينًا يُنفِّذ التَعْليمَة rate = 0.07; ثم يُنفِّذ التَعْليمَة interest = rate * principal; بوقت لاحق ضِمْن البرنامج، هل يُمكِننا ببساطة أن نَدعِي أننا قد ضربنا principal بالقيمة 0.07؟ في الواقع لا! لأن قيمة rate قد تَتَغيَّر في أي لحظة بواسطة تَعْليمَة إِسناد آخرى. يختلف معنى تَعْليمَة الإِسناد تمامًا عن معنى أي معادلة رياضية على الرغم من أن كليهما يَستخدِم الرمز =. الأنواع (types) يُمكِن لأي مُتْغيِّر (variable) بالجافا أن يَحمِل نوعًا (type) واحدًا فقط من البيانات وليس أي نوع آخر. إذا حاولت أن تُسنِد (assigning) قيمة من نوع مختلف عن نوع مُتْغيِّر معين إلى ذلك المُتْغيِّر، فسيُعدّ ذلك انتهاكًا لتلك القاعدة، وسيُبلِّغ عنه المُصرِّف (compiler) على أساس كَوْنه خطأ في بناء الجملة (syntax error). نقول إذًا أن الجافا هي لغة صارمة في تَحْديد النوع (strongly typed language). تُوفِر الجافا 8 أنواع أساسية (primitive types) مَبنية مُسْبَقًا هي كالتالي: byte و short و int و long و float و double و char و boolean. تَحمِل الأنواع الأربعة الأولى أعدادًا صحيحة (integers) مثل 17 و -38477 و 0، ولكنها تختلف عن بعضها فيما يَتعَلَّق بنطاق الأعداد الصحيحة التي يُمكِن لكل نوع منها حَمْله. في المقابل، يَحمِل النوعان float و double أعدادًا حقيقية (real numbers) مثل 3.6 و -145.99، ويختلفان عن بعضهما بكُلًا من نطاق الأعداد المسموح به وبدقة العَدَد. تستطيع المُتْغيِّرات من النوع char أن تَحمِل إحدى محارف اليونيكود (Unicode character set) أما المُتْغيِّرات من النوع boolean فتَحمِل إما القيمة المنطقية true أو القيمة false. تُمثَل جميع قيم البيانات بذاكرة الحاسوب بهيئة عدد من النظام الثُنائي (binary number) عبارة عن سِلسِلة نصية (string) مُكوَّنة من الرقمين صفر وواحد. يُطلَق اسم "بت (bit)" على كُلًا منها بينما يُطلَق اسم "بايت (byte)" على أي سِلسِلة نصية مُكوَّنة من ٨ بتات، وتُقاس الذاكرة عادةً بتلك الوحدة. يُشير النوع byte إلى بايت واحد فقط بالذاكرة أي تَحمِل المُتْغيِّرات (variable) من النوع byte سِلسِلة نصية من ٨ بتات يُمكِنها أن تُمثِل ٢٥٦ عددًا صحيحًا (٢ مرفوعة للأس ٨) أي قد تُمثِل أي عدد صحيح بين -١٢٨ و ١٢٧. أما بالنسبة للأنواع الصحيحة (integer) الآخرى: short يُمثِل ٢ بايت (١٦ بت)، وتَحمِل المُتْغيِّرات من النوع short قيم ضِمْن نطاق يتراوح من -32768 إلى 32767. int يُمثِل ٤ بايت (٣٢ بت)، وتَحمِل المُتْغيِّرات من النوع int قيم تتراوح من -2147483648 إلى 2147483647. long يُمثِل ٨ بايت (٦٤ بت)، وتَحمِل المُتْغيِّرات من النوع long قيم بنطاق يتراوح من -9223372036854775808 إلى 9223372036854775807. لا تحتاج إلى تَذكُّر كل تلك الأعداد بالأعلى، فالغرض منها فقط هو إِعطائك فكرة عن حجم الأعداد الصحيحة التي يُمكِن لكل نوع أن يَحمِلها. وفي العموم عادةً ما يُستخدَم النوع int لتمثيل أي قيمة عددية صحيحة لأنه جيد كفاية لغالبية الأغراض. أما النوع float فيَستخدِم طريقة ترميز (encoding) قياسية لتمثيل الأعداد الحقيقية (real) بمساحة ٤ بايت من الذاكرة، ويُمكِنه أن يَحمِل قيمة قصوى تَصِل إلى ١٠ مرفوعة للأس ٣٨. علاوة على ذلك، يُمكِن للقيم من النوع float أن تَشتمِل على ٧ أرقام معنوية (significant digits) بحد أقصى. لابُدّ إذًا من تَقْرِيب كُلًا من العددين 32.3989231134 و 32.3989234399 إلى العَدَد 32.398923 حتى نَتَمكَّن من تَخْزِينه بمُتْغيِّر من النوع float. في المقابل، يُمثِل النوع double الأعداد الحقيقية بمساحة ٨ بايت، لذا يُمكِنه أن يَحمِل قيمة قصوى تَصِل إلى ١٠ مرفوعة للأس ٣٠٨، وقد يَشتمِل على ١٥ رقم معنوي (significant digit). في العموم عادةً ما يُستخدَم النوع double لتمثيل قيم الأعداد الحقيقية. أما المُتْغيِّرات من النوع char فتَحتلّ مساحة ٢ بايت من الذاكرة، وتَحمِل محرفًا وحيدًا مثل "A" أو "*" أو "x" أو مسافة فارغة (space) أو محرفًا خاصًا مثل المحرف "tab" أو محرف "العودة إلى بداية السطر (carriage return)" أو إحدى محارف اليونيكود (Unicode character) بلغات مختلفة. يَستخدِم النوع char ترميزًا عدديًا صحيحًا (integer code number) مُكوَّن من ١٦ بت لتمثيل أي محرف، لذلك فإنه قيمه تُعدّ مرتبطة نوعًا ما بقيم الأعداد الصحيحة (integer)، وسنرى لاحقًا أنه يُمكِننا حتى اِستخدَامها كما لو كانت أعدادًا صحيحة بمواقف معينة. لمّا كان الحاسوب يَستخدِم عددًا مُحدّدًا ومُتناهيًا (finite) من البتات لتمثيل قيمة أي نوع أساسي (primitive type)، فلا يُمكِن إذًا لقيم النوع int أن تُمثِل أي عدد صحيح عشوائي بل لابُدّ أن يَقَع العََدَد الصحيح ضِمْن نطاق مُحدّد ومُتناهي من القيم. بالمثل، يُمكِن للمُتْغيِّرات من النوعين float و double أن تَحمِل قيمًا محددة فقط أي أنها ليست أعدادًا حقيقية فعليًا بالمفهوم الرياضي. فمثلًا، لأن الثابت الرياضي π يَتَطلّب عددًا لامُتناهيًا من الأرقام العشرية لتمثيله، فلابُدّ من تقريبه أولًا قبل تَخْزِينه بمُتْغيِّر من النوع float أو النوع double. بالمثل، لابُدّ من تقريب الأعداد البسيطة مثل 1/3 قبل تَخْزِينها بمُتْغيِّرات من النوع float و double. القيم مصنَّفة النوع (literals) تُخزَّن أي بيانات بالحاسوب كمتتالية من البتات (bits). بالنسبة للقيم الثابتة (constant values)، فإنها تُمثَل باستخدَام ما يُعرَف باسم "القيم مُصنَّفة النوع (literals)"، والتي هي عبارة عن شيء يُمكِنك كتابته لتمثيل قيمة أي يُمكِن عدّه اسمًا لقيمة ثابتة. لكتابة قيمة من النوع char على سبيل المثال، لابُدّ من إحاطتها بعلامتي اقتباس مُفرّدتين مثل 'A' و '*' و 'x'. يُشكِّل المحرف وعلامتي الاقتباس معًا "قيمة مُصنَّفة النوع (literal)" من النوع char. بدون علامتي الاقتباس، سيُمثِل A مُعرّفًا (identifier) أما * فستُمثِل عامل حاصل الضرب (multiplication operator). إذا كنت تريد أن تُخزِّن المحرف A بمُتْغيِّر ch من النوع char، فيُمكِنك أن تَستخدِم تَعْليمَة الإِسناد (assignment statement) التالية. لا تُعدّ علامتي الاقتباس جزءًا من القيمة ولا تُخزَّن بالمُتْغيِّر (variable)، فهي فقط نَمْط لتسمية الثوابت المحرفية (character constant): ch = 'A'; تَستخدِم بعض المحارف الخاصة قيمًا خاصة مُصنَّفة النوع (literals) تَتَضمَّن خطًا مائلًا عكسيًا (backslash) يَعمَل كمحرف هروب (escape character)، فمثلًا، يُمثِل '\t' المحرف "tab"، ويُمثِل '\r' محرف العودة إلى بداية السطر (carriage return)، ويُمثِل '\n' محرف إضافة سطر جديد، ويُمثِل ''\' علامة اقتباس مُفردة، ويُمثِل '\\' محرف خط مائل عكسي (backslash). يَتَكوَّن كُلًا منها من محرفين إلى جانب علامتي الاقتباس، ومع ذلك فإنه يُشير إلى محرف واحد فقط، فمثلًا تُشير القيمة مُصنَّفة النوع '\t' إلى محرف "tab" واحد فقط. قد تَكُون القيم العَدَدية مُصنَّفة النوع (Numeric literals) أعقد قليلًا مما تَتَوقَّع. هنالك بالطبع أمثلة واضحة مثل 317 و 17.42، ولكن هنالك احتمالات وطرائق أخرى لتمثيل الأعداد بلغة الجافا. أولًا، قد تُستخدَم صيغة أُسية (exponential form) لتمثيل الأعداد الحقيقية مثل 1.3e12 أو 12.3737e-108 حيث "e12" و "e-108" هي أسس للعدد ١٠ أي يُكافئ العدد 1.3e12 حاصل ضرب ١,٣ في ١٢١٠ أما العدد 12.3737e-108 فيُكافئ حاصل ضرب ١٢,٣٧٣٧ في ١٠-١٠٨. عادةً ما تُستخدَم هذه الصيغة للتعبير عن الأعداد الصغيرة جدًا أو الكبيرة جدًا. ثانيًا، قد تحتوي بعض القيم العددية مُصنَّفة النوع (numeric literal) على علامة عشرية (decimal point) أو على أس (exponential)، وتُعدّ عندها قيمًا من النوع double أتوماتيكيًا أما إذا أردت أن تُنشِيء قيمة مُصنَّفة من النوع float، فينبغي أن تضيف الحرف "f" أو "F" إلى نهاية العدد، فتُمثِل "1.2F" مثلًا قيمة من النوع float تُساوِي 1.2. ولأن الجافا لا تَسمَح بإسناد (assign) قيمة من النوع double لمُتْغيِّر من النوع float، فستُواجهك رسالة خطأ سخيفة نوعًا ما إذا حاولت أن تقوم بشيء مثل x = 1.2; عندما يَكُون x مُتْغيِّر من النوع float، وعندها يَنبغي أن تُضيف الحرف "F" بالنهاية كالتالي x = 1.2F;. حاول عمومًا أن تَستخدِم النوع double لتمثيل الأعداد الحقيقية بالبرامج الخاصة بك. أما بالنسبة لقيم الأعداد الصحيحة مُصنَّفة النوع (integer literals)، فهنالك أعداد صحيحة عادية مثل 177777 و -32 والتي إما أن تَكُون من النوع byte أو النوع short أو النوع int بحسب حجمها. في المقابل، يُمكِنك أن تُنشِيء قيمة مُصنَّفة النوع (literal) من النوع long بإضافة الحرف "L" بنهاية العدد مثل 17L أو 728476874368L. تَسمَح الجافا أيضًا باستخدام قيم عددية ثنائية (binary) أو ثُمانيّة (octal) أو ست عشريّة (hexadecimal) مُصنَّفة النوع. لن نناقش أيًا منها على نحو تفصيلي وإنما سنَكتفِي بالنقاط التالية: أولًا، تَقْتصِر الأعداد الثُمانيّة (octal numbers) على الأرقام العشرية من ٠ إلى ٧ فقط، وتبدأ أي قيمة ثُمانيّة مُصنَّفة النوع (octal literal) بصفر مثل القيمة 045 والتي تُمثِل العدد 37 وليس العدد 45. نادرًا ما تُستخدَم الأعداد الثُمانيّة ومع ذلك تذكَّر على الأقل أنها تبدأ بصفر. ثانيًا، تَتَكوَّن الأعداد الست عشرية (Hexadecimal numbers) من ١٦ رقم هي الأرقام العشريّة العادية من ٠ إلى ٩ بالإضافة إلى الحروف A و B و C و D و E و F وبحيث تُمثِل تلك الحروف القيم من ١٠ إلى ١٥ على التوالي. لاحِظ أن الحروف الصغيرة (lower case letters) من a وحتى f لها نفس قيمة نظيراتها من الحروف الكبيرة (upper case). تبدأ القيم الست عشرية مُصنَّفة النوع (hexadecimal literal) بالجافا باستخدام 0x أو 0X مثل 0x45 أو 0xFF7A. وأخيرًا، تبدأ القيم الثنائية مُصنَّفة النوع (binary literals) باستخدام 0b أو 0B، وتَتَكوَّن من الرقمين ٠ و ١ فقط مثل 0b10110. قد تحتوي القيم العددية مُصنَّفة النوع (numeric literals) على محرف شرطة سفلية (underscores) "_" لفَصْل مجموعات الأرقام (digits) عن بعضها وبدون أي قاعدة فيما يَتَعلَّق بعَدَد الأرقام ضِمْن كل مجموعة. يُمكِننا مثلًا كتابة الثابت العددي ٢ بليون بهيئة 2_000_000_000 بدلًا من 2000000000 مما يُسهِل من قراءة العدد. تَبرُز أهمية الشُرَط السفليّة على نحو أكبر عند اِستخدَامها بالأعداد الثنائية (binary numbers) الطويلة مثل 0b1010_1100_1011. تُستخدَم الأعداد الست عشرية (hexadecimal numbers) أيضًا كقيم محرفية مُصنَّفة النوع (character literals) لتمثيل بعض محارف اليونيكود (unicode characters). تَتَكوَّن أي قيمة يونيكود مُصنَّفة النوع (unicode literal) من \u متبوعة بأربعة أرقام ست عشريّة. تُمثِل مثلًا القيمة المحرفية مُصنَّفة النوع '\u00E9' محرف يونيكود هو الحرف "é". أخيرًا، بالنسبة للنوع boolean، فتُستخدَم القيمتين true و false كقيم مُصنَّفة النوع (literal)، وتُكْتَب على نفس هذه الهيئة بدون علامتي اقتباس مع أنها تُمثِل قيم لا مُتْغيِّرات (variables). عادةً ما تُستخدَم القيم المنطقية كقيم للتعبيرات الشرطية (conditional expressions) كالمثال التالي: rate > 0.05 يُعيد التعبير المنطقي (boolean-valued expressions) السابق قيمة إما تُساوِي true إذا كانت قيمة المُتْغيِّر rate أكبر من 0.05 أو تُساوِي false إذا كنت قيمته أقل من أو تُساوِي 0.05. تُستخدَم التعبيرات المنطقية بكثرة ضِمْن بُنى التَحكُّم (control structure) كما سنرى بالفصل الثالث. إلى جانب ذلك، يُمكِننا أيضًا أن نُسنِد القيم المنطقية إلى مُتْغيِّرات من النوع boolean. فمثلًا إذا كان test مُتْغيِّرًا من النوع boolean فيُمكننا كتابة تَعْليمَتي الإِسْناد (assignment statement) التاليتين: test = true; test = rate > 0.05; السلاسل النصية (strings) والسلاسل النصية المجردة (string literals) إلى جانب الأنواع الأساسية (primitive types)، تُوفِّر الجافا أنواعًا آخرى قيمها عبارة عن كائنات (objects) وليس قيم أساسية (primitive). ليس هذا وقتًا مناسبًا بالطبع للحديث عن الكائنات (objects)، ولكننا مُضطرّون للحديث عن النوع String لأهميته. هو ببساطة نوع كائني (object type) مُعرَّف مُسْبَقًا تُوفِّره الجافا لتمثيل السَلاسِل النصية (strings). لا يَقَع ذلك النوع ضِمْن الأنواع الأساسية (primitive)، بل هو في الواقع اسم صَنْف (class)، وهو ما سنعود للحديث عنه بالقسم التالي. تَتَكوَّن القيم من النوع String من متتالية من المحارف مثل السِلسِلة النصية المُجرّدة "Hello World!" التي تَعرَضنا لها بقسم سابق. لابُدّ من إحاطة النص بعلامتي اقتباس مزدوجتين حيث تُعدّ كلتاهما جزءًا من السِلسِلة النصية المجرّدة (string literal)، ولكنهما مع ذلك ليسا جزءًا من قيمتها الفعليّة والتي تَقْتصِر على المحارف بين علامتي الاقتباس. قد تحتوي السِلسِلة النصية من النوع String على أي عدد من المحارف بما في ذلك الصفر، وعندها تَكُون بمثابة سِلسِلة نصية فارغة (empty string) تُمثَل بالسِلسِلة النصية المُجرّدة "" أي مُكوَّنة من علامتي اقتباس مزدوجتين فارغتين. انتبه جيدًا للفارق بين علامتي اقتباس فرديتين وآخريتين مزدوجتين حيث تُستخدَم الأولى مع القيم المحرفية مُصنَّفة النوع (char literals) أما الثانية فتُستخدَم مع السَلاسِل النصية المُجرّدة (String literals)، فمثلًا، لاحظ الفرق بين السِلسِلة النصية "A" والحرف 'A'. قد تَتَضمَّن السَلاسِل النصية المُجرّدة (string literal) محارفًا خاصة، ويُستخدم عندها خطًا مائلًا عكسيًا (backslash) لتمثيلها. ضِمْن هذا السياق، تُعدّ علامة الاقتباس المزدوجة محرفًا خاصًا، فإذا كان لدينا القيمة النصية التالية (string value): I said, "Are you listening!" يُمكِننا إذًا تمثيلها باستخدام سِلسِلة نصية مُجرّدة (string literal) كالتالي مع محرف "إضافة سطر جديد (linefeed)" بالنهاية: "I said, \"Are you listening!\"\n" يُمكِنك أيضًا أن تَستخدِم \t و \r و \\ وكذلك متتاليات اليونيكود مثل \u00E9 لتمثيل محارف خاصة آخرى ضمن السِلاسِل النصية المُجرّدة (string literals). المتغيرات بالبرامج ينبغي دائمًا أن تُصرِّح (declare) عن أي مُتْغيِّر (variable) قبل اِستخدَامه. تُستخدَم تَعْليمَة التَصْرِيح عن المُتْغيِّرات (variable declaration statement) للإعلان عن مُتْغيِّر واحد أو أكثر وكذلك تَخْصيص اسم لكُلًا منها. عندما يُنفِّذ الحاسوب تَعْليمَة تَصْرِيح عن مُتْغيِّر، فإنه يَحجِز له مساحة من الذاكرة كما يَربُط اسمه بتلك المساحة. تُكْتَب تَعْليمَة التَصْريح عن مُتْغيِّر بالصياغة التالية: <type-name> <variable-name-or-names>; تُمثِل اسم مُتْغيِّر واحد أو قائمة من أسماء المُتْغيِّرات يَفصِل بينها فاصلة (comma). يُمكِن لتَعْليمَات التَصْرِيح عن المُتْغيِّرات أن تَكُون أكثر تعقيدًا من ذلك كما سنرى لاحقًا. يُفضَّل عمومًا التَصْرِيح (declare) عن مُتْغيِّر واحد فقط بكل تَعْليمَة تَصْرِيح (declaration statement) إلا لو كانت تلك المُتْغيِّرات مُرتبطَة ببعضها. اُنظر الأمثلة التالية: int numberOfStudents; String name; double x, y; boolean isFinished; char firstInitial, middleInitial, lastInitial; يُفضَّل أيضًا إضافة بعض التعليقات (comment) لكل تَعْليمَة تَصْرِيح عن مُتْغيِّر (variable declaration)؛ لشرح غرضه بالبرنامج أو لتَوفِير بعض المعلومات التي يُمكِنها أن تُساعِد أي مبرمج آخر يَرغَب بقراءة البرنامج. اُنظر الأمثلة التالية: double principal; // مقدار النقود المُستثمَرَ double interestRate; // معدل الفائدة كقيمة وليس نسبة بهذا الفصل، سنَستخدِم المُتْغيِّرات (variables) المُصرَّح (declare) عنها داخل البرنامج الفرعي main() فقط. تُعرَف المُتغيرات المُصرَّح عنها داخل برنامج فرعي (subroutine) معين باسم "المُتْغيِّرات المحليّة (local)" وتَكُون مُتوفِّرة فقط داخل ذلك البرنامج الفرعي عند تَشْغِيله ولا يُمكِن الوصول إليها من خارجه نهائيًا. يُمكِنك أن تُصرِّح عن مُتْغيِّرات بأي مكان داخل البرنامج الفرعي بشَّرْط ألا تَستخدِم أبدًا مُتْغيِّر معين بدون أن تُصرِّح عنه أولًا. يُفضِّل بعض الأشخاص التَصْريح (declare) عن جميع المُتْغيِّرات ببداية البرنامج الفرعي بينما يُفضِّل آخرون إرجاء التَصْرِيح عن المُتْغيِّرات إلى حين الحاجة لاِستخدَامها مباشرةً. يُفضِّل الكاتب التَصْرِيح عن المُتْغيِّرات الهامة والمحورية ببداية البرنامج الفرعي مع إضافة تعليق (comment) على كل تَعْليمَة تَصْرِيح منها يشرح الغرض من المُتْغيِّر، وفي المقابل يرى الكاتب أنه من الممكن إرجاء التَصْرِيح عن المُتْغيِّرات غَيْر المُهِمّة إلى حين الحاجة لاِستخدَامها لأول مرة. يَستخدِم البرنامج التالي بعض المُتْغيِّرات (variables) وتَعْليمَات الإِسْناد (assignment statements): public class Interest { public static void main(String[] args) { /* صرح عن المتغيرات */ double principal; // قيمة الاستثمار double rate; // معدل الفائدة السنوي double interest; // قيمة معدل الفائدة بعام واحد /* إحسب القيم المطلوبة */ principal = 17000; rate = 0.027; interest = principal * rate; // احسب معدل الفائدة principal = principal + interest; // احسب قيمة الاستثمار بعد عام واحد مع الفائدة /* اعرض النتائج */ System.out.print("The interest earned is $"); System.out.println(interest); System.out.print("The value of the investment after one year is $"); System.out.println(principal); } // نهاية main() } // نهاية الصنف Interest يَستدعِي البرنامج بالأعلى مجموعة من البرامج الفرعية (subroutine call) مثل System.out.print و System.out.println لعَرْض بعض المعلومات للمُستخدِم. بخلاف البرنامج الفرعي الأول، يَطبَع البرنامج الثاني محرف "إضافة سطر جديد (linefeed)" بَعْد عَرْض المعلومة ذاتها، فمثلًا، تَطبَع تَعْليمَة الاستدعاء System.out.println(interest); قيمة المُتْغيِّر interest بنفس سطر السِلسِلة النصية المعروضة بواسطة تَعْليمَة الاستدعاء System.out.print السابقة. يُفْترَض للقيمة المطلوب طباعتها باِستخدَام System.out.print أو System.out.println أن تُكْتَب بَعْد اسم البرنامج الفرعي (subroutine) داخل أقواس ويُطلق عليها اسم المُعامِل (parameter). قد يَستقبِل أي برنامج فرعي معاملًا واحدًا أو أكثر لتوفير بعض المعلومات التي يَحتاج إليها ليُنفِّذ المُهِمّة المُوكَلة إليه. تُكْتَب تلك المُعامِلات (parameters) عند استدعاء البرنامج الفرعي (subroutine call) بَعْد اسمه داخل أقواس. في المقابل، قد لا يَستقبِل برنامج فرعي (subroutine) أي مُعامِلات، وفي تلك الحالة لابُدّ أن يُكْتَب زوج من الأقواس الفارغة بَعْد اسم البرنامج الفرعي بتَعْليمَة استدعائه. ملحوظة: جميع ملفات الشيفرة المصدرية (source code) للأمثلة التوضيحية بالكتاب متاحة بنسخة الكتاب المتوفرة عبر الانترنت كما أنها مُضمَّنة بمجلد اسمه "source" بالنسخة الأرشيفية لموقع الإنترنت. يمكنك مثلًا العثور على ملف الشيفرة المصدرية للبرنامج Interest بالملف Interest.java الموجود بمجلد فرعي اسمه "chapter2" داخل المجلد "source". ترجمة -بتصرّف- للقسم Section 2: Variables and the Primitive Types من فصل Chapter 2: Programming in the Small I: Names and Things من كتاب Introduction to Programming Using Java.1 نقطة
-
هذه السلسلة عبارة عن ترجمة لكتاب Dive Into HTML5 لمؤلفه Mark Pilgrim والتي سنتعلم من خلالها أساسيات HTML5 وكيفية الإنتقال إليها من نسخ HTML أقدم مع مراعاة دعم المتصفحات المختلفة. قبل البدء باستخدام HTML5 سنتطرق في هذا الدرس الأول إلى خمسة أشياء عليك معرفتها حول HTML5. 1. HTML5 ليست شيئا واحدا كبيرا ربما تتساءل: "كيف يمكنني البدء باستعمال HTML5 إن لم تكن تدعمها المتصفحات القديمة؟" لكن السؤال نفسه سيُضلِّلُكَ، HTML5 ليست شيئًا واحدًا كبيرًا، وإنما مجموعة من الميزات المنفصلة عن بعضها، أي أنَّك لن تحاول اكتشاف "دعم HTML5" في المتصفح، لأن ذلك غير منطقي؛ وإنما يمكنك اكتشاف الدعم للمزايا المختلفة مثل التخزين المحلي، أو عرض الفيديو، أو الحصول على الموقع الجغرافي. ربما تظن أنَّ HTML هي مجموعة من الوسوم وتلك الأقواس التي تشبه الزاوية… إن هذا جزءٌ مهمٌ منها، لكنه لا يمثلها كلها. إذ تُعرِّف مواصفات HTML5 كيف تتفاعل تلك الوسوم مع لغة JavaScript وذلك عبر ما يُعرَف بالمصطلح "DOM" (اختصار للعبارة Document Object Model). فلا تُعرِّف HTML وسمًا باسم <video> فقط، وإنما هنالك واجهة برمجية للتعامل مع كائنات الفيديو عبر DOM. يمكنك استعمال تلك الواجهة البرمجية (أي API) لكي تكتشف الدعم لمختلف صيغ الفيديو، ولكي تبدأ المقطع أو توقفه مؤقتًا، أو أن تكتم صوته، أو أن تعرف ما هو المقدار الذي نُزِّل (downloaded) من الفيديو، وكل شيءٍ آخر يلزمك لبناء تجربة مستخدم رائعة عند استعمال وسم <video> لعرض المقاطع. 2. ليس عليك التخلي عن كل شيء شئت أم أبيت، لا تستطيع أن تنكر أنَّ HTML 4 هي أنجح لغة توصيف (markup) على الإطلاق. بُنيَت HTML5 على هذا النجاح، وليس عليك أن تتخلى عن الشيفرات التي كتبتها، وليس عليك إعادة تعلم أشياء تعرفها من قبل، فإن كان تطبيقك يعمل البارحة باستخدام HTML 4، فسيبقى يعمل اليوم في عصر HTML5. لكن إن أتيت لتحسين تطبيق الويب الخاص بك، فقد أتيت إلى المكان الصحيح. هذا مثالٌ واقعي: تدعم HTML5 كل عناصر النماذج (forms) في HTML 4، لكنها تتضمن عناصر جديدة أخرى. كنا ننتظر إضافة بعض تلك العناصر بفارغ الصبر، مثل المزلاج (slider) ومنتقي التاريخ (date picker)؛ بعضها الآخر ذو ميزاتٍ خفية. فحقل email مثَلَهُ كمَثَلِ حقل الإدخال النصي العادي، إلا أنَّ متصفحات الهواتف الذكية ستخصص لوحة المفاتيح الظاهرة على الشاشة لتسهيل كتابة عناوين البريد الإلكتروني. بعض المتصفحات القديمة لا تدعم حقل email وستعامله على أنَّه حقل نصي عادي، وسيبقى النموذج يعمل دون تعديلات في الشيفرة أو استخدام أساليب ملتوية عبر JavaScript. هذا يعني أنك تستطيع تحسين النماذج في صفحاتك اليوم، حتى لو كان زوارك يستعملون IE 6. 3. من السهل البدء باستعمالها يمكن أن يكون "التحديث" إلى HTML5 بسيطًا لدرجة أنَّ كل ما عليك فعله هو تعديل doctype، الذي يجب أن يكون أول سطر من كل صفحة HTML. تُعرِّف الإصدارات السابقة من HTML الكثير من أنواع doctype، وكان من الصعب اختيار النوع المناسب؛ لكن هنالك نوع doctype وحيد في HTML5: <!DOCTYPE html> لن يضر التحديث إلى نمط doctype في HTML5 شيفراتك المكتوبة، لأنَّ جميع الوسوم (tags) المُعرَّفة في HTML 4 ما تزال مدعومةً في HTML5، لكنها ستسمح لك باستعمال –والتحقق من صحة صياغة– العناصر التنظيمية الجديدة مثل <article> و <section> و <header> و <footer>، سنتحدّث عن هذه العناصر الجديدة في مقال قادم. 4. إنها تعمل بالفعل سواءً كنت تريد الرسم عبر canvas، أو تشغيل مقطع فيديو، أو تصميم نماذج أفضل، أو بناء تطبيقات ويب تعمل دون اتصال؛ فستجد أنَّ HTML5 مدعومةً دعمًا جيدًا، حيث يوجد دعمٌ لخاصية canvas في Firefox و Safari و Chrome و Opera ومتصفحات الهواتف الذكية وتشغيل الفيديو وتحديد المواقع والتخزين المحلي والمزيد. تدعم غوغل (في متصفحها) البيانات الوصفية الخاصة (microdata)، وحتى مايكروسوفت –المشهورة بتأخرها عن اللحاق بركب دعم المعايير القياسية– تدعم أغلبية ميزات HTML5 في متصفح "Internet Explorer 9". يتضمن كل درس من هذه السلسلة جداول لتوافقية المتصفحات الشهيرة للميزة المشروحة، ولكن الأهم من ذلك أنَّ كل درس يتضمن نقاشًا عن خياراتك إن كنت تحتاج إلى دعم المتصفحات القديمة. تم توفير ميزات في HTML5 مثل تحديد الموقع الجغرافي وتشغيل الفيديو في السابق عبر إضافات للمتصفح مثل Gears أو Flash. الميزات الأخرى، مثل canvas، تستطيع محاكاتها بشكلٍ تام باستعمال JavaScript. ستتعلم من خلال هذه السّلسلة (التي تقرأ الآن درسها الأول) كيف تستهدف المتصفحات ذات الدعم المدمج لتلك الميزات، دون أن تترك خلفك المتصفحات القديمة. 5. HTML5 ستبقى وستتطور اخترع "Tim Berners-Lee" الشبكة العنكبوتية في بدايات التسعينات من القرن الماضي، ثم أنشَأ جمعية W3C لكي تكون المرجع في معايير الويب، وهذا ما فعلته تلك الجمعية لأكثر من 20 عامًا. هذا ما قالته W3C عن مستقبل معايير الويب في تموز/يوليو عام 2009: ستبقى HTML5 في المستقبل، لنبدأ بتعلمها. ترجمة -وبتصرّف- لفصل Introduction من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: نظرة على تاريخ HTML - الجزء الأول النسخة الكاملة من كتاب نحو فهم أعمق لتقنيات HTML51 نقطة
-
العلاقتين Composition و Aggregation في البرمجة غرضية التوجه OOP ليستا علاقة وراثة بل هما علاقة احتواء بين غرضين أو كائنين object (كائن يحوي كائن). لا يختلف المفهومان عن بعضهما بطريقة التطبيق إنما الاختلاف من ناحية ماهية الكائنات وعلاقتها ببعضها. علاقة Composition يكون فيها الصف class الذي يحوي على الكائنات objects مسؤولًا عن إنشاء بقية الكائنات التي يحويها ومسؤول أيضًا عن عملية إنهائها أي أنه ليتم إنشاء الصف الذي يحوي الكائنات composition يجب أولًا إنشاء جميع الكائنات التي يحويها وهذه هي علاقة composition وهي علاقة قوية Strong Relationship. مثال لعلاقة Composition (وهو الأكثر استخدامًا لتوضيح هذه العلاقة) هو مثال السيارة فمثلًا السيارة تحتوي على مجموعة كائنات مثل العجلات والمحرك..الخ هل يمكن تصنيع سيارة أوجعلها تتحرك من دون هذه الأغراض؟ بالطبع لا إذًا تصنيع السيارة مرتبط بتصنيع الكائنات التي تحويها كالعجلات والمحرك وأيضًا عطل السيارة مرتبط بعطل الكائنات التي تحويها. أما علاقة Aggregation هي عكس علاقة Composition أي أن الصف الكبير غير مسؤول عن إنشاء أو إنهاء الكائنات التي يحويها. أي أنه إن تم إنهاء الصف الذي يحوي الكائنات فلن تتأثر بذلك وتبقى تعمل. مثال على ذلك علاقة الشركة مع موظفيها هي علاقة Aggregation في حال تم إلغاء قسم في الشركة فإن الموظفين الذين يعملون بالقسم لن يتأثروا بذلك أي لن يتم إلغاء وجودهم وربما ينتقلون لقسم آخر أو لشركة أخرى. يمكن تعريف علاقة Composition بـ "owns" أي A "owns" B وعلاقة Aggregation بـ "uses" أي A "uses" B ما الفرق بين مفهومي Composition و Aggregation في OOP؟1 نقطة
-
إذا كنت تظنّ أنّ WPEngine أو أيّة شركة عالميّة أخرى ناجحة، قد انتقلت من مجرّد فكرة عابرة إلى قمة النّجاح والتّفوّق في لمح البصر وبدون أيّ عناء أو تخطيط، وأنّك لن تتمكّن – في حال من الأحوال – من تحصيل نجاحٍ مشابه لأفكارٍ مشابهة من دون أن تحظى بنفس القدر من الحظّ والظّروف المواتية، إذا كنتَ تظنّ ذلك فأبشّرك من البداية، أنت مخطئ. صحيح أن الإلهام والحظّ الجيّد يلعبان دورًا في البدايات، لكنّ نقل فكرة لامعة من عالم الأفكار إلى عالم الأعمال حيث يأتي أشخاص غرباء إلى موقع ما وينفقون فيه أموالهم عن سابق إصرار وترصّد – كما يقال – هذا الأمر لا يحدث مصادفة أبدًا، بل يحدث عبر منهجيّة يمكنكَ تعلّمها وتطبيقها وحصد نتائجها أيضًا. هذه القصّة هي واحدة من القصص الكثيرة المشابهة التي يحكيها روّاد الشّركات النّاشئة، بدأتْ من فكرة مستلهمة من واقعي ومعاناتي الشّخصية، ظهرت في البداية على أنها عديمة النّفع وأنّ أحدًا لن يستفيد منها، لكنها تطوّرت مع الوقت، وأخذت أشكالًا متعددة خلال سلسلة من المراحل لتصل في النّهاية إلى مجموعة من الأفكار الرّائعة الّتي من الممكن تحويلها إلى مشاريع مدرّة للمال. إليك كيف تفعل ذلكأوّل فكرة شركة ناشئة بدأت بالعمل عليها كانت مستلهمةً من معاناة واجهتني في شركة Smart Bear التي كنتُ اعمل بها، ألا وهي كيف أقوم بمراقبة وقياس فعّاليّة ثلاثين حملة تسويقيّة تجري جميعها في آن واحد، تحديدًا إن كانت هذه الحملات تقليديّة كالمنشورات الورقيّة والإعلانات التلفزيونيّة وغيرها. في شركة Smart Bear قمتُ ببناء نظام خاصّ بي، وكان يعمل بشكل رائع ويقدّم بيانات دقيقةٌ للغاية، لدرجة أنه كان بإمكاني معرفة الوقت الذي تصلُ فيه إحدى المجلّات التي نقوم بالإعلان فيها إلى منازل القرّاء. هذه الدّقة لم يكن قد وصل إليها أحد في ذلك الوقت بالنسبة لإعلانات مشابهة. المشكلة الوحيدة أنّ بناء هذا النّظام استغرق منّي سنتين كاملتين، وكان غاية في التّعقيد، فهو مبنيّ على مجموعة متشابكة من الجداول والسكريبتات واستدعاءات واجهات برمجية API-calls وقواعد بيانات عديدة، وحتى أحصل على النتيجة المرجوّة كان يجب أن أجري العمليّة بطريقة معيّنة لا يستطيع فعلها أحدٌ سواي، وحتى أنا كنتُ أستعين بقائمة مهام طويلة حتى أتمكّن من تنفيذ الإجراء بالشّكل الصّحيح. ولا شكّ أنّك إن حاولتَ الاطّلاع على تفاصيل هذا النّظام – لنسمّه نظامًا تجاوزًا فهو أبعد ما يكون عن الانتظام – قد تستنتج في النهاية أنّه قد صمّم خصيصًا بحيث لا يتمكّن من استخدامه إلا الشّخص الذي قام بتصميمه، وربّما وجدتَه مجرّد أسلوبٍ غريب من أساليب تعذيب الذّات. لكنّه كان يعمل وبشكل مذهل! بعدما تركتُ عملي في تلك الشركة بحوالي سنة أجريت خلالها محادثات ولقاءات مع العديد من أصحاب الشّركات الناشئة حول مشاكل التّسويق لديهم، استنتجت لاحقًا أنّ جميعهم – وبدون استثناء – بحاجة إلى منصّة قياسِ الأداء التسويقيّ التي قمتُ باختراعها سابقًا. ظننت حينها أنّ اللّحظة المواتية قد حانت، تلك التي تدرك فيها أنّ فكرتك العابرة – المستلهمة أصلًا من معاناة عشتَها – إنّما هي فكرةٌ عبقريّة، وبكلّ تأكيد ستحلّ مشكلةً يواجهها الآلاف إن لم نقل الملايين من روّاد الأعمال. بل والأجمل من ذلك، كنتُ أعرف بالضّبط كيف أقوم بتنفيذها وتحويلها إلى منتج. إذًا ما الّذي أنتظره، عليّ أن أبدأ في الحال! دومًا تذكّر هذه المقولة: "إذا لم تعثر على عشرة أشخاص مستعدّين لأن يدفعوا لك بالفعل ثمن خدمتك، عندها تأكّد أنّ ما تقوم بممارسته هو محض هواية وليس عملًا ربحيًّا". وهذا بالضّبط ما قمتُ بفعله، بدأتُ بإجراء مقابلاتٍ مع جميع الأشخاص الذين تمكّنت من الوصول إليهم، ركّزت بحثي في البداية على مؤسّسي الشّركات الناشئة في مدينتي كونه من السّهل دعوتهم إلى وجبة غداء وإجراء محادثة ودّيّة. كما أنني كنتُ خلال الفترة الماضية أحرص على توسيع شبكة علاقاتي ومعارفي، لذلك كانت لديّ قائمةٌ جاهزة من الأشخاص لأتّصل بهم، فقد كنتُ أساعد روّاد الأعمال الذين أعرفهم إمّا عبر البريد الإلكترونيّ أو المقابلات الشّخصية، وحتى من خلال مساحات العمل التشاركيّ co-working spaces التي كنتُ أتواجد فيها، وجلّ ما فعلتُه لاحقًا أنّني قمتُ باسترداد المعروف الذي أسديتُه لهم. على الهامش، قد يقول قائل: "وماذا إن لم تكن لديّ شبكة علاقات واسعة كهذه؟" عندها ابحث في محيطك عن لقاءات أو ورشات عملٍ يحضرها أشخاص من المحتمل أن يستفيدوا من منتجك أو الخدمة التي تقدّمها. وماذا إن لم تكن أحداثٌ كهذه تجري قريبًا من محلّ سكنك؟ عندها عليك أن تحرص على إثبات حضورك إلكترونيًّا في المجتمعات الافتراضيّة والشبكات المهتمّة. وماذا إن لم تكن تريدُ فعل ذلك؟ عندئذ يمكنك أن تعثر على الأشخاص المهتمّين وتحاول جذبهم إلى موقعك من خلال إعلانات جوجل. وماذا إن لم تستطع فعل ذلك أو لا تريدُ إنفاق المال على هذا الأمر؟ إذًا فأنت تقول أنّك لا تريدُ أن تتواصل مع أحدٍ أيّ كان بأيّ شكل من الأشكال، ولا ترغب حتى أن تدفع أيّ مبلغ من المال. في هذه الحالة يا صديقي أظنّ أنّك لست مهيئًا بعد لتبدأ عملك الخاص أو لتقوم بأيّ عملٍ مشابه يتطلّب حضورًا اجتماعيًّا واحتكاكًا مباشرًا مع الآخرين. وبالعودة إلى قصّتنا، كان عليّ في نهاية هذه المقابلات والاجتماعات أن أصل إلى السؤال الأهم: هل هم مستعدّون لدفع ثمن هذه الأداة؟ وكم سيدفعون؟ بالتأكيد لا يمكنكَ التوقّف عند إجابة السؤال: "هل تظنّ أنّ هذه الفكرة جيّدة؟" وقصّتي هذه تؤكّد هذا الأمر، وذلك لأنّ جميع الأشخاص الذين قابلتهم – بدون استثناء – أخبروني أنّها فكرة رائعة، لكنّها في الواقع لم تكن كذلك! إليك التّفاصيل. عندما كنتُ أحدّثهم عن المشروع، لم أكن أمتلك حينها أيّة صور له تبيّن طريقة عمله أو التعامل معه، في الحقيقة في هذه المرحلة بالذّات من التّخطيط للمشروع لستَ بحاجة لتلك الصور، لأنّ الفكرة من المنتج أو الخدمة يجبُ أن تكون واضحة وسهلة الفهم من خلال شرح بسيط، فإذا وجدت نفسك مضطرًّا لأن تقول: "لا يمكنني شرح فكرة المنصّة، عليكم أن تشاهدوها بأنفسكم" عندها تكون قد فشلت بالفعل! ربّما يجب أن يجرّبوا المنصّة ليحبّوها أكثر ويستمتعوا باستخدامها، أو حتى ليدفعوا لك من أجلها. لكنّك لا يمكنّك أن تجري دراسة تسويقيّة، أو حتى تقييمًا للفكرة ذاتها إذا وصلتَ إلى مرحلة "لا أعرف كيف أشرح لكم الموضوع". فكّر بالأمر، كيف يفترض بهؤلاء الزبائن أن ينشروا فكرة مشروعك الرائع بين أصدقائهم ومعارفهم؟ كيف سيوضحون لهم الفكرة إن لم تكن أنت نفسك قادرٌ على شرحها؟ لذلك كنتُ أبدأ لقاءاتي بالتحدّث قليلًا عن كيف أمكنني قياس أداء الحملات التّقليدية غير القابلة للقياس عادة، كالإعلانات في المجلات، وكيف أمكنني مراقبة أداء ثلاثين حملة إعلانيّة على التوازي في وقت واحد. هنا تحديدًا كان الجميع يجيبون بنفس الجملة تقريبًا: "هذا رائع، كم أحتاج هذه المنصّة، وأعرف أيضًا مجموعة من الأشخاص الآخرين الذين يحتاجونها". بعد ذلك كنت أدخل في تفاصيل ميزات المنصة، على سبيل المثال عندما تستخدم أداة Google Analytics لأغراض التّسويق، عليك أن تحدّد أهدافك وحملاتك بالبداية، ومن ثمّ تراقب تغيّر البيانات. لكنّك لو أدركت بعد مدة أنّك بحاجة لقياس أداء صفحة أخرى لم تكن تراقبها سابقًا أو أنّك تريد تعديل أحد الأهداف التي قمت بتحديدها، أو أنّ حملةً ما لم تكن تتوقّع منها أيّ شيء قد جلبت لك مردودًا فاق التوقعات، في حالات كهذه، أقصى ما يمكنك فعله هو تغيير إعدادات الحملات والأهداف، ومن ثمّ البدء بالمراقبة والتّجريب من جديد مع المحدّدات الجديدة. لكنّ الأداة التي قمتُ بابتكارها لم تكن كذلك، كان بإمكانها أن تطبّق التّعديلات التي تجريها مباشرة وتريَك نتائجها حتى على بيانات مضت، حينها لن تقلق من فكرة أنّ عليك أن تضبط كل شيء بالشّكل الصّحيح من البداية، بل ستتمكن من إجراء الاختبارات والتعديلات في أي وقت. ثلاثون شخصًا من أصل ثلاثين وافقوا على أنّ هذه الميّزة خارقة!. حتى هذه اللحظة كلّ شيء كان يسير على ما يرام، حتى وصلتُ إلى النقطة التي أدركت حينها أنّ هذه الفكرة لا تصلح لتكون مشروعًا ربحيًّا، تحديدًا عندما وصلتُ إلى السؤال المتعلّق بالتكلفة، هذا السؤال الذي يحسم كل جدال. بعضهم قال أنّ هذه المنصّة ستوفّر عليهم حوالي ألف دولار شهريّا من نفقات التّسويق، لكنهم مع ذلك ليسوا مستعدّين حتى لدفع خمسين دولار شهريًّا ثمنًا للخدمة بل يجب أن تتاح لهم بشكل مجانيّ! آخرون قالوا أنّ خمسين دولار في الشهر ليس كثيرًا ويجب عليّ أن أستهدف بمشروعي هذا الشّركات الصّغيرة والمتوسّطة، لكن من الواضح بالنسبة لي أنّ تكاليف التّدريب والتّسويق لهذه الفئة ستكون باهظة. على الجانب الآخر، أصرّ البعض على أنّني يجب أن أركّز على الشّركات الضخمة وكبار رجال الأعمال، ما يعني أن أتقاضى منهم ألف دولار شهريًا وأن أقدّم خدمات استثنائيّة على مستوىً عالٍ من الاحتراف، أو أن أعقد شراكاتٍ مع استشاريين كبار في مجال التسويق والذين يرغبون بتحسين سمعتهم في عالم الأعمال. لكنني مع الأسف لم أتمكّن من العثور على استشاريّ واحد أبدى تفاعلًا مع الفكرة. تسعير الخدمة أو المنتج ليس مجرّد رقم، إنّه يحدّد لك جمهورك المستهدف، وبالتالي يحدّد حجم المنافسة، والمزايا التي يجب أن يتمتّع بها المنتج، والطريق الذي عليك أن تسلكه لكي تصل إلى الزبائن. إذا لم تبدأ بمناقشة موضوع سعر الخدمات التي تقدمّها أثناء تخطيطك لعملك الرياديّ فلن تحصل على إجابة شافية لأيّ من هذه الأسئلة. قد تكون لديك فكرة إبداعيّة، وقد تتمكّن من إقناع الجميع بأنّ الخدمات التي تقدّمها رائعة ومثيرة للاهتمام، لكن إن لم تستطع أن تصيغ نموذج العمل التّجاري Business model الخاصّ بك، ولم تتمكّن من تحديد أسعارك بدقّة، عندها تأكّد أنّك لستَ جاهزًا بعد للبدء بمشروعك الخاص. هنا قرّرت أنّ عليّ أن أتخلّى عن فكرة المنصّة نهائيًّا، صحيحٌ أنّها كلّفتني شهرين كاملين من البحث والعمل المتواصل، لكنّني تعلّمت أنّه من الممكن أن تكون لديك فكرةٌ عظيمة لكنّها ببساطة لا يمكن أن تتحوّل لعمل ربحيّ، وهذا بالفعل ما حصل معي. والآن ما علاقة كلّ ذلك بمشروع WPEngine ؟ في الحقيقة لولا أنّني كنتُ صارمًا وجادًّا بالتخلّي عن فكرة منصّتي التسويقيّة السّابقة، لما تمكّنت من إيجاد فكرة عبقريّة ثانية. القضاءُ تمامًا على الفكرة التي كانت تشغلني لفترة طويلة سمح لي بالفعل من العمل على فكرة أخرى بنفس الهمّة والعزيمة التي بدأتُ بها فكرتي الأولى. فكرة WPEngine كان لها نفس القصّة تمامًا، لكنّ نتيجة دراسة الزبائن Customer development كانت مختلفة، لذلك أخبرتكم سابقًا أنّها منهجيّة قابلة للتّكرار وليست محض مصادفة أو ضربة حظّ. كنتُ بحاجة شيء يشبه WPEngine لمدوّنتي الخاصّة، فقد كانت صفحات الموقع تحتاج زمنًا طويلًا للتّحميل، عدا محاولات الاختراق العديدة التي تعرضت لها (معروف عن منصّات ووردبرس أنّها عرضة للاختراق أكثر من غيرها، ليسا المشكلة في المنصّة ذاتها بالطبع، إنّما ربما الأمر راجع إلى ضعف القائمين عليها تقنيًّا). لذلك فقد كنت بحاجة إلى استضافة تريحني من هذا العناء الإضافيّ، تساعد على تسريع الموقع وحمايته بنفس الوقت دون الحاجة لأن أمتلك خبرةً تقنيًّة إضافية. بحثت طويلًا عن حلول استضافة تؤمّن طلبي لكنني لم أجد، سألت كثيرًا هنا وهناك لكن كلّ ما كنتُ أحصل عليه هو إجابات من النّوع: "استضافات كهذّه غير موجودة، لكن أتعلم، إنّها فكرةٌ رائعة بالفعل وأنا أحتاجها أيضًا!". حسنًا لديّ خبرة طويلة مع عباراتٍ كهذه وأعلم تمامًا أنّها لا تكفي لتدفعني إلى الانطلاق بمشروع مشابه! لذلك بدأت بتنظيم اللّقاءات، عبر البريد الإلكترونيّ، المكالمات الهاتفية، المقابلات الشخصيّة، وبأيّ وسيلة ممكنة. أردت أن أصل إلى أيّ إنسان لديه مدوّنة وأعرف إجابته عن سؤال واحد: هل أنت مستعدّ لدفع 49 دولار شهريًّا وتحصل بالمقابل على حلّ متكامل يجمع بين الأداء العالي والحماية الفائقة والقابلية للتوسّع مهما بلغ حجم موقعك؟ وماذا لو أضفتُ لك ميّزة قمتُ بتطويرها بنفسي – أيضًا مستلهمة من معاناتي الشخصية – منصّة إضافيّة تستطيع من خلالها إجراء تجاربك بضغطة زر، دون أن تؤثر على موقعك الحقيقيّ؟ ردّ الفعل هذه المرّة كان مختلفًا تمامًا، بعض الأشخاص أخبروني أنهم مستعدون لدفع هذا المبلغ شهريًّا مقابل ميّزة المنصّة وحدها. عندما سمعتُ هذه الجملة علمتُ أنّ الخدمة التي أقدّمها بالسّعر الذي عرضتُه تفوق كلّ التوقّعات، وأدركتُ أيضًا أنّني قد وجدتُ الشريحة المناسبة لتسويق منتجي. لم أبدأ بالعمل على مشروعي قبل أن نجحت بالعثور على ثلاثين شخصًا أخبروني أنّهم سيدفعون لي حالما ينطلق المشروع، لم يقولوا ربّما، بل بالتأكيد سيدفعون، في النهاية عشرين منهم أصبحوا من زبائني الدّائمين. بعد ذلك انتشر صدى المشروع بين مئات الآلاف من أصحاب المواقع والمدوّنات، وأخذوا يتدفّقون إلى الموقع كلّ يوم وذلك حتّى قبل أن أدفع قرشًا واحدًا على أيّة حملة تسويقيّة. السّؤال الآن، هل تصلح هذه المنهجيّة لكل مكان وزمان؟ في الواقع إذا كنتَ تنوي العمل على مشروع ليس له سوقٌ أصلًا وتريد أن تخلق أنت هذا السوق، عندها لن تتمكّن من شرح فكرتك ببساطة ولن يتمكّن الآخرون من فهم كنه هذا المشروع ولا كيف يستفيدون منه. لكن في الحالة العامّة، عندما تقوم ببناء مشروع يفترض به أن يحلّ مشكلة قائمة بالفعل، مشروع من السّهل أن يتعامل معه النّاس ويستمتعون باستخدامه، مشروع من السّهل أن ينتشر بين النّاس وأن يتناقلوه بينهم، مشروع يجلب لك الرّبح بالفعل. في هذه الحالة ليس لديك أيّ عذر يمنعك من أن تخبر به ثلاثين شخصًا قبل أن تبدأ بالاستثمار والعمل على الفكرة. تذكّر دائمًا، مهما كانت أفكارك مبتكرة وإبداعيّة، لا يمكنك أن تدّعي أنها ناجحة كمشروع ربحيّ ما لم تضعها جدّيًّا في ميزان عالم الأعمال. والآن هل لديك أيّة تجارب سابقة أو أفكار حول موضوع دراسة الزّبائن في مرحلة ما قبل إطلاق المنتج؟ هل تعتقد أن المنهجيّة التي ذكرتُها صالحة للتطبيق في حالتك؟ شاركنا النّقاش في التّعليقات. ترجمة -وبتصرّف- للمقال Vetting a startup (or two): The systematic birth of @WPEngine لصاحبه Jason Cohen. حقوق الصورة البارزة: Designed by Freepik.1 نقطة
-
في سلسلة من عدة أجزاء سنناقش موضوعًا نظريًّا يُعتبر من أساسيّات هندسة البرامج، وهو أنماط التصميم (Design Patterns)، وسنعتمد لغة JavaScript في نقاشنا لتصاعد شعبيّتها ومرونتها التي تسمح لنا ببناء مشاريعنا وفق أنماط متنوّعة مما سيُسهّل علينا شرح موضوع السّلسلة نمط المُشيِّد (Constructor)يشيع استخدام المُشيّدات في اللغات الكائنيّة التّوجّه، حيث تُستخدم لإنشاء نُسخ (instances) من الأصناف (classes)، ومع أنّ JavaScript ليست لغةً كائنيّة التّوجّه بالمعنى التّقليديّ، إلّا أنّها تسمح بإنشاء نُسخ عن كائنات باستخدام بالمُشيّدات، ويمكن لأيّ دالّة أن تُستخدم كمُشيّد، وذلك بأن نُسبقها بالكلمة new، ولتوضيح هذا النّمط سنقوم بإنشاء مُنبّه (كالّذي تضبطه للاستيقاظ في هاتفك) يمكن ضبطه إلى تاريخ ووقت معيّنين ثمّ تفعيله أو تعطيله حسب الرّغبة: function Alarm(when) { this.setAt = when; this.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } this.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } } var a = new Alarm("2015-03-19 5:58 PM"); a.enable() // Alarm will wake you up after 8.982 seconds // After a few seconds: // Wake up!في المثال السّابق نُسمّي الدّالة Alarm() مُشيّدًا (constructor)، والكائن a نُسخة (instance). لاحظ أنّ Alarm في المثال السّابق ليست سوى دالّة (function)، فهي ليست صنفًا كما في لغات أخرى مثل Java وC++، إذ تُعتبر الدّوال في JavaScript مكوّنًا من الدّرجة الأولى وتُعامل كما يُعامل أيّ كائن، وهكذا يمكن استخدامها كمشيّد لكائن آخر ممّا يسمح بمحاكاة مفهوم الأصناف الّذي لم يُضَف إلّا مؤخّرًا في JavaScript. من عيوب المِثال السّابق إسناد الدّوال الّتي ستعمل عمل الوظائف (methods) إلى النُسخة ذاتها عند إنشائها، وهذا يعني تكرار محتوى الدّوال في الذّاكرة مع كلّ نسخة جديدة من الكائن Alarm، بينما يمكننا توفير هذا الاستهلاك غير المُبرّر للذّاكرة بإسناد الدّوال إلى النّموذج البدئيّ للكائن (أي إلى Alarm.prototype) مما يسمح بمشاركتها بين كل نسخ الكائن، لتقوم الآلة الافتراضيّة بتنفيذ النّصّ البرمجيّ للدّالّة ذاتها بسياق النّسخة (instance context) الّتي استدعت الدّالة، أي إنّ this تُشير ضمن الدّالة عند تنفيذها إلى النُسخةَ المنشأة وليس الصّنف؛ بالطّبع ليس من المرغوب تطبيق الفكرة ذاتها على المُتغيّرات الأخرى مثل setAt، لأنّه من البديهيّ أن تختلف قيمتها بين نسخة وأخرى. لنُعد كتابة المثال السّابق بصورة أفضل: function Alarm(when) { this.setAt = when; } Alarm.prototype.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } Alarm.prototype.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } var a = new Alarm("2015-03-19 6:21 PM"); a.enable(); // Alarm will wake you up after 30.243 seconds // After 30 seconds... // Wake up!هذا الأسلوب في إنشاء الأصناف شائع جدًّا، وهو يتطلّب فهمًا دقيقًا لآليّة الوراثة في JavaScript؛ إذ يُبنى كلّ كائنٍ فيها على كائن آخر يُسمّى النّموذج البدئيّ (prototype)، ويقوم هذا الكائن الأخير على كائن ثالث أعلى منه في السّلسلة هو نموذجه البدئيّ، وهكذا حتّى نصل إلى null الّذي ليس له نموذج بدئيّ بحسب تعريف اللّغة. في مثالنا السّابق الكائن Alarm.prototype هو النّموذج البدئيّ للكائن a، وهذا يعني أنّ كل الخواصّ المُسندة إلى Alarm.prototype وما فوقه ستكون مُتاحة للكائن a، ولو كتابنا برنامجًا مُشابهًا بـJava لقُلنا إنّ Alarm صنفٌ وإنّ a نُسخة عن هذا الصّنف (instance). عندما نحاول الوصول إلى الخاصّة a.setAt، فإنّ مُفسِّر JavaScript يبدأ بالبحث عن هذه الخاصّة من أدنى سلسلة الوراثة، أي من الكائن a ذاته، فإنّ وجدها قرأها وأعاد قيمتها، وإلّا تابع البحث صعودًا إلى النّموذج البدئيّ وهكذا... وطبيعة JavaScript هذه هي ما سمح لنا بإسناد الوظيفتين enable وdisable إلى Alarm.prototype مطمئنّين إلى أنّها ستكون مُتاحة عند قراءة a.enable() وa.disable(). يمكن التأكّد من النّموذج البدئيّ للكائن a كما يلي: Object.getPrototypeOf(a) == Alarm.prototype; // trueالأصناف في ECMAScript 6يُقدّم الإصدار الأحدث من JavaScript مفهوم الأصناف (classes) بصورته التّقليديّة المعروفة في اللّغات الأخرى، إلّا أنّه ليس سوى أسلوب آخر لصياغة النّماذج البدئيّة (أو ما يُسمّى syntactic sugar)، وهذا يعني أنّه نموذج الوراثة في JavaScript لم يتغيّر. يمكننا إعادة كتابة المثال السّابق بصياغة الأصناف في ES6 كما يلي: class Alarm { constructor(when) { this.startAt = when; } enable() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } disable() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } }وستُسند الدّوال enable() وdisable() إلى Alarm.prototype تمامًا كما في المثال الذي سبقه. يُذكر أنّ استخدام new ليست الطّريقة الوحيدة لتشييد الكائنات، إذ يمكن استخدام الوظيفة Object.create() لتُعطي النّتيجة ذاتها: var a = Object.create(Alarm.prototype); Object.getPrototypeOf(a) == Alarm.prototype; // trueإسناد الخواصّ إلى الكائناتJavaScript لغة ديناميكية، وهذا يعني أنّه يمكن إضافة وحذف الخواصّ من الكائنات وتعديل نماذجها البدئيّة أثناء التّنفيذ، وهذا ما يمنحها القسم الأكبر من مرونتها ويجعلها مناسبة للاستخدام في بيئة مُعقّدة مثل بيئة الويب، وليس من الغرابة أن توفّر اللّغة وسائل متعدّدة لإسناد الخصائص إلى الكائنات لتلبية الحاجات المتنوّعة لتطبيقات الويب. ماذا لو أردنا تغيير قيمة المنبّه في مثالنا السّابق بعد تفعيله؟ لربّما ترادونا للوهلة الأولى إمكانيّة تغيير قيمة الخاصّة setAt بالطّريقة التّقليدية: a.setAt = "2016-03-03 03:03 PM"; // or a["setAt"] = "2016-03-03 03:03 PM";لكنّ نتيجة هذا الفعل لن تكون كما يُتوقّع، فلو عدنا للمثال السابق وتمعّنا في خواصّه، للاحظنا عيبًا في كيفيّة عمل المُنبّه، إذ إنّ الخاصّة setAt مكشوفة ويمكن تغيير قيمتها في أيّ وقت، حتى بعد تفعيل المُنبّه، إلّا أنّ تغييرها بعدئذٍ لن يغيّر اللّحظة الحقيقيّة الّتي سينطلق فيها المنبّه كما يتضّح لنا عند قراءة النّصّ البرمجيّ، ولذا فنحن هنا أمام حلّين: إمّا منع تغيير قيمة الخاصّة setAt وجعلها للقراءة فقط بعد إنشاء المُنبّه، أو إيقاف المنبّه وإعادة ضبطه في كلّ مرّة تُغيّر فيها قيمة الخاصّة setAt، وكلا الحلّين متاحان إذا كنّا على علم بأساليب إسناد الخصائص في JavaScript. توفّر اللّغة الوظيفة Object.defineProperty() الّتي تسمح بتعريف خواصّ لكائن ما مع إمكانيّة التّحكم بتفاصيل هذه الخاصّة، ومن هذه التّفاصيل: هل الخاصّة قابلة للكتابة؟ (writable)هل يجب المرور على هذه الخاصّة عند سرد خواصّ الكائن؟ (enumerable)ما الذي يحدث عند إسناد قيمة للخاصّة؟ (set)ما الذي يحدث عند قراءة قيمة الخاصّة؟ (get)وبهذا يمكننا بسهولة منع تغيير قيمة الخاصّة setAt بعد إسنادها: function Alarm(when) { Object.defineProperty(this, "setAt", { value: when, writable: false }) } Alarm.prototype.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } Alarm.prototype.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } var a = new Alarm("2015-03-19 7:51 PM"); a.setAt = new Date("2016-03-19"); console.log(a.setAt); // "2015-03-19 7:51 PM"لاحظ أنّ قيمة setAt لم تتغيّر. هذا حلّ جيّد، لكن سيكون من الأفضل السّماح للمُستخدم بتعديل قيمة المنبّه، وعندها سنلجأ لإيقاف المنّبه وإعادة ضبطه كما يلي: function Alarm(when) { var _hidden_value = new Date(when); Object.defineProperty(this, "setAt", { set: function(newValue) { _hidden_value = new Date(newValue); if (this.timeout) { this.disable(); console.log("Alarm changed to " + newValue); console.log("You need to re-enable the alarm for changes to take effect"); } }, get: function() { return _hidden_value; } }) } Alarm.prototype.enable = function() { var startAfter = new Date(this.setAt) - new Date; console.log("Alarm will wake you up after " + startAfter/1000 + " seconds"); this.timeout = setTimeout(function() { console.log("Wake up!"); }, startAfter); } Alarm.prototype.disable = function() { if (this.timeout) { clearTimeout(this.timeout); delete this.timeout; console.log("Alarm diabled"); } } var a = new Alarm("2016-03-03 03:03 PM") a.enable(); // Alarm will wake you up after 30221933.66 seconds a.setAt = "2015-03-19 8:05 PM"; // Alarm changed to 2015-03-19 8:05 PM // You need to re-enable the alarm for changes to take effect a.enable() // Alarm will wake you up after 20.225 seconds // After 20 seconds... // Wake up!لاحظ أنّنا سنحتاج إلى مُتغيّر سرِّيِّ (_hidden_value) نُخزّن فيه القيمة الفعليّة لوقت التّنبيه. متى أستخدم هذا النّمط؟نمط المُشيّد لا يحتكر بنية مشروعك عند استخدامه؛ معنى هذا أنّه لا شيء يمنعك من استخدام نمط المُشيّد مع أي نمط آخر عند الحاجة لذلك، فيمكن (بل يشيع كثيرًا) استخدام المُشيّدات ضمن الوحدات (modules) ثمّ تصديرها لاستخدامها من موضع آخر في المشروع، ومثال ذلك أشياء مثل EventEmitter وStreams في Node.js. كما يمكن بناء أنماط أخرى سنتعرّف عليها لاحقًا على أساس المُشيِّدات مثل نمط الكائن المُتفرّد (Singleton) ونمط المُراقِب (Observer pattern). المصادر: شبكة مُطوّري موزيلّا: Inheritance and the prototype chainكتاب JavaScript Design Patterns لمؤلّفه Addy Osmani1 نقطة