◐ Shell
clean mode source ↗

ربط الدوالّ Function binding

ثمّة مشكلة معروفة تواجهنا متى مرّرنا توابِع الكائنات على أنّها ردود نداء (كما نفعل مع ‎setTimeout‎)، هي ضياع هويّة الأنا ‎this‎.

سنرى في هذا الفصل طرائق إصلاح هذه المشكلة.

ضياع الأنا (الكلمة المفتاحية this)

رأينا قبل الآن أمثلة كيف ضاعت قيمة ‎this‎. فما نلبث أن مرّرنا التابِع إلى مكان آخر منفصلًا عن كائنه، ضاع ‎this‎.

إليك ظواهر هذه المشكلة باستعمال ‎setTimeout‎ مثلًا:

let user = {
  firstName: "John",
  sayHi() {
    alert(`‎Hello, ${this.firstName}!‎`);
  }
};

setTimeout(user.sayHi, 1000); // Hello, undefined!

كما رأينا في ناتج الشيفرة، لم نرحّب بالأخ «John» (كما أردنا باستعمال ‎this.firstName‎)، بل بالأخ غير المعرّف ‎undefined‎!

هذا لأنّ التابِع ‎setTimeout‎ استلم الدالة ‎user.sayHi‎ منفصلةً عن كائنها. يمكن أن نكتب السطر الأخير هكذا:

let f = user.sayHi;
setTimeout(f, 1000); // ‫ضاع سياق المستخدم user

بالمناسبة فالتابِع ‎setTimeout‎ داخل المتصفّحات يختلف قليلًا، إذ يضبط ‎this=window‎ حين نستدعي الدالة (بينما في Node.js يصير ‎this‎ هو ذاته كائن المؤقّت، ولكنّ هذا ليس بالأمر المهم الآن). يعني ذلك بأنّ ‎this.firstName‎ هنا هي فعليًا ‎window.firstName‎، وهذا المتغير غير موجود. عادةً ما تصير ‎this‎ غير معرّفة ‎undefined‎ في الحالات الأخرى.

كثيرًا ما نواجه هذه المسألة ونحن نكتب الشيفرة: نريد أن نمرّر تابِع الدالة إلى مكان آخر (مثل هنا، مرّرناه للمُجدول) حيث سيُستدعى من هناك. كيف لنا أن نتأكّد بأن يُستدعى في سياقه الصحيح؟

الحل رقم واحد: نستعمل دالة مغلفة

أسهل الحلول هو استعمال دالة غالِفة Wrapping function:

let user = {
  firstName: "John",
  sayHi() {
    alert(`‎Hello, ${this.firstName}!‎`);
  }
};

setTimeout(function() {
  user.sayHi(); // Hello, John!
}, 1000);

الآن اكتملت المهمة إذ استلمنا المستخدم ‎user‎ من البيئة المُعجمية الخارجية، وثمّ استدعينا التابِع كما العادة.

إليك ذات المهمة بأسطر أقل:

setTimeout(() => user.sayHi(), 1000); // Hello, John!

ممتازة جدًا، ولكن ستظهر لنا نقطة ضعف في بنية الشيفرة.

ماذا لو حدث وتغيّرت قيمة ‎user‎ قبل أن تعمل ‎setTimeout‎؟ (لا تنسَ التأخير، ثانية كاملة!) حينها سنجد أنّا استدعينا الكائن الخطأ دون أن ندري!

let user = {
  firstName: "John",
  sayHi() {
    alert(`‎Hello, ${this.firstName}!‎`);
  }
};

setTimeout(() => user.sayHi(), 1000);

// ‫...تغيّرت قيمة user خلال تلك الثانية
user = {
  sayHi() { alert("Another user in setTimeout!"); }
};

// setTimeout! هناك مستخدم آخر داخل التابِع‏

الحل الثاني سيضمن لنا ألّا تحدث هكذا أمور غير متوقّعة.

الحل رقم اثنين: ربطة

تقدّم لنا الدوال تابِعًا مضمّنًا في اللغة باسم bind يتيح لنا ضبط قيمة ‎this‎.

إليك صياغته الأساسية:

// ستأتي الصياغة المعقّدة لاحقًا لا تقلق
let boundFunc = func.bind(context);

ناتِج التابِع ‎func.bind(context)‎ هو «كائن دخيل» يشبه الدالة ويمكن لنا استدعائه على أنّه دالة، وسيمرّر هذا الاستدعاء إلى ‎func‎ بعدما يضبط ‎this=context‎ من خلف الستار.

أي بعبارة أخرى، لو استدعينا ‎boundFunc‎ فكأنّما استدعينا ‎func‎ بعدما ضبطنا قيمة ‎this‎.

إليك مثالًا تمرّر فيه ‎funcUser‎ الاستدعاء إلى ‎func‎ بضبط ‎this=user‎:

let user = {
  firstName: "John"
};

function func() {
  alert(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John

رأينا «النسخة الرابطة» من ‎func‎، ‏‎func.bind(user)‎ بعد ضبط ‎this=user‎.

كما أنّ المُعاملات كلّها تُمرّر إلى دالة ‎func‎ الأًصلية «كما هي». مثال:

let user = {
  firstName: "John"
};

function func(phrase) {
  alert(phrase + ', ' + this.firstName);
}

// ‫نربط this إلى user
let funcUser = func.bind(user);

funcUser("Hello"); // ‫Hello, John (مُرّر المُعامل "Hello" كما وُضبط this=user)

فلنجرّب الآن مع تابع لكائن:

let user = {
  firstName: "John",
  sayHi() {
    alert(`‎Hello, ${this.firstName}!‎`);
  }
};

let sayHi = user.sayHi.bind(user); // (*)

// يمكن أن نشغّلها دون وجود كائن
sayHi(); // Hello, John!

setTimeout(sayHi, 1000); // Hello, John!

// even if the value of user changes within 1 second
// sayHi uses the pre-bound value which is reference to the old user object
user = {
  sayHi() { alert("Another user in setTimeout!"); }
};

أخذنا في السطر ‎(*)‎ التابِع ‎user.sayHi‎ وربطناه مع المستخدم ‎user‎. ندعو الدالة ‎sayHi‎ بالدالة «المربوطة» حيث يمكن أن نستدعيها لوحدها هكذا أو نمرّرها إلى ‎setTimeout‎. مهما فعلًا فسيكون السياق صحيحًا كما نريد.

نرى هنا أنّ المُعاملات مُرّرت «كما هي» وما ضبطه ‎bind‎ هو قيمة ‎this‎ فقط:

let user = {
  firstName: "John",
  say(phrase) {
    alert(`‎${phrase}, ${this.firstName}!‎`);
  }
};

let say = user.say.bind(user);

say("Hello"); // ‫Hello, John!‎ (مُرّر المُعامل "Hello" إلى say)
say("Bye"); // ‫Bye, John!‎ (مُرّر المعامل "Bye" إلى say)

تابِع مفيد: ‎bindAll‎ لو كان للكائن توابِع كثيرة وأردنا تمريرها هنا وهناك بكثرة، فربّما نربطها كلّها في حلقة:

for (let key in user) {
  if (typeof user[key] == 'function') {
    user[key] = user[key].bind(user);
  }
}

JavaScript libraries also provide functions for convenient mass binding , e.g. _.bindAll(object, methodNames) in lodash.

## الدوال الجزئية

طوال هذه الفترة لم نُناقش شيئًا إلّا ربط `‎this‎`. لنُضيف شيئًا آخر على الطاولة.

يمكن أيضًا أن نربط المُعاملات وليس `‎this‎` فحسب. صحيح أنّا نادرًا ما نفعل ذلك إلّا أنّ الأمر مفيد في أحيان عصيبة.

صياغة `‎bind‎` الكاملة:

```
let bound = func.bind(context, [arg1], [arg2], ...);
```

وهي تسمح لنا بربط السياق ليكون `‎this‎` والمُعاملات الأولى في الدالة.

نرى مثالًا: دالة ضرب `‎mul(a, b)‎`:

```
function mul(a, b) {
  return a * b;
}
```

فلنستعمل `‎bind‎` لنصنع دالة «ضرب في اثنين» `‎double‎` تتّخذ تلك أساسًا لها:

```
function mul(a, b) {
  return a * b;
}

let double = mul.bind(null, 2);

alert( double(3) ); // = mul(2, 3) = 6
alert( double(4) ); // = mul(2, 4) = 8
alert( double(5) ); // = mul(2, 5) = 10
```

يصنع استدعاء `‎mul.bind(null, 2)‎` دالةً جديدة `‎double‎` تُمرّر الاستدعاءات إلى `‎mul‎` وتضبط `‎null‎` ليكون السياق و`‎2‎` ليكون المُعامل الأول. الباقي من مُعاملات يُمرّر «كما هو».

هذا ما نسمّيه [باستعمال الدوال الجزئية](https://en.wikipedia.org/wiki/Partial_application) -- أن نصنع دالة بعد ضبط بعض مُعاملات واحدة غيرها.

Please note that we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`.

الدالة `‎triple‎` أسفله تضرب القيمة في ثلاثة:

```
function mul(a, b) {
  return a * b;
}

let triple = mul.bind(null, 3);

alert( triple(3) ); // = mul(3, 3) = 9
alert( triple(4) ); // = mul(3, 4) = 12
alert( triple(5) ); // = mul(3, 5) = 15
```

ولكن لماذا نصنع الدوال الجزئية أصلًا، وعادةً؟!

الفائدة هي إنشاء دالة مستقلة لها اسم سهل القراءة (`‎double‎` أو `‎triple‎`)، فنستعملها دون تقديم المُعامل الأول في كلّ مرة إذ ضبطنا قيمته باستعمال `‎bind‎`.

وهناك حالات أخرى يفيدنا الاستعمال الجزئي هذا حين نحتاج نسخة أكثر تحديدًا من دالة عامّة جدًا، ليسهُل استعمالها فقط.

فمثلًا يمكن أن نصنع الدالة `‎send(from, to, text)‎`. وبعدها في كائن المستخدم `‎user‎` نصنع نسخة جزئية عنها: `‎sendTo(to, text)‎` تُرسل النصّ من المستخدم الحالي.

## الجزئية، بدون السياق

ماذا لو أردنا أن نضبط بعض المُعاملات ولكن دون السياق `‎this‎`؟ مثلًا نستعملها لتابِع أحد الكائنات.

تابِع `‎bind‎` الأصيل في اللغة لا يسمح بذلك، ومستحيل أن نُزيل السياق ونضع المُعاملات فقط.

لكن لحسن الحظ فيمكننا صنع دالة مُساعدة `‎partial‎` تربط المُعاملات فقط.

هكذا تمامًا:

```
function partial(func, ...argsBound) {
  return function(...args) { // (*)
    return func.call(this, ...argsBound, ...args);
  }
}

// الاستعمال:
let user = {
  firstName: "John",
  say(time, phrase) {
    alert(`‎[${time}] ${this.firstName}: ${phrase}!‎`);
  }
};

// نُضيف تابِعًا جزئيًا بعد ضبط الوقت
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());

user.sayNow("Hello");
// وسيظهر ما يشبه الآتي:
// [10:00] John: Hello!
```

ناتِج استدعائنا للدالة `‎partial(func[, arg1, arg2...])‎` هو غِلاف `‎(*)‎` يستدعي الدالة `‎func‎` هكذا:
- يترك `‎this‎` كما هو (فتكون قيمته `‎user‎` داخل الاستدعاء `‎user.sayNow‎`)
- ثمّ يمرّر لها `‎...argsBound‎`: أي المُعاملات من استدعاء `‎partial‎` ‏(`‎"10:00"‎`)
- وثمّ يمرّر لها `‎...args‎`: المُعاملات الممرّرة للغِلاف (`‎"Hello"‎`)

ساعدنا مُعامل التوزيع كثيرًا هنا، أم لا؟

كما أنّ هناك شيفرة [`‎_.partial`](https://lodash.com/docs#partial)  في المكتبة lodash.

## ملخص

يُعيد التابِع `‎func.bind(context, ...args)‎` «نسخة مربوطة» من الدالة `‎func‎` بعد ضبط سياقها `‎this‎` ومُعاملاتها الأولى (في حال مرّرناها).

عادةً ما نستعمل `‎bind‎` لنضبط `‎this‎` داخل تابِع لأحد الكائنات، فيمكن أن نمرّر التابِع ذلك إلى مكان آخر، مثلًا إلى `‎setTimeout‎`.

وحين نضبط بعضًا من مُعاملات إحدى الدوال، يكون الناتج (وهو أكثر تفصيلًا) دالةً ندعوها بالدالة *الجزئية* أو *المطبّقة بنحوٍ جزئي* _partially applied_.

تُفيدنا هذه الدوال الجزئية حين لا نريد تكرار ذات الوسيط مرارًا وتكرارًا، مثل دالة `‎send(from, to)‎` حيث يجب أن يبقى `‎from‎` كما هو في مهمّتنا هذه، فنأخذ دالة جزئية ونتعامل بها.
## تمارين
### دالة ربط على أنّها تابِع
_الأهمية: 5_

ما ناتج هذه الشيفرة؟

```
function f() {
  alert( this ); // ؟
}

let user = {
  g: f.bind(null)
};

user.g();
```

#### الحل
الجواب هو: `‎null‎`.

سياق دالة الربط مكتوب في الشيفرة (hard-coded) ولا يمكن تغييره لاحقًا بأيّ شكل من الأشكال.

فحتّى لو شغّلنا `‎user.g()‎` فستُستدعى الدالة الأصلية بضبط `‎this=null‎`.
### ربطة ثانية
_الأهمية: 5_

هي يمكن أن نغيّر قيمة `‎this‎` باستعمال ربطة إضافية؟

ما ناتج هذه الشيفرة؟

```
function f() {
  alert(this.name);
}

f = f.bind( {name: "John"} ).bind( {name: "Ann" } );

f();
```

#### الحل
الجواب هو: **John**.

```
function f() {
  alert(this.name);
}

f = f.bind( {name: "John"} ).bind( {name: "Pete"} );

f(); // John
```

لا يتذكّر كائن [دالة الربط](https://tc39.github.io/ecma262/#sec-bound-function-exotic-objects) «الدخيل» (الذي يُعيده `‎f.bind(...)‎`) السياق (مع الوُسطاء إن مُرّرت) - لا يتذكّر هذا كلّه إلى وقت إنشاء الكائن.

أي: لا يمكن إعادة ربط الدوال.
### خاصية الدالة بعد الربط
_الأهمية: 5_

تمتلك خاصية إحدى الدوال قيمة ما. هل ستتغيّر بعد `‎bind‎`؟ نعم، لماذا؟ لا، لماذا؟

```
function sayHi() {
  alert( this.name );
}
sayHi.test = 5;

let bound = sayHi.bind({
  name: "John"
});

alert( bound.test ); // ما الناتج؟ لماذا؟
```

#### الحل
الجواب هو: `‎undefined‎`.

ناتِج `‎bind‎` هو كائن آخر، وليس في هذا الكائن خاصية `‎test‎`.

### أصلِح هذه الدالة التي يضيع «this» منها
_الأهمية: 5_

على الاستدعاء `‎askPassword()‎` في الشيفرة أسفله فحص كلمة السر، ثمّ استدعاء `‎user.loginOk/loginFail‎` حسب نتيجة الفحص.

ولكن أثناء التنفيذ نرى خطأً. لماذا؟

أصلِح الجزء الذي فيه `‎(*)‎` لتعمل الشيفرة كما يجب (تغيير بقية الأسطر ممنوع).

```
function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  loginOk() {
    alert(`‎${this.name} logged in‎`);
  },

  loginFail() {
    alert(`‎${this.name} failed to log in‎`);
  },

};

askPassword(user.loginOk, user.loginFail); // (*)
```
#### الحل
سبب الخطأ هو أنّ الدالة `‎ask‎` تستلم الدالتين `‎loginOk/loginFail‎` دون كائنيهما.

فمتى ما استدعتهما، تُعدّ `‎this=undefined‎` بطبيعتها.

علينا ربط السياق!

```
function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  loginOk() {
    alert(`‎${this.name} logged in‎`);
  },

  loginFail() {
    alert(`‎${this.name} failed to log in‎`);
  },

};

// (*)\maskPassword(user.loginOk.bind(user), user.loginFail.bind(user));
```

الآن صارت تعمل.

أو، بطريقة أخرى:
```
//...
askPassword(() => user.loginOk(), () => user.loginFail());
```

هذه الشيفرة تعمل وعادةً ما تكون سهلة القراءة أيضًا.

ولكنّها في حالات أكثر تعقيدًا تصير أقلّ موثوقية، مثل لو تغيّر المتغير `‎user‎` *بعدما* استُدعيت الدالة `‎askPassword‎` و*قبل* أن يُجيب الزائر على الاستدعاء `‎() => user.loginOk()‎`.
### استعمال الدوال الجزئية لولوج المستخدم

هذا التمرين معقّد أكثر من سابقه، بقليل.

هنا تعدّل كائن `‎user‎`، فصار فيه بدل الدالتين `‎loginOk/loginFail‎` دالة واحدة `‎user.login(true/false)‎`.

ما الأشياء التي نمرّرها إلى `‎askPassword‎` في الشيفرة أسفله فتستدعي `‎user.login(true)‎` باستعمال `‎ok‎` وتستدعي `‎user.login(false)‎` باستعمال `‎fail‎`؟

```
function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  login(result) {
    alert( this.name + (result ? ' logged in' : ' failed to log in') );
  }
};

askPassword(?, ?); // ؟ (*)
```

يجب أن تعدّل الجزء الذي عليه `‎(*)‎` فقط لا غير.

#### الحل
1. نستعمل دالة غالِفة... سهمية لو أردنا التفصيل:

    ```
    askPassword(() => user.login(true), () => user.login(false));
    ```

    هكذا تأخذ `‎user‎` من المتغيرات الخارجية وتُشغّل الدوال بالطريقة العادية.

2. أو نصنع دالة جزئية من `‎user.login‎` تستعمل `‎user‎` سياقًا لها ونضع مُعاملها الأول كما يجب:


    ```
    askPassword(user.login.bind(user, true), user.login.bind(user, false));
    ```

ترجمة -وبتصرف- للفصل [Function binding](https://javascript.info/bind) من كتاب [The JavaScript language](https://javascript.info/js)