الدوال السهمية في JavaScript
تعبير الدوال السهمية (arrow function expression) له بنيةٌ عامةٌ مختصرةٌ أكثر من تعبير تعريف الدوال، ولا يملك قيم this
أو arguments
أو super
أو new.target
خاصة به؛ والدوال السهمية تناسب الدوال التي لا تكون أعضاءً في كائنٍ ما، وتلك التي لن تُستخدَم كدالة بانية.
البنية العامة
البنية الأساسية
(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// ستُقدَّر إلى: (param1, param2, …, paramN) => { return expression; }
// الأقواس اختيارية عندما يكون هنالك وسيطٌ وحيدٌ فقط
(singleParam) => { statements }
singleParam => { statements }
singleParam => expression
// إذا لم تكن هنالك معاملات للدالة فيجب وضع زوجٌ من الأقواس
() => { statements }
البنية المتقدمة
// وضع أقواس معقوفة ضمن جسم الدالة لإعادة كائن مُهيّئ بالطريقة المختصرة
params => ({foo: bar})
// من المدعوم استخدام معامل البقية والمعاملات الافتراضية
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements }
// ومن المدعوم تفكيك مصفوفة وإسنادها إلى المتغيرات
let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f();
// 6
الوصف
هنالك عاملان أساسيان ساهما في إضافة الدوال السهمية: الدوال القصيرة وعدم استخدام قيمة this
خاصة بالدالة.
الدوال القصيرة
لاحظ كيف استخدمنا دالةً مجهولةً لإعادةً طول كل سلسلة نصية موجودة ضمن المصفوفة materials
، ثم استخدمنا دالةً سهميةً لها نفس شكل الدالة المجهولة، ثم استخدمنا نسخة قصيرة منها أدت إلى تبسيط التعبير البرمجي كثيرًا:
var materials = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
materials.map(function(material) {
return material.length;
}); // [8, 6, 7, 9]
materials.map((material) => {
return material.length;
}); // [8, 6, 7, 9]
materials.map(material => material.length); // [8, 6, 7, 9]
قيمة this
في الدوال السهمية
قبل وجود الدوال السهمية، كانت كل دالة تُعرِّف قيمة this
خاصة بها (كائن جديد في حال كانت الدالةُ بانيةً، أو undefined
في النمط strict عند استدعاء الدوال، أو الكائن الأصلي إذا كانت الدالة جزءًا من كائن ...إلخ.)، لكن ذلك لم يكن ملائمًا لنمط البرمجة كائنية التوجه.
لاحظ أنَّ قيمة this
داخل الدالة البانية Person
ستُشير إلى نسخة الكائن المُنشَأ عبرها، أما الدالة growUp
فستُشير قيمة this
فيها إلى الكائن العام، والذي يختلف عن قيمة this
المُعرَّفة في الدالة البانية Person
:
function Person() {
// تُشير إلى نسخة من الكائن المُنشأ
this.age = 0;
setInterval(function growUp() {
// تُشير إلى الكائن العام
this.age++;
}, 1000);
}
var p = new Person();
لكن في ECMAScript 3/5 أصبحت مشكلة this
قابلةً للحل بإسناد قيمة this
إلى متغيرٍ آخر:
function Person() {
var that = this;
that.age = 0;
setInterval(function growUp() {
// الإشارة إلى المتغير الصحيح
that.age++;
}, 1000);
}
ويمكن استخدام طريقة بديلة عبر توظيف الدالة bind()
لإعادة إسناد قيمة this
إلى الدالة الهدف (الدالة growUp()
في المثال السابق).
الدوال السهمية لا تملك قيمة this
خاصة بها؛ إذ ستُستعمَل قيمة this
لسياق الاستدعاء، وبالتالي ستُمرَّر قيمة this
الموجودة داخل الدالة إلى الدالة السهمية التي استدعيناها ضمن setInterval
:
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // تُشير إلى المتغير الصحيح
}, 1000);
}
var p = new Person();
العلاقة مع نمط strict
لمّا كانت قيمة this
تأتي من سياق الاستدعاء، فسيتم تجاهل قواعد نمط strict التي تتعلق بقيمة this
.
var f = () => { 'use strict'; return this; };
f() === window; // أو الكائن العام
لاحظ أنَّ بقية قواعد نمط strict ستُطبَّق كالمعتاد.
استدعاء الدوال السهمية عبر call
أو apply
لمّا كانت الدوال السهمية لا تملك قيمة this خاصية بها، فإنَّ الدالة call()
أو apply()
يمكنها أن تُمرِّر وسائط إلى الدالة فقط، وسيتم تجاهل قيمة this
المُمرَّرة.
var adder = {
base: 1,
add: function(a) {
var f = v => v + this.base;
return f(a);
},
addThruCall: function(a) {
var f = v => v + this.base;
var b = {
base: 2
};
return f.call(b, a);
}
};
console.log(adder.add(1)); // 2
console.log(adder.addThruCall(1)); // 2 أيضًا
عدم وجود الكائن arguments
الدوال السهمية ليس لها نسخة من الكائن arguments، ففي المثال الآتي سيختلف ما يشير إليه arguments حسب المجال المحيط بها (enclosing scope):
var arguments = [1, 2, 3];
var arr = () => arguments[0];
arr(); // 1
function foo(n) {
var f = () => arguments[0] + n; // foo's arguments. arguments[0] = n
return f(10);
}
foo(1); // 2
في أغلبية الأحيان، يكون معامل البقية بديلًا مناسبًا للكائن arguments
:
function foo(n) {
var f = (...args) => args[0] + n;
return f(10);
}
foo(1); // 11
استخدام الدوال السهمية كجزء من كائن
كما ذكرنا سابقًا، تعابير الدوال السهمية مناسبة لاستخدامها دون أن تكون أعضاءً في أحد الكائنات، لكن لننظر ما سيحدث إذا حاولنا استخدمها كجزء من كائن:
'use strict';
var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function() {
console.log(this.i, this);
}
}
obj.b(); // undefined, Window {...} (أو الكائن العام)
obj.c(); // 10, Object {...}
الدوال السهمية لا تملك قيمة this
خاصة بها، وهذا مثالٌ آخر يستخدم الدالة Object.defineProperty()
:
'use strict';
var obj = {
a: 10
};
Object.defineProperty(obj, 'b', {
get: () => {
console.log(this.a, typeof this.a, this);
return this.a + 10; // ستُشير هنا إلى الكائن العام
}
});
استخدام المعامل new
لا يمكن استخدام الدوال السهمية كدوال بانية، وستُعيد خطأً عند إسباقها بالمعامل new
:
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
استخدام الخاصية prototype
لا تملك الدوال السهمية الخاصية prototype
.
var Foo = () => {};
console.log(Foo.prototype); // undefined
استخدام الكلمة المحجوزة yield
لا يجوز استخدام الكلمة المحجوزة yield
في جسم الدالة السهمية (لكن يُسمَح باستخدامها في حال وجود دوال متشعبة داخلها)، وبالتالي لا يمكن استخدام الدوال السهمية كدوال مولِّدة.
جسم الدالة
يمكن أن تملك الدوال السهمية جسمًا «مختصرًا» (concise) أو «كتليًا» (block).
يعطى تعبيرٌ وحيدٌ فقط في الجسم المختصر، والذي سيصبح القيمة المُعادة من الدالة، أما في الجسم الكتلي فيجب استخدام التعبير البرمجي return
لإعادة القيمة.
var func = x => x * x;
// الجسم المختصر، وستُعدّ قيمة التعبير هي القيمة المعادة
var func = (x, y) => { return x + y; };
// أما في الجسم الكتلي فيجب إعادة القيم يدويًا
إعادة كائنات من الدوال السهمية
أبقِ في ذهنك أنَّ إعادة كائنات مُهيّئة باستخدام «الجسم المختصر» params => {object:literal}
لن يعمل كما هو متوقع.
var func = () => { foo: 1 };
// func() تعيد undefined
var func = () => { foo: function() {} };
// SyntaxError: function statement requires a name
هذا لأنَّ الشيفرة الموجودة ضمن قوسين معقوفين {}
ستعامل على أنها سلسلة من التعابير، أي أنَّ foo
ستُعامَل على أنها لافتة (label) وليس على أنها مفتاح.
تذكر أن تضع الكائن المُهيّئ ضمن قوسين هلاليين ()
كما في المثال الآتي:
var func = () => ({foo: 1});
الأسطر الجديدة
لا يمكن أن تحتوي الدالة السهمية على سطر جديد بين المعاملات والسهم:
var func = ()
=> 1;
// SyntaxError: expected expression, got '=>'
ترتيب الأولوية
صحيحٌ أنَّ السهم في الدوال السهمية ليس معاملًا (operator) لكن للدوال السهمية قواعد خاصة تختلف عن الدوال العادية في ترتيب أولوية المعاملات.
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok
أمثلة إضافية
الدوال السهمية الفارغة ستعيد القيمة undefined
دومًا:
let empty = () => {};
يمكن أن نكتب دالةً سهميةً آنية الاستدعاء (Immediately Invoked Function Expression اختصارًا IIFE)، فالدالة السهمية الآتية ستعيد foobar
دومًا:
(() => 'foobar')();
استخدام المعامل الشرطي الثلاثي مع الدوال السهمية:
var simple = a => a > 15 ? 15 : a;
simple(16); // 15
simple(10); // 10
let max = (a, b) => a > b ? a : b;
لاحظ كيف تُسهِّل الدوال السهمية من استخدام دوال المصفوفات (ذكرنا في مثالنا الدالة reduce
و filter
و map
):
var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b);
// 66
var even = arr.filter(v => v % 2 == 0);
// [6, 0, 18]
var double = arr.map(v => v * 2);
// [10, 12, 26, 0, 2, 36, 46]
تكون قابلية قراءة الدوال السهمية دون وسائط أسهل بكثير، كما في المثال الآتي:
setTimeout( () => {
console.log('I happen sooner');
setTimeout( () => {
// deeper code
console.log('I happen later');
}, 1);
}, 1);
دعم المتصفحات
الميزة | Chrome | Firefox | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
الدعم الأساسي | 45 | 22 | غير مدعومة | 32 | 10 |
على الرغم أنَّ متصفح IE لا يدعم الدوال السهمية، لكن متصفح Edge يدعمها.
مصادر ومواصفات
- مسودة المعيار ECMAScript Latest Draft.
- معيار ECMAScript 2015 (6th Edition).