الكلمة المحجوزة this
في JavaScript
الكلمة المحجوزة this
لها سلوكٌ في JavaScript يختلف عن بقية لغات البرمجة، وهنالك اختلافاتٌ في سلوكها بين النمط strict و non-strict.
في أغلبية الحالات، تُحدَّد قيمة الكلمة المحجوزة this
وفق طريقة استدعاء الدالة، إذ لا يمكن ضبط قيمتها باستخدام عملية الإسناد العادية أثناء التنفيذ؛ وقد تختلف قيمة this
في كل مرة تُستدعى فيها الدالة.
أضافت ES5 الدالة bind
التي تضبط قيمة this
داخل الدالة بغض النظر عن طريقة استدعائها، وأضافت ES2015 الدوال السهمية (arrow functions) التي تُوفِّر ربطًا خاصًا بها للكلمة المحجوزة this
.
البنية العامة
this
السياق العام
تُشير الكلمة المحجوزة this
في سياق التنفيذ العام (أي خارج جميع الدوال) إلى الكائن العام (global object) سواءً كان التنفيذ في النمط strict أو non-strict.
// الكائن العام في المتصفحات هو window
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "HSOUB";
console.log(window.b); // "HSOUB"
console.log(b); // "HSOUB"
سياق الدوال
تختلف قيمة الكلمة المحجوزة this
داخل الدوال اعتمادًا على طريقة استدعاء الدالة.
استدعاء بسيط
المثال الآتي لن يُنفَّذ في نمط strict، ولأنَّ قيمة this ليست مضبوطةً باستخدام call
، فستُشير إلى الكائن العام (global object) والذي هو الكائن window
في المتصفح.
function f1() {
return this;
}
// متصفح
f1() === window; // true
// Node
f1() === global; // true
أما قيمة this في نمط strict فستبقى مساويةً إلى القيمة التي ضُبِطَت لها عند دخولها إلى سيبق التنفيذ، أي ستكون قيمة this
في المثال الآتي مساويةً إلى undefined
:
function f2() {
'use strict'; // strict mode
return this;
}
f2() === undefined; // true
أي لو كانت قيمة this
غير مُعرَّفة في سياق التنفيذ فستبقى undefined
، وذلك في النمط strict.
ملاحظة: يجب أن تكون قيمة this
في المثال السابق مساويةً إلى undefined
، ذلك لأنَّ الدالة f2
اُستدعيَت مباشرةً ولم تستدعَ كخاصية (property) لكائنٍ ما (مثلًا: window.f2()
)؛ لاحظ أنَّ هذه الميزة لم تكن مُطبّقةً في بعض المتصفحات عندما بدأت بدعم النمط strict، وكنتيجة لذلك قد تجدها تعيد الكائن window
خطأً.
لتمرير قيمة إلى this
من سياقٍ لآخر فيمكن استخدام الدالة call
أو apply
، لاحظ كيف أنشأنا كائنًا باسم obj
ووضعنا فيه الخاصية a
بقيمة معيّنة، ثم أنشأنا الخاصية a
في الكائن العام واستدعينا الدالة بثلاث طرائق مختلفة:
// الكائن الذي سيُمرَّر إلى الدالة
var obj = {a: 'Custom'};
// خاصية في الكائن العام
var a = 'Global';
function whatsThis(arg) {
return this.a; // هذه القيمة تختلف حسب طريقة استدعاء الدالة
}
whatsThis(); // 'Global'
whatsThis.call(obj); // 'Custom'
whatsThis.apply(obj); // 'Custom'
نجد أنَّه عندما تستخدم دالةٌ ما الكلمةَ المحجوزةَ this
في جسمها، فستكون قيمتها مرتبطةً بالكائن الذي مُرِّر إليها باستخدام الدالة call
أو apply
والتي ترثها كل الدوال من سلسلة prototype (عبر الخاصية Function.prototype
).
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// سيُمرَّ أوّل معامل إلى الدالة كقيمة this
// أما المعاملات التي تليه فستُمرَّر كوسائط إلى الدالة
add.call(o, 5, 7); // 16
// سيُمرَّ أوّل معامل إلى الدالة كقيمة this
// أما عناصر المصفوفة التي تليه فستُمرَّر كوسائط إلى الدالة
add.apply(o, [10, 20]); // 34
لاحظ أنَّه عند استخدام call
و apply
إذا كانت القيمة المُمرَّرة كقيمة للكلمة المحجوزة this
ليست كائنًا، فستتم محاولة تحويلها إلى كائن باستخدام العملية ToObject
، فلو مررنا قيمةً أوليّةً (primitive) مثل 7
أو 'foo'
، فستحوَّل إلى كائن باستخدام الدالة البانية المناسبة، أي أنَّ الرقم 7
سيُحوَّل إلى كائن باستخدام new Number(7)
والسلسلة النصية 'foo'
ستحوَّل إلى كائن باستخدام new String('foo')
:
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call('foo'); // [object String]
الدالة bind
أضافت ECMAScript 5 الدالة Function.prototype.bind
، وسيؤدي تنفيذ f.bind(someObject)
إلى إنشاء دالة جديدة لها نفس جسم ومجال الدالة f
لكن ستكون قيمة الكلمة المحجوزة this
في الدالة الجديدة مرتبطةً بالكائن المُمرَّر كأوّل وسيط للدالة bind
، بغض النظر عن كيفية استخدام الدالة:
function f() {
return this.a;
}
var g = f.bind({a: 'azerty'});
console.log(g()); // azerty
var h = g.bind({a: 'yoo'}); // لا يمكن تكرار العملية أكثر من مرة
console.log(h()); // azerty
var o = {a: 37, f: f, g: g, h: h};
console.log(o.f(), o.g(), o.h()); // 37, azerty, azerty
الدوال السهمية
في الدوال السهمية (arrow functions) ستأخذ this قيمتها من سياق التنفيذ، أي أنها ستساوي الكائن العام إذا كانت الشيفرة في المجال العام:
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
ملاحظة: إذا مررنا كائنًا إلى الدالة call
أو bind
أو apply
عند استدعاء دالة سهمية، فلن تُسنَد قيمته إلى this
وسيُهمَل، لكن ستُمرَّر بقيمة الوسائط إلى الدالة.
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// call
console.log(foo.call(obj) === globalObject); // true
// bind
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
بغض النظر عن طريقة استدعاء الدالة foo
، فستبقى الكلمة المحجوزة this
فيها تابعةً لسياق الإنشاء (وهي تساوي الكائن العام في المثال السابق)، وسينطبق المثال على الدوال السهمية المُنشَأة داخل الدوال الأخرى، إذ ستبقى الكلمة المحجوزة this
تُشير إلى السياق الذي أُنشِئت الدالة فيه:
var obj = {bar: function() {
var x = (() => this);
return x;
}
};
var fn = obj.bar();
console.log(fn() === obj); // true
var fn2 = obj.bar;
console.log(fn2()() == window); // true
في المثال السابق، أعادت الدالة (التي سنطلق عليها اسم الدالة المجهولة A) دالةً أخرى (سنطلق عليها اسم الدالة المجهولة B) التي هي دالةٌ سهميةٌ، والنتيجة هي ربط this
في الدالة B إلى قيمة this
للخاصية obj.bar
(الدالة A) عند استدعائها.
استخدام this
في دالة تابعة لكائن
عند استدعاء دالة تابعة لكائن، فستكون قيمة this
فيها مضبوطةٌ إلى الكائن الذي استدعاها؛ ففي المثال الآتي عندما ستُستدعى الدالة o.f()
فستُشير الكلمة المحجوزة this
فيها إلى الكائن o
:
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
لاحظ أنَّ هذا السلوك لن يتأثر بكيفية أو مكان إنشاء الدالة، ففي المثال السابق عرِّفنا الدالة على الخاصية f
عند تهيئة الكائن o
، لكن يمكنك تعريف الدالة أولًا ثم ربطها إلى الخاصية o.f
ولن يختلف أثرها أبدًا:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
المثال السابق يوضِّح أنَّ كل ما يهم هو أن تُستدعى الدالة من الخاصية f
للكائن o
.
لاحظ أنَّ this
سترتبط بأقرِّب كائن، ففي المثال الآتي سنستدعي الدالة g
التابعة للكائن o.b
، وستُشير this
فيها إلى الكائن o.b
، ولن يهم أنَّ الكائن o.b
هو عضوٌ في الكائن o
:
o.b = {g: independent, prop: 42};
console.log(o.b.g()); // 42
استخدام this
في سلسلة prototype
إذا كانت الدالة موجودةً في سلسلة prototype فستشير this
إلى الكائن الذي اُستدعيت الدالة عليها، كما لو كانت الدالةُ تابعةً للكائن مباشرةً:
var o = {f: function() { return this.a + this.b; }};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
الكائن المُسنَد إلى المتغير p
في المثال السابق لا يملك خاصيةً تابعةً له مباشرةً باسم f
، وإنما يرثها من سلسلة prototype، وعند استدعاء الدالة f
مع الكائن p
، فستشير الكلمة المحجوزة this
إلى الكائن p
؛ لاحظ أنَّ هذا السلوك مثير للاهتمام في طريقة الوراثة عبر سلسلة prototype في JavaScript.
استخدام this
مع الدوال البانية
عند استخدام إحدى الدوال كدالة بانية (constructor، مع الكلمة المحجوزة new
)، فسترتبط الكلمة المحجوزة this
مع الكائن الذي سيُنشَأ.
وصحيحٌ أنَّ السلوك الافتراضي للدالة البانية هو إعادة الكائن المُشار إليها عبر الكلمة المحجوزة this
، لكن يمكنها أن تُعيد كائنًا آخر.
function C() {
this.a = 37;
}
var o = new C();
console.log(o.a); // 37
function C2() {
this.a = 37;
return {a: 38};
}
o = new C2();
console.log(o.a); // 38
لاحظ كيف أُعيد كائنٌ أثناء الإنشاء في المثال الأخير (C2
)، وأُهمِلَ الكائن الذي كانت تُشير this
إليه.
استخدام this
للتعامل مع الأحداث في DOM
عندما تُستخدَم دالةٌ لمعالجة أحد الأحداث، فستُشير الكلمة المحجوزة this
إلى الكائن الذي أدى إلى إطلاق الحدث:
function bluify(e) {
// الشرط الآتي محقق دومًا
console.log(this === e.currentTarget);
// سيُحقَّق الشرط الآتي إذا كان
// currentTarget === target
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// الحصول على قائمة بجميع عناصر المستند
var elements = document.getElementsByTagName('*');
// إضافة الدالة للاستماع إلى حدث النقر على العنصر
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', bluify, false);
}
استخدام this
للتعامل مع الأحداث ضمن عناصر HTML
إذا استخدمنا الأحداث ضمن عناصر HTML، فستُضبَط قيمة this
إلى عنصر DOM الذي أدى إلى إطلاق الحدث. جرِّب الضغط على عنصر <button>
الآتي:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
نافذة التنبيه السابقة ستُظهِر الكلمة button
. لاحظ أنَّ الشيفرة الموجودة في أعلى مستوى ستكون قيمة this
فيها كما سبق:
<button onclick="alert((function() { return this; })());">
Show inner this
</button>
في هذه الحالة، ستُشير الكلمة المحجوزة this
داخل الدالة إلى الكائن العام.
دعم المتصفحات
الميزة | Chrome | Firefox | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
الدعم الأساسي | نعم | نعم | نعم | نعم | نعم |
مصادر ومواصفات
- مسودة المعيار ECMAScript Latest Draft.
- معيار ECMAScript 2015 (6th Edition).
- معيار ECMAScript 5.1.
- معيار ECMAScript 3rd Edition.
- معيار ECMAScript 1st Edition .