انت الان في اول فصل من جافا سكريبت يحتوي كل فصل علي 20 دروس في نهاية كل فصل قم بالتعليق وكتابة ما استفد بة وما لم تستفد منة واذا وجهتك اي مشكال لا تتردة في الاتصال بناء
تعرف علي
ما الجديد في الإصدار القادم من جافاسكربت (ECMAScript 6)##
Harmony هو الاسم الرمزي لـ ECMAScript 6 وهي اللغة القياسية التي تقوم عليها JavaScript، والإصدار الجديد يأتي بميزات جديدة تتناول العديد من جوانب اللغة بما فيها الصياغة (syntax) وأسلوب البناء وأنواع جديدة من المكونات المدمجة في اللغة. في هذا المقال نتعرف على بعض من المميزات التي ستجعل كتابة شيفرة جافاسكربت أكثر اختصاراً وفعالية.

متغيرات نطاقها القطعة البرمجية(Block-scoped Variables)
في الإصدار الحالي من JavaScript، تُعامل كل المتغيرات المفروضة ضمن دالة (function) على أنها تابعة لهذه الدالة (Function-scoped) أي يمكن الوصول إليها من أي موقع ضمن هذه الدالة، حتى وإن كانت هذه المتغيرات قد فُرضِت ضمن قطعة برمجية فرعية ضمن هذه الدالة (كحلقة for أو جملة شرطية if)، وهذا يخالف ما تتبناه بعض من أشهر لغات البرمجة، وقد يسبب بعض الارتباك لمن لم يعتد عليه.
لنوضح أكثر في هذا المثال:
var numbers = [1, 2, 3];
var doubles = []
for (var i = 0; i < numbers.length; i++) {
var num = numbers[i];
doubles[i] = function() {
console.log(num * 2);
}
}
for (var j = 0; j < doubles.length; j++) {
doubles[j]()
}
عند تنفيذ هذا المثال، سنحصل على الرقم 6 ثلاث مرات، وهو أمر غير متوقع ما لم نكن على معرفة بطبيعة مجالات JavaScript، ولو طبق ما يشبه هذا المثال في لغة أخرى، لحصلنا على النتيجة 2 ثم 4 ثم 6، وهو ما يبدو النتيجة المنطقية لشيفرة كهذه.
ما الذي يحدث هنا؟ يتوقع المبرمج أن المتغير num محصور ضمن حلقة for وعليه فإن الدالة التي ندخلها في المصفوفة doubles ستعطي عند استدعائها القيمة التي ورثتها عن مجال حلقة for إلا أن الحقيقة هي أن المتغير num يتبع للمجال العام، لأن حلقة for لا تُنشئ مجالًا فرعيًّا وعليه فإن القيمة العامة num تتغير ضمن حلقة for من 2 إلى 4 إلى 6 وعند استدعاء أي دالة ضمن المصفوفة doubles فإنها ستعيد إلينا القيمة العامة num، وبما أن الاستدعاء يحدث بعد إسناد آخر قيمة للمتغير num، فإن قيمته في أي لحظة بعد انتهاء الحلقة الأولى ستكون آخر قيمة أسندت إليه ضمن هذه الحلقة، وهي القيمة 6.
يعطينا الإصدار القادم طريقة لحل هذا الارتباك باستخدام الكلمة المفتاحية let بدلاً عن var، وهي تقوم بخلق مجال ضمن القطعة البرمجية التي تُستخدم فيها، بمعنى آخر: ستكون let هي بديلنا عن var من الآن فصاعدًا، لأنها ببساطة تعطينا النتائج البديهية التي نتوقعها. لنُعِد كتابة المثال السابق باستبدال var num بـlet num:
var numbers = [1, 2, 3];
var doubles = [];
for (var i = 0; i < numbers.length; i++) {
let num = numbers[i];
doubles[i] = function() {
console.log(num * 2);
}
}
for (var j = 0; j < doubles.length; j++) {
doubles[j]();
}
عند تطبيق هذا المثال (يمكنك تطبيقه فيFirefox وChrome لأن كلا المتصفحين شرعا في دعم let) سنحصل على النتيجة البديهية 2 ثم 4 ثم 6. بالطبع بإمكاننا تحسين الشيفرة باعتماد let عند التصريح عن كل المتغيرات السابقة، وهو الأمر الذي يجب أن تعتاد فعله من اليوم!
الدرس الاول من javascript
شيفرة أقصر وأسهل للقراءة
لعل أكثر ما أُحبّه في JavaScript مرونتها الفائقة، وبالذات القدرة على إمرار دوال مجهولة (Anonymous Functions) لدوال أخرى، الأمر الذي يسمح لنا بكتابة شيفرة ما كان من الممكن كتابتها بلغات أخرى إلا بضعفي عدد الأسطر وربما أكثر. لاحظ هذا المثال:
var people = ['Ahmed', 'Samer', 'Khaled'];
var greetings = people.map(function(person) { return 'Hello ' + person + '!'; });
console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!'];
لو أردنا تنفيذ هذه المهمة في لغة أخرى، فلربما احتجنا إلى حلقة for لنمرّ من خلالها على كل عنصر ضمن المصفوفة ثم إدخال العبارات الجديدة ضمن مصفوفة أخرى، وهذا يعني أن مهمة يمكن كتابتها بسطرين في JavaScript قد تتطلب 5 سطور في لغة أخرى. لو لم تمتلك JavaScript القدرة على إمرار الدالة المجهولة function(person) {...} أعلاه، لفقدت جزءًا كبيرة من مرونتها.
لكن الإصدار القادم من JavaScript تذهب أبعد من ذلك، وتختصر علينا كتابة الكثير من النص البرمجي. لُنعد كتابة المثال السابق:
let people = ['Ahmed', 'Samer', 'Khaled'];
let greetings = people.map(person => 'Hello ' + person + '!');
console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!'];
في هذا المثال استخدمنا ما اصطلح على تسميته دوال الأسهم (Arrow Functions)، وهي طريقة أكثر اختصارًا لكتابة الدوال المجهولة، لن تحتاج لكتابة return، فهي ستضاف تلقائيًا عند التنفيذ. من الآن فصاعداً اعتمد دوال الأسهم عندما تريد تنفيذ دالة مجهولة بسيطة بسطر واحد.
بمناسبة الحديث عن الشيفرة المختصرة... ما رأيكم لو جعلنا الشيفرة أعلاه أكثر اختصارًا؟
let people = ['Ahmed', 'Samer', 'Khaled'];
let greetings = ['Hello ' + person + '!' for (person of people)];
console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!'];
قد تبدو الصياغة غريبة بعض الشيء، لكنها تتيح لنا فهم النص بسهولة أكبر، وتغنينا عن الحاجة لدالة مجهولة (الأمر الذي قد يؤثر على الأداء، وإن كان بأجزاء من الثواني). الصياغة التي استخدمناها أعلاه تُسمى Array Comprehensions، وإن كنت قادرًا على ترجمتها إلى العربية بطريقة واضحة، فلا تبخل بها علينا!
لكن... ألا ترون أنه يمكن تحسين هذه الشيفرة قليلاً؟
let people = ['Ahmed', 'Samer', 'Khaled'];
let greetings = [`Hello ${ person }!`for(person of people)];
console.log(greetings); // ['Hello Ahmed!', 'Hello Samer!', 'Hello Khaled!'];
هنا استبدلنا إشارات الاقتباس (' أو ") بالإشارة ` الأمر الذي أتاح لنا إحاطة المتغير person بقوسين معكوفين مسبوقين بإشارة $، وهذه الصياغة تدعى "السلاسل النصية المقولبة" أو Template Strings، والتي تسمح -بالإضافة إلى القولبة- بالعديد من الأشياء الرائعة، كالعبارات على عدة أسطر:
let multilineString = `I am
a multiline
string`;
console.log(multilineString);
// I am
// a multiline
// string
. تحديث: بدأ Firefox Nightly باعتمادها.
من المميزات الجديدة كذلك إمكانية اختصار بناء الكائنات ذات الخصائص بالشكل التالي:
· حاليًا، نقوم بكتابة شيفرة مثل هذه:
var createPerson = function(name, age, location) {
return {
name: name,
age: age
location: location
greet: function() {
console.log('Hello, I am ' + name + ' from ' + location + '. I am ' + age + '.');
}
}
};
var fawwaz = createPerson('Fawwaz', 21, 'Syria');
console.log(fawwaz.name); // 'Fawwaz'
fawwaz.greet(); // "Hello, I am Fawwaz from Syria. I am 21."
· في الإصدار القادم، سيكون بالإمكان كتابة الشيفرة كالتالي:
let createPerson = function(name, age, location) {
return {
name,
age,
location,
greet() {
console.log('Hello, I am ' + name + ' from ' + location + '. I am ' + age + '.');
}
}
};
let fawwaz = createPerson('Fawwaz', 21, 'Syria');
console.log(fawwaz.name); // 'Fawwaz'
fawwaz.greet(); // "Hello, I am Fawwaz from Syria. I am 21."
بما أن اسم المُعامل (parameter) يماثل اسم الخاصة (property)، فإن هذا يتم تفسيره على أن قيمة الخاصة توافق قيمة المعامل، بمعنى: name: name، بالإضافة إلى كتابة greet() {...} بدل greet: function() {...}.
كذلك سيكون بإمكاننا تحسين هذا النص أكثر من ذلك باستخدام الأصناف (Classes)، نعم! سيكون لدينا أصناف أخيرًا! (سنستعرضها لاحقاً)
الدرس الثاني منjavascript
الثوابت (Constants)
سيداتي وسادتي... رحبوا بالثوابت... نعم إنها أخيرًا متوفرة فيJavaScript، إحدى المكونات الأساسية لأي لغة برمجية التي لم تكن متوفرة في JavaScript، أصبحت الآن متوفرة. والآن نأتي للسؤال البديهي: لماذا أحتاج للثوابت؟ أليس بإمكاني التصريح عن متغير دون أن أغير قيمته بعد إعطاءه القيمة الأولية؟ نعم بالطبع بإمكانك ذلك، لكن هذا لا يعني بالضرورة أن المستخدم أو نصاً برمجيًا من طرف ثالث ليس بإمكانه تغيير قيمة هذا المتغير في سياق التنفيذ، وطالما أن المتغير "متغير" بطبيعته، فإننا دومًا بحاجة إلى شيء من أصل اللغة يحمينا من تغييره خطأ. عند التصريح عن ثابت فإننا نعطيه قيمة أولية ثم ستتولى الآلة البرمجية لـJavaScript حماية هذا الثابت من التغيير، وسُيرمى خطأ عند محاولة إسناد قيمة جديدة لهذا الثابت.
const myConstant = 'Never change this!'
myConstant ='Trying to change your constant';
// TypeError: redeclaration of const myConstant
console.log(myConstant); // "Never change this!"
المُعاملات الافتراضية(Default Parameters)
غياب دعم المُعاملات الافتراضية فيJavaScript واحد من أكثر الأشياء التي تزعجني، لأنها تجبرني على كتابة شيفرة مثل هذه:
function SayHello (user) {
if (typeof user == 'undefined') {
user ='User';
}
console.log('Hello ' + user);
}
console.log(SayHello('Fawwaz')); // Hello Fawwaz!
console.log(SayHello()); // Hello User!
لو كان عندي 3 متغيرات غير إجبارية، فهذا يعني أنني سأحتاج 3 جمل شرطية، الأمر الذي يتطلب الكثير من الكتابة المُملة. بفضل الإصدار القادم من JavaScript، سيكون بالإمكان كتابة شيفرة أبسط بكثير:
function SayHello (user='User') {
console.log('Hello ' + user);
}
SayHello('Fawwaz'); // Hello Fawwaz!
SayHello(); // Hello User!
الوعود (Promises)
الدرس الثالث من javascript
الوعود هي الحل الذي تأتينا بهJavaScript لحل مشكلة هرم الموت (Pyramid of Death) الذي نواجهه عند تنفيذ مهمات غير متزامنة تعتمد إحداها على الأخرى:
function getFullPost(url, callback) {
var getAuthor = function(post, callback) {
$.ajax({ method: 'GET', url: '/author/' + post.author_id }, callback);
};
var getRelatedPosts = function(post, callback) {
$.ajax({ method: 'GET', url: '/related/' + post.id }, callback);
};
$.ajax({ method: 'GET', url: url }, function(post) {
getAuthor(post, function(res) {
post.author = res.data.author;
getRelatedPosts(post, function(res)
post.releated = res.data.releated;
callback(post);
});
});
});
}
هل تلاحظ أن الشيفرة تتجه نحو اليمين؟ لو أردنا تنفيذ هذه المهمات غير المتزامنة واحدة بعد الأخرى وكان عددها 10 مثلًا فستصبح الشيفرة شديدة التعقيد، كما أن هذه الطريقة ليست بديهية، ولا يمكن لك أن تفهم ماذا تفعل هذه الدالة المجهولة (المعامل الثاني في كل دالة) ما لم تألفها. ماذا لو أمكننا كتابة هذه الشيفرة بصورة أفضل؟
function getFullPost(url) {
var post = { };
var getPost = function(url) {
return $http.get(url);
};
var getAuthor = function(post) {
return $http.get('/author/' + post.author_id).then(function(res) {
post.author = res.data.author;
});
};
var getRelatedPosts = function(post) {
return $http.get('/related/' + post.id).then(function(res) {
post.related = res.data.related;
});
};
return getPost().then(getAuthor).then(getRelatedPosts).catch(function(err) {
console.log('We got an error:', err);
});
}
ذكرنا في الجزء السابق أن اهتمامًا كبيرًا أُوليَ لتسهيل كتابة الشفرة وقراءتها في ECMAScript 6، والإسناد بالتفكيك (Destructuring assignment) لا يخرج عن هذا السياق، وهو ليس بالمفهوم الجديد في عالم البرمجة، فهو معروف فيPython وفي Ruby. بعيدًا عن تعقيدات المصطلحات، إليك هذا المثال:
var [a, b, c] = [1, 2, 3];
a ==1// true
b ==2// true
c ==3// true
ما الذي يحدث هنا؟ بكل بساطة تسمحECMAScript 6 بصياغة جديدة للتعريف عن المتغيرات أو إسناد قيم جديدة إليها جُملةً واحدة من خلال جمعها ضمن قوسي مصفوفة(Array) وسيقوم مُفسّر اللغة بإسناد قيمة مقابلة لكل متغيّر من المصفوفة الواقعة على يمين مُعامل الإسناد (=). الأمر لا يقتصر على إسناد المصفوفات، بل يمكن أيضًا إسناد خصائص العناصر:
let person = { firstName: "John", lastName: "Smith", Age: 42, Country: "UK" };
let { firstName, lastName } = person;
console.log(`Hello ${ firstName } ${ lastName }!`); // Hello John Smith!
في هذا المثال لدينا متغيّرات تتبع للنطاق العامّ firstName وlastName، وقد أسندنا لها قيمًا من خصائص الكائن person، حيث يبحث مفسّر اللّغة عن خصائص في الكائن person يماثل اسمها اسم المتغيّر المفروض ويُسندها إلى المُتغيّرات. يمكن توضيح المقصود بصورة أفضل إذا أعدنا كتابة الشفرة لتتوافق مع الإصدار الحالي من JavaScript:
var person = { firstName: "John", lastName: "Smith", Age: 42, Country: "UK" };
var firstName = person.firstName;
var lastName = person.lastName;
console.log("Hello " + firstName + " " + lastName + "!"); // Hello John Smith!
يشيع استخدام التفكيك في CoffeeScript (وهي لغة أقر Brendan Eich مُخترعJavaScript بأنّ الإصدار الأخير من JavaScript استوحى الكثير منها)، وخصوصًا عندما تُنظّم البرامج في وحدات كما في Node.js ويكون اهتمامًا مُقتصرًا على استيراد جزء مُحدّد من الوحدة المعنيّة:
{ EventEmitter } = require 'events'
{ EditorView } = require 'atom'
{ compile } = require 'coffee-script'
compile('# coffeescript code here');
عند تحويل هذا النص إلى JavaScript الحالية، سنحصل على:
var EventEmitter = require('events').EventEmitter;
var EditorView = require('atom').EditorView;
var compile = require('events').compile;
compile('# coffeescript code here');
من الاستخدامات المفيدة للإسناد بالتفكيك التبديل بين قيمتي متغيّرين بصورة سهلة، سنقتبس المثال من توثيق CoffeeScript ونُعيد كتابته بـJavaScript:
var theBait = 1000;
var theSwitch = 0;
[theBait, theSwitch] = [theSwitch, theBait];
قبل ES6 كنا لنحتاج لكتابة مُتغيّر مؤقّت نخزن فيه قيمة إحدى المتغيّرين للاحتفاظ بها قبل التبديل بين القيمتين، وهو ما يفعله محوّل CoffeeScript بالفعل ليعطينا شفرة JavaScript متوافقة مع الإصدار الحالي (مع أنه يقوم بتخزين كلا القيمتين في مصفوفة، إلا أنّ الفكرة تبقى ذاتها):
var theBait, theSwitch, _ref;
theBait =1000;
theSwitch =0;
_ref =[theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1];
المُكرِّرات (Iterators) وحلقة for... of
الدرس الرابع من javascript
ما من لغة برمجة تخلو من وسيلة للمرور على عدد من القيم وتكرار تنفيذ عمليّة معيّنة على هذه القيم، من أبسط هذه الوسائل حلقة for التقليديّة الشّهيرة، وفي JavaScript يشيع استخدام حلقة for... in إلى جانبها للمرور على أسماء خصائص العناصر، إذ يمكننا معرفة كل خصائص العنصر document بسطرين فقط:
for (var propertyName in document) {
console.log(propertyName);
}
// "body"
// "addEventListener"
// "getElementById"
// ...
لاحظ أن حلقة for... in تُعيد أسماء خصائص العنصر (كسلسة نصيّة String)، والأمر لا يستثني المصفوفات، فهي ليست سوى كائنات بأسماء خصائص توافق رقم الفهرس(Index):
for (vari in [1, 2, 3]) {
console.log(i);
}
// "0"
// "1"
// "2"
من عيوب حلقة for... in أن لا شيء في تعريف اللغة يُجبر مُفسّر اللّغة على إخراج العناصر بترتيب ثابت بالضّرورة، وهذا يعني أنها تصبح مباشرة غير صالحة للمرور على المصفوفات - التي تستخدم لحفظ عناصر مُرتّبة - بطريقة بديهيّة، ويحلّ محلّها حلقة for التقليديّة عندئذٍ، وأمّا عند استخدامها للمرور على الكائنات، فإنّها لا تُعيد إلّا الخصائص الّتي تُعرّف على أنها قابلة للتعداد (enumerable)، وهو شيء يُحدّد عند تعريف الخاصّة، كما أنّها تُعيد الخصائص القابلة للتعداد التي ورثها الكائن عن "آباءه" ضمن سلسلة الوراثة، وهو تصرّف قد لا يكون مرغوبًا دومًا، وغالبًا سترى المطوّرين يُجرون فحصًا للخاصّة قبل متابعة تنفيذ الشفرة لمعرفة ما إذا كانت تخصّ العنصر ذاته أمّ أنّه ورثها:
var obj = { a: 1, b: 2, c: 3 }; // كائن جديد لا يرث سوى النموذج Object
for (varprop in obj) {
console.log("o." + prop + " = " + obj[prop]);
}
// "o.a = 1"
// "o.b = 2"
// "o.c = 3"
في هذا المثال (المنقول عن شبكة مطوّري موزيلا) فرضنا كائنًا جديدًا بثلاث خصائص، وعند المرور عليه بحلقة for... in فإنّنا حصلنا على النتيجة المتوقّعة، ولم نحصل على خصائص إضافيّة لأنّ الكائن الذي فرضناه لا يرث أي كائن آخر سوى Object (الذي ترثه كل الكائنات افتراضًا). أما في المثال التالي، فقد احتجنا لإجراء اختبار hasOwnProperty على العنصر الوارث لكي لا تظهر سوى الخاصة color التي يملكها بذاته ولم يرثها:
var triangle = {a: 1, b: 2, c: 3};
function ColoredTriangle() {
this.color = "red";
}
ColoredTriangle.prototype = triangle;
var obj = new ColoredTriangle();
for (varprop in obj) {
if (obj.hasOwnProperty(prop)) {
console.log("o." + prop + " = " + obj[prop]);
}
}
// Output:
// "o.color = red"
حسنًا، لقد أطلنا الحديث عن حلقة for... in وهي ليست بالجديدة؛ لكنّنا أصبحنا نرى الحاجة لشيء جديد أكثر بساطة ومرونة، فهذا ما تبتغيه ES6 في النهاية، ولهذا نشأت فكرة المُكرّرات؛ التي تسمح لأي عنصر بأن يختار لنفسه الطّريقة التي يتصرّف بها عند المرور به في حلقة، ومع المُكرّرات لا بدّ من نوع جديد من الحلقات لتلبية هذه الحاجة والمحافظة على حلقة for... in للتوافق مع الإصدارات القديمة. من هنا نشأت حلقة for... of الجديدة.
for (varnum of [1, 2, 3]) {
console.log(num);
}
// 1
// 2
// 3
for (varnode of document.querySelectorAll('a')) {
console.log(node);
}
//
//
حصلنا في المثالين السابقين على قيمة الخاصّة وليس اسم الخاصّة، لكنّ هذا لا يعني أنّ حلقة for... of تُعيد قيم الخصائص دومًا، بل إنّها تستدعي مُكرّر الكائن (Iterator) وتطلب منه في كلّ دورة للحلقة تزويدها بشيء ما، وتترك للمُكرّر الحُريّة بإعادة أي قيمة يرغب بها، ولكن ولأنّنا نستدعي في مثالنا مصفوفة أولاً، وعنصر من نوع NodeList ثانيًا، وكلا النّوعين يُعيد مُكرُّرهما قيمَ العناصر في المصفوفة، فإنّنا نحصل على تلك النتيجة البديهيّة. بإمكاننا إنشاء أصناف بمُكرّرات خاصّة نُنشئها بأنفسنا، ولنفترض أن لدينا نوعًا لصفّ ضمن مدرسة ابتدائية، ونريد أن نحصل على تفاصيل الطلّاب على هيئة نص مُنسّق عند المرور على الصّفّ في حلقة for... of:
function SchoolClass(students) {
this.students = students;
}
SchoolClass.prototype[Symbol.iterator] = function*() {
for (leti = 0; i <this.students.length; i++){
let student = this.students[i];
yield `#${i+1} ${student.name} (${student.age} years old)`;
}
}
var ourClass = new SchoolClass([ { name: "Ahmed", age: 10 }, { name: "Alaa", age: 9 }/*, ...*/ ]);
for (student of ourClass) {
console.log(student);
}
// "#1 Ahmed (10 years old)"
// "#2 Alaa (9 years old)"
// "#3 ..." ...
استخدمنا الرمز الخاص Symbol.iterator لإسناد دالّة مُولّد(Generator function) التي تُعطينا عند استدعائها نسخة من مُكرّر الصنف المُخصّص الذي أنشأناه. سنتعرف بعد قليل على المُولّدات(Generators) وكذلك على الرموز (Symbols) في وقت لاحق. لاحظ أنّنا استخدمنا حلقة for... of للمرور على محتويات ourClass.
تذكّر أنّنا استخدمنا هذه الحلقة في الجزء السابق معArray Comprehension، كما في المثال:
let people = ["Samer", "Ahmed", "Khalid"];
console.log([`Hello ${person}` for(person of people)]);
إن كانت الفقرة الأخيرة غامضة بعض الشيء فلا تقلق، سنتوسّع بشرح المولّدات بعد قليل.
لكن دعونا نتوقّف قليلاً ولننتقل إلى الجانب الفلسفي لهذه الإضافات في JavaScript، قد تبدو للوهلة الأولى تعقيدات بلا طائل، خصوصًا وأنّ كثيرًا منها لا يهدف سوى للتسهيل، ولا يقدّم شيئًا يستحيل إنجازه بالإصدارات السابقة من اللغة؛ هنا يمكن الرّدّ بأنّ تطوّر اللغة متعلّق بكيفيّة استخدامها والخبرة التي تُكتسب مع مرور السّنين، حيث تظهر للمطوّرين حاجات جديدة وأفكار تطبّق مرارًا لدرجة أنها ترتقي لتصبح ضمن أساسات اللغة. سهولة كتابة الشفرة لم تعد رفاهية، بل هي ضرورة لإنجاز المشاريع الكبيرة لأنّها تتيح اختصار الوقت الذي كان سيضيع في كتابة متكرّرة ومُملّة، كما أنّها تُلبّي ما يتوقّعه المطوّرون من لغة أصبحت تؤخذ على محمل الجدّ وتُستخدم في تطوير تطبيقات ضخمة ومُعقّدة بعد أن كان جُلّ استخدامها تنفيذ بعض المهام البسيطة.
الدرس الخامس من javascript
المُولِّدات (Generators)
المولدات (Generators) ببساطة هي دوال يمكن إيقافها والعودة إليها في وقت لاحق مع الاحتفاظ بسياقها دون تغيير، صياغة دوال المولدات لا تختلف كثيرًا عن صياغة الدوال التقليدية، كل ما عليك هو إضافة إشارة * بعد function واستخدام yield بدل return، المثال التالي سيوضح فكرة المولدات أكثر:
function* getName() {
let names = ['Muhammad', 'Salem', 'Abdullah'];
for (name of names) {
yield name;
}
}
let nameGenerator = getName();
nameGenerator.next().value; // 'Muhammad'
nameGenerator.next().value; // 'Salem'
nameGenerator.next().value; // 'Abdullah'
nameGenerator.next().value; // undefined
}
ما الذي يحدث هنا؟ فرضنا دالّة مولّد(Generator function) (والتي تُميّز بإشارة النجمة *) سمّيناها getName، وفيها صرحنا عن مصفوفة فيها أسماء، وظيفة هذه الدالة أن تعطينا عند استدعائها نسخة من مُكرّر(Iterator) (الذي شرحناه لتوّنا)، يزوّدنا بالأسماء بالترتيب في كل مرة نستدعيه فيها ليعطينا النتيجة التالية (next())، أولاً يجب حفظ نسخة المُكرّر ضمن متغير لكي نسمح له بحفظ حالته، ودون ذلك سيعطينا استدعاء دالّة المولد مباشرةً getName().next() دوماً النتيجة الأولى لأننا عملياً نُنشئ نسخة جديدة عنه في كل مرة نستدعيه، أما استدعاء نسخة عنه وحفظها في متغير مثل myGenerator فيسمح لنا باستدعاء .next() عليها كما هو متوقع. لا ترجع الدالة .next() القيمة التي نرسلها عبر yield فقط، بل ترجع كائناً يحوي القيمة المطلوبة ضمن الخاصة value، وخاصة أخرى done تسمح لنا بمعرفة ما إذا كان المولد قد أعطانا كل شيء.
لنُعِدْ ترتيب أفكارنا:
1. المولّدات تسمح بتوقّف تنفيذها مع الاحتفاظ بحالة التنفيذ (يحدث توقّف التنفيذ عند كلّ كلمة yield). فلو أنّنا كتبنا دالّة تقليديّة في المثال أعلاه مع return بدل yield لحصلنا في كلّ مرّة على الاسم الأول (Muhammad). وهذه الميزة في المولّدات يمكن استغلالها لإنشاء حلقات لا نهائية دون إعاقة متابعة البرنامج:
2. function* numberGenerator() {
3. for(leti = 0; true; i++) {
4. yieldi;
5. }
6. }
7.
8. letnumGen = numberGenerator();
9. numGen.next(); // { value: 0, done: false }
10. numGen.next(); // { value: 1, done: false }
11. numGen.next(); // { value: 2, done: false }
12. numGen.next(); // { value: 3, done: false }
// ...
13. دوالّ المولّدات تُعطي عند استدعاءها مُكرّرات، وهذا يعني إمكانيّة استخدامها في حلقة for... of (احذر من تطبيق مثال كهذا على مولّد غير منتهٍ كما في المثال السابق!):
14. for(letname of getName()) {
15. console.log(name);
16. }
17.
18. // "Muhammad"
19. // "Salem"
// "Abdullah"
20. لكلّ مُكرّر وظيفة .next() مهمّتها بدء تنفيذ الدّالّة أو متابعة تنفيذها ثم إيقافها مؤقّتًا عند كلّ كلمة yield.
21. استدعاء next() على المُكرّر يعيد لنا في كلّ مرة كائنًا ذا خاصّتين: الأولى value وهي أيّ شيء نُعيده بكلمة yield، والثّانية done وهي قيمة منطقيّة(Boolean) تشير إلى حالة انتهاء تنفيذ الدّالة.
تقبل الوظيفة .next() للمُكرّرات مُعاملاً اختياريًّا تستقبله وتُرسله لدالّة المولّد بعد متابعة التنفيذ، ويمكن استخدامها لإرسال رسائل لدالّة المولّد بحيث نؤثّر في تنفيذه:
function*numberGenerator() {
for (leti = 0; true; i++) {
var reset = yield i;
if (reset) i = -1;
}
}
let numGen = numberGenerator();
numGen.next();// { value: 0, done: false }
numGen.next();// { value: 1, done: false }
numGen.next();// { value: 2, done: false }
numGen.next();// { value: 3, done: false }
numGen.next(true); // { value: 0, done: false }
في هذا المثال مرّرنا القيمة true إلى الوظيفة .next() على المُكرّر، والذي بدوره يُرسلها لدالّة المولّد كنتيجة yeild i في الدّورة الموافقة للحلقة، لنقومَ بحفظها في متغيّر reset ونُجريَ فحصًا عند متابعة التنفيذ لإعادة تعيين قيمة i، التي ستزداد بمقدار واحد مع بدء الدورة التالية لحلقة for جاعلةً قيمة i مساوية للصّفر.
خصائص المولّدات تجعلها مناسبة جدًا لكتابة شيفرة غير متزامنة بصورة أسهل تكاد تبدو فيها وكأنها شيفرة متزامنة خالية من الاستدعاءات الراجعة المتداخلة (Nested callbacks)؛ هذه الفكرة تحتاج إلى تركيز لأنها أساس لعدد من المكتبات مثل co وsuspend التي ظهرت مؤخّرًا وتصاعدت شعبيّتها بسرعة لأنّها تحلّ مشكلة جوهرية في استخدامJavaScript، ألا وهي التعامل مع الدوال غير المتزامنة(asynchronous functions) وذلك بالاعتماد كُليًّا على المُولّدات.
لنفترض أنّ لدينا موقعًا لقراءة الكتب يعرض ملفّ المستخدم الشّخصيّ مع عدد الكتب التي قرأها وعنوان آخر كتاب مع تقييم المستخدم له:
var list = document.querySelector("#book-list");
getJSON("http://reading-website.com/users/fwz.json", function(err, user) {
if (err) return; // افعل شيئًا بما بخصوص الخطأ
var num_books = user.books.length;
var most_recent_book_id = user.books[num_books - 1];
getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json", function(err, user_rating) {
getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json", function(err, book) {
var fragment = document.createDocumentFragment();
var h2 = document.createElement("h2");
h2.textContent = user.full_name;
var h3 = document.createElement("h3");
h3.textContent = "الكتب التي قرأها";
for (letbook of books) {
let li = document.createElement("li");
li.textContent = book.title + (book.id == most_recent_book_id ? " "+ user_rating : "");
fragment.appendChild(li);
}
list.appendChild(fragment);
});
});
})
في المثال السابق احتجنا إلى إرسال 3 طلباتAJAX يعتمد أحدها على الآخر، ولأنّنا لا نستطيع إرسال طلب بتقييم المستخدم للكتاب قبل معرفة مُعرّف الكتاب، فلا بدّ من أن يرسل الطلب الخاصّ بتقييم الكتاب ضمن الاستدعاء الرّاجع لطلب معلومات المستخدم، ثمّ يمكن جلب عنوان الكتاب ضمن الاستدعاء الرّاجع للطلب السّابق، وهذا يعني زيادة تعقيد الشفرة مع تداخل الاستدعاءات الرّاجعة لتبدو أشبه بسباغيتي لا تُعرف بدايته من نهايته.
تخيّلوا -لغرض التّخيّل- لو أمكننا كتابة هذه الشفرة (وهي غير متزامنة) لتبدو لقارئها وكأنها نص برمجي يسير بترتيب متزامن وبديهيّ... ألن يكون هذا أعظم شيء منذ اختراع JavaScript؟
var list = document.querySelector("#book-list");
try {
var user = getJSON("http://reading-website.com/users/fwz.json");
var num_books = user.books.length;
var most_recent_book_id = user.books[num_books - 1];
var user_rating = getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json");
var book = getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json");
var fragment = document.createDocumentFragment();
var h2 = document.createElement("h2");
h2.innerText = user.full_name;
var h3 = document.createElement("h3");
h3.innerText = "الكتب التي قرأها";
for (letbook of books) {
let li = document.createElement("li");
li.innerText = book.title + (book.id ==most_recent_book_id ? " " +user_rating : "");
fragment.appendChild(li);
}
list.appendChild(fragment);
} catch (e) {
// افعل شيئًا بما بخصوص الخطأ
// Error
}
نحن نعلم أن الأمور لا يمكن أن تكون بهذه الروعة، وأنّ الشفرة أعلاه لن تعمل... نحن نعلم أن شيفرتنا تحتاج تفاصيل المستخدم للحصول على الكتب، وأننا نحتاج للكتاب لجلب عنوانه وتقييمه، وتنفيذ هذه المهمّات بشكل غير متزامن لا يعني أنّه ليس علينا انتظار المهمّة الأولى قبل إطلاق الثانية - بل يعني فقط أن المتصفح يمكنه تنفيذ رسم العناصر الأخرى وعرض الصفحات وإرسال طلبات أخرى في هذا الوقت.
حسنًا، لدي خبر جيّد وآخر سيئ: أمّا الجيّد فهو أنّنا كتابة شيفرة شبيه بهذه أصبحت قريبة المنال مع الدّوالّ غير المتزامنة(Async Functions)، وأمّا الخبر السيّئ فهو أنّ علينا الانتظار إلى الإصدار 7 من ECMAScript لنستطيع كتابتها! (مع العلم أن المتصفّحات لم تنتهِ من تطبيق ES6!).
لكن هذا لا يعني أن نقف مكتوفي الأيدي إلى أن تصدر ES7، بل بإمكاننا إيجاد حلّ وسط لهذه المشكلة؛ لماذا نضطّر إلى تعقيد الأمور بالاستدعاءات الرّاجعة المتداخلة؟ ألا يتوفّر في اللّغة بنية برمجيّة تسمح بإيقاف شيفرتنا ريثما يتمّ أمر ما غير متزامن (الانتظار لإكمال طلب AJAX) ثمّ المتابعة بعد انتهاءه؟ يبدو هذا الحديث مألوفًا!
نعلم حتى الآن أننا بحاجة لاستخدام مولّد، ولذلك سنحيط شيفرتنا بدالّة مولّد كخطوة أولى:
var list = document.querySelector("#book-list");
function*displayUserProfile() {
// شيفرتنا هنا
}
الآن نحتاج لتنفيذ طلب AJAX الأوّل والانتظار إلى انتهاءه قبل الانتقال إلى الطّلب الثّاني، نعلم أنّ yieldتوقف تنفيذ المولّد:
var list = document.querySelector("#book-list");
function*displayUserProfile() {
yield getJSON("http://reading-website.com/users/fwz.json");
// ...
}
عظيم! لكن كيف نُخبر المولّد بأنّ عليه متابعة التنفيذ؟
var list = document.querySelector("#book-list");
function*displayUserProfile() {
yield getJSON("http://reading-website.com/users/fwz.json", resume);
// ...
}
سنمرر دالة اسمها resume للدّالة getJSON، وهذه الدالة ستُستدعى عند انتهاء جلب جواب الطّلب الذي أرسلناه، وهي فرصتنا لإخبار المولّد بمتابعة التنفيذ... فكيف سيكون محتواها؟
var list = document.querySelector("#book-list");
var resume = function(err, response) {
displayIterator.next(response);
}
function*displayUserProfile() {
var user = yield getJSON("http://reading-website.com/users/fwz.json", resume);
var num_books = user.books.length;
var most_recent_book_id = user.books[num_books - 1];
var user_rating = yield getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json", resume);
var book = yield getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json", resume);
var fragment = document.createDocumentFragment();
var h2 = document.createElement("h2");
h2.innerText = user.full_name;
var h3 = document.createElement("h3");
h3.innerText = "الكتب التي قرأها";
for (letbook of books) {
let li = document.createElement("li");
li.innerText = book.title + (book.id ==most_recent_book_id ? " " +user_rating : "");
fragment.appendChild(li);
}
list.appendChild(fragment);
}
var displayIterator = displayUserProfile();
displayIterator.next();
حفظنا نسخة عن المكرّر في متغيّر ثم استدعينا وظيفته next() في دالّة المتابعة، ممرّرين لها جواب الطّلب ليمكننا تخزينه ضمن المتغيّر user. الدّالة resume تستطيع الوصول إلى displayIterator لأنّه يكون معرّفًا قبل استدعاءها حتمًا، ولا ننسَ أن تعريف المتغيّرات في JavaScript يخضع لعملية الرّفع إلى أعلى النّطاق(variable hoisting) ممّا يجعل المتغيّر displayIterator موجودًا (وإن كان بلا قيمة) منذ بداية تنفيذ الشيفرة.
للتأكّد من فهم هذه الشيفرة، سنعيد تحليلها خطوة بخطوة: في طلب AJAX الأوّل تستدعى الدالة resume ويمرّر إليها جواب الطّلب(response)، الذي يمرّر بدوره إلى المُكرّر ليُحفظ في المتغيّر user الذي سيُستخدم في الخطوة التّالية للمولّد لإرسال الطّلب الثّاني. تُكرّر العمليّة ذاتها للطلبين الآخرين ثمّ تُعرض النتائج في الصّفحة. الفائدة التي جنيناها من استخدام المولّدات هي التخلّص من تعقيد الاستدعاءات الرّاجعة نهائيًّا وتحويل شيفرة غير متزامنة وجعلها تبدو وكأنّها متزامنة. ذكرنا القليل عن مكتبات مثل co وsuspend، لكنّها باختصار تعمل بطريقة مماثلة جدًا لمثالنا الأخير:
var suspend = require('suspend'),
resume = suspend.resume;
suspend(function*(){
var data = yield fs.readFile(__filename, 'utf8', resume());
console.log(data);
})();
هذه المكتبات خطوة نحو مستقبلJavaScript، الذي بدأ يتشكل مع مشروع الدّوال غير المتزامنة باستخدام الكلمتين المفتاحيتين الجديدتين async وawait اللّتان ستتوفّران في الإصدار السّابع وتستندان في عملهما إلى أرضيّة الوعود (Promises) الّتي تتوفّر اليوم في ES6. سيكون بإمكاننا كتابة هذه الشيفرة بدل الاعتماد على المولّدات:
async function displayUserProfile() {
var user = await getJSON("http://reading-website.com/users/fwz.json");
var num_books = user.books.length;
var most_recent_book_id = user.books[num_books - 1];
var user_rating = await getJSON("http://reading-website.com/users/fwz/ratings/" + most_recent_book_id + ".json");
var book = await getJSON("http://reading-website.com/books/" + most_recent_book_id + ".json");
var fragment = document.createDocumentFragment();
var h2 = document.createElement("h2");
h2.innerText = user.full_name;
var h3 = document.createElement("h2");
h2.innerText = "الكتب التي قرأها";
for (letbook of books) {
let li = document.createElement("li");
li.innerText = book.title + (book.id ==most_recent_book_id ? " " +user_rating : "");
fragment.appendChild(li);
}
list.appendChild(fragment);
}
في هذا المثال يجب على getJSON أن تُعيد وعدًا Promise ليستطيع مُفسّر اللّغة انتظاره إلى أن يُحقّق (resolve) أو يُرفض(reject)، والقيمة الّتي تُحقّق تُحفظ ضمن المُتغيّر user، وأما عند رفض الوعد يُرمى خطأ (throw) يمكن تلقّيه(catch) كما في الشيفرة غير المتزامنة.
الدرس الخامس من javascript
مُعامِل البقيّة (Rest parameter) والناشرة (Spread)
بعد كلّ هذا الكلام المُعقّد عن الأشياء غير المتزامنة التي نريد جعلها تبدو متزامنة وما إلى ذلك، سنختم الجزء الثّاني بفكرتين بسيطتين أُضيفتا إلى ECMAScript في الإصدار السّادس وتحلّان مشكلتين شائعتين في كثير من اللّغات البرمجيّة:
أمّا الأولى فهي الحاجة إلى تنفيذ نصّ برمجيّ ضمن دالة على عدد غير معروف من المُعاملات، فلنفترض أنّ لدينا دالة تجمع عددين:
function add(n1, n2) {
return n1 + n2;
}
add(1)
// 1
add(1, 2)
// 3
ونظرًا لكوننا مبرمجين أذكياء فقد قرّرنا جعل الدّالة تقبل أي عددين أو ثلاثة أو أكثر... لنجعلها تقبل عددًا لا نهائيًّا من الأعداد؛ في الإصدار الحالي سنلجأ إلى استخدام الكائن الخاصّ arguments المتوفّر ضمن نطاق كلّ دالّة تلقائيًا:
function add() {
return [].reduce.call(arguments, function(memo, n) { returnmemo + n; });
}
add(1)
// 1
add(1, 2)
// 3
add(1, 2, 3)
// 6
حسنًا لقد اضطررنا إلى "استعارة" دالة الاختزال من مصفوفة فارغة لتطبيقها على الكائن الخاص arguments الذي يُعتبر "شبيه مصفوفة" ولا يملك ما تمتلكه المصفوفة من دوالّ، لماذا لا يمكننا كتابة هذا فحسب:
function add(...numbers) {
return numbers.reduce(function(memo, n) { return memo + n; });
}
add(1)
// 1
add(1, 2)
// 3
add(1, 2, 3)
// 6
وأمّا الفكرة الثّانية فهي تكاد تكون عكس السّابقة، فإذا كانت الأولى تجمع بقيّة المعاملات في كائن مُفرَد، فإنّ هذه "تَنشر" محتويات المصفوفة إلى عناصرها المكوّنة لها، ماذا لو لم نكن أذكياء وعجزنا عن الإتيان بدالة تجمع عددًا غير منتهٍ من الأرقام:
function addThreeNumbers(n1, n2, n3) {
return n1 + n2 + n3;
}
var myNumbers = [1, 2, 3];
addThreeNumbers(...myNumbers);
// 6
لاحظ أن صياغة النشر (Spread) تطابق تمامًا صياغة البقيّة (Rest)، والاختلاف في السّياق فقط. لاحظ أيضًا أنّ معامل البقيّة، وكما يوحي اسمه، يمكن استخدامه لتجميع ما تبقى من مُعاملات الدّالة فقط:
function addThreeOrMoreNumbers(n1, n2, ...numbers) {
return n1 + n2 + numbers.reduce(function(memo, n) { return memo + n; });
}
addThreeOrMoreNumbers(1, 2, 3);
// 6
في الجزء القادم سنتعرف بمشيئة الله على الوحدات(Modules) التي تُعتبر طريقة جديدة لتنظيم الشفرة اُستلهمَت من عالم Node.js وrequire.js، وسُنلقي نظرة على الأصناف (Classes)، المكوّن البرمجيّ الذي وجد طريقه أخيرًا إلىJavaScriptّ!
JavaScript تستطيع أن تُنظّف أطباقك أيضا##


ليست هناك تعليقات:
إرسال تعليق