الدوال السهمية في 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 يدعمها.

مصادر ومواصفات