لفهم هذا بناءً على المكان الذي يوجد فيه هذا ، يمكن تقسيم الموقف تقريبًا إلى ثلاثة أنواع:
1. في الوظيفة: هذه عادة ما تكون معلمة ضمنية.
2. خارج الوظيفة (في النطاق العلوي): في المتصفح ، يشير هذا إلى الكائن العالمي ؛ في node.js يشير إلى صادرات الوحدات النمطية.
3. تم تمرير السلسلة إلى تقييم (): إذا تم استدعاء eval () مباشرة ، فإن هذا يشير إلى الكائن الحالي ؛ إذا تم تسمية eval () بشكل غير مباشر ، فإن هذا يشير إلى الكائن العالمي.
لقد أجرينا اختبارات مقابلة لهذه الفئات:
1. هذا في الوظيفة
يمكن أن تمثل الوظائف أساسًا جميع الهياكل التي تم استدعاؤها في JS ، لذلك هذا هو أيضًا السيناريو الأكثر شيوعًا حيث يتم استخدام هذا ، ويمكن تقسيم الوظائف إلى الأدوار الثلاثة التالية:
وظائف حقيقية
مُنشئ
طريقة
1.1 هذا في وظائف حقيقية
في الوظائف الحقيقية ، فإن قيمة هذا هو نمط يعتمد على السياق الذي يقع فيه.
وضع قذرة: يشير هذا إلى كائن عالمي (نافذة في المتصفح).
نسخة الكود كما يلي:
وظيفة sloppyfunc () {
console.log (هذا === نافذة) ؛ // حقيقي
}
sloppyfunc () ؛
الوضع الصارم: قيمة هذا غير محددة.
نسخة الكود كما يلي:
وظيفة StrictFunc () {
"استخدام صارم" ؛
console.log (هذا === غير محدد) ؛ // حقيقي
}
StrictFunc () ؛
هذه معلمة ضمنية لوظيفة ، لذلك تكون قيمتها هي نفسها دائمًا. ومع ذلك ، يمكنك تحديد هذه القيمة باستخدام طرق Call () أو تطبيق () لعرض القيمة.
نسخة الكود كما يلي:
وظيفة Func (Arg1 ، Arg2) {
console.log (هذا) ؛ // 1
console.log (arg1) ؛ // 2
console.log (arg2) ؛ // 3
}
func.call (1 ، 2 ، 3) ؛ // (هذا ، Arg1 ، Arg2)
func.apply (1 ، [2 ، 3]) ؛ // (هذا ، arraywithargs)
1.2 هذا في المنشئ
يمكنك استخدام وظيفة كمنشئ من خلال الجديد. تقوم العملية الجديدة بإنشاء كائن جديد ويمرر هذا الكائن إلى المُنشئ من خلال هذا.
نسخة الكود كما يلي:
var أنقذ هذا ؛
وظيفة const () {
ينقذ هذا = هذا ؛
}
var inst = new condr () ؛
console.log (SaveThis === inst) ؛ // حقيقي
يظهر مبدأ التنفيذ للعملية الجديدة في JS تقريبًا في الكود التالي (انظر هنا للحصول على تنفيذ أكثر دقة ، هذا التنفيذ أكثر تعقيدًا أيضًا):
نسخة الكود كما يلي:
وظيفة newOperator (const ، arraywithargs) {
var thisvalue = object.create (condr.prototype) ؛
const.apply (هذه القيمة ، arraywithargs) ؛
إرجاع هذه القيمة ؛
}
1.3 هذا في الطريقة
في الأساليب ، يميل استخدام هذا إلى أن يكون أكثر في اللغة التقليدية الموجهة للكائنات: يشير المتلقي إلى هذا ، أي الكائن الذي يحتوي على هذه الطريقة.
نسخة الكود كما يلي:
var obj = {
الطريقة: الدالة () {
console.log (هذا === OBJ) ؛ // حقيقي
}
}
obj.method () ؛
2. هذا في النطاق
في المتصفح ، النطاق هو النطاق العالمي ، وهذا يشير إلى هذا الكائن العالمي (مثل النافذة):
نسخة الكود كما يلي:
<script>
console.log (هذا === نافذة) ؛ // حقيقي
</script>
في node.js ، عادة ما تقوم بتنفيذ وظائف في الوحدات النمطية. لذلك ، فإن النطاق الأعلى هو نطاق الوحدة الخاصة جدًا:
نسخة الكود كما يلي:
// "عالمي" (وليس "نافذة") الرجوع إلى الكائن العالمي:
console.log (Math === Global.math) ؛ // حقيقي
// "هذا" لا يشير إلى الكائن العالمي:
console.log (هذا! == Global) ؛ // حقيقي
// "هذا" يشير إلى صادرات الوحدة النمطية:
console.log (هذا === module.exports) ؛ // حقيقي
3. هذا في eval ()
يمكن استدعاء eval () مباشرة (عن طريق استدعاء اسم الوظيفة "eval") أو بشكل غير مباشر (بوسائل أخرى ، مثل Call ()). لمزيد من التفاصيل ، يرجى الاطلاع هنا.
نسخة الكود كما يلي:
// وظائف حقيقية
وظيفة sloppyfunc () {
console.log (eval ('this') === window) ؛ // حقيقي
}
sloppyfunc () ؛
وظيفة StrictFunc () {
"استخدام صارم" ؛
console.log (eval ('this') === undefined) ؛ // حقيقي
}
StrictFunc () ؛
// البنائين
var أنقذ هذا ؛
وظيفة const () {
ينقذ هذا = eval ('هذا') ؛
}
var inst = new condr () ؛
console.log (SaveThis === inst) ؛ // حقيقي
// طُرق
var obj = {
الطريقة: الدالة () {
console.log (eval ('this') === OBJ) ؛ // حقيقي
}
}
obj.method () ؛
4. الفخاخ المتعلقة بهذا
يجب أن تكون حذراً بشأن الفخاخ الثلاثة المتعلقة بهذا والتي سيتم تقديمها أدناه. لاحظ أنه في المثال التالي ، يمكن أن يؤدي استخدام الوضع الصارم إلى تحسين أمان الرمز. نظرًا لأنه في الوظائف الحقيقية ، فإن قيمة هذا غير محددة ، ستحصل على تحذير عندما يحدث خطأ ما.
4.1 نسيت استخدام جديد
إذا كنت لا تستخدم جديدًا للاتصال بالمنشئ ، فأنت تستخدم فعليًا وظيفة حقيقية. لذلك لن تكون هذه هي القيمة التي تتوقعها. في الوضع القذر ، يشير هذا إلى النافذة وستنشئ متغيرات عالمية:
نسخة الكود كما يلي:
نقطة الوظيفة (x ، y) {
this.x = x ؛
this.y = y ؛
}
var p = point (7 ، 5) ؛ // ننسى الجديد!
console.log (p === undefined) ؛ // حقيقي
// تم إنشاء المتغيرات العالمية:
console.log (x) ؛ // 7
console.log (y) ؛ // 5
ومع ذلك ، إذا كنت تستخدم الوضع الصارم ، فستظل تحصل على تحذير (هذا === غير محدد):
نسخة الكود كما يلي:
نقطة الوظيفة (x ، y) {
"استخدام صارم" ؛
this.x = x ؛
this.y = y ؛
}
var p = point (7 ، 5) ؛
// typeerror: لا يمكن تعيين خاصية "x" من غير المحددة
4.2 طريقة الاستخدام غير لائقة
إذا حصلت مباشرة على قيمة الطريقة (لا تسميها) ، فأنت تستخدم هذه الطريقة كدالة. عندما تريد تمرير طريقة كمعلمة في وظيفة أو طريقة استدعاء ، فمن المرجح أن تفعل ذلك. هذا هو الحال مع SetTimeOut () ومعالجات أحداث التسجيل. سأستخدم طريقة Callit () لمحاكاة هذا السيناريو:
نسخة الكود كما يلي:
/** على غرار setTimeOut () و setImMediate ()*/
وظيفة callit (func) {
func () ؛
}
إذا قمت بتسمية طريقة كدالة في وضع قذرة ، فإن هذا * يشير إلى الكائن العالمي ، وبالتالي فإن الإنشاء في وقت لاحق سيكون متغيرات عالمية.
نسخة الكود كما يلي:
var counter = {
العد: 0 ،
// طريقة وضع قذرة
Inc: Function () {
this.count ++ ؛
}
}
callit (counter.inc) ؛
// لم يعمل:
console.log (counter.count) ؛ // 0
// بدلاً من ذلك ، تم إنشاء متغير عالمي
// (نان ناتج عن تطبيق ++ على غير محدد):
console.log (count) ؛ // نان
إذا قمت بذلك في وضع صارم ، فهذا غير محدد وما زلت لن تحصل على النتيجة المرجوة ، لكن على الأقل ستحصل على تحذير:
نسخة الكود كما يلي:
var counter = {
العد: 0 ،
// طريقة الوضع الصارم
Inc: Function () {
"استخدام صارم" ؛
this.count ++ ؛
}
}
callit (counter.inc) ؛
// typeerror: لا يمكن قراءة خاصية "عدد" العدد غير المحدد
console.log (counter.count) ؛
للحصول على النتائج المتوقعة ، يمكنك استخدام BIND ():
نسخة الكود كما يلي:
var counter = {
العد: 0 ،
Inc: Function () {
this.count ++ ؛
}
}
callit (counter.inc.bind (counter)) ؛
// عملت!
console.log (counter.count) ؛ // 1
BIND () ينشئ وظيفة أخرى يمكنها دائمًا تعيين هذه القيمة لمواجهتها.
4.3 إخفاء هذا
عندما تستخدم وظائف في الأساليب ، فغالبًا ما تتجاهل أن الوظائف لها هذا. هذا يختلف عن الطريقة ، لذلك لا يمكنك مزج هذين هذا معًا. للحصول على التفاصيل ، يرجى الاطلاع على الرمز التالي:
نسخة الكود كما يلي:
var obj = {
الاسم: "جين" ،
الأصدقاء: [Tarzan "،" Cheeta "] ،
حلقة: وظيفة () {
"استخدام صارم" ؛
this.friends.foreach (
وظيفة (صديق) {
console.log (this.name+'يعرف'+صديق) ؛
}
) ؛
}
} ؛
obj.loop () ؛
// typeerror: لا يمكن قراءة خاصية "اسم" غير محدد
لا يمكن استخدام هذا. اسم الوظيفة في المثال أعلاه لأن قيمة هذه الوظيفة غير محددة ، والتي تختلف عن هذا في حلقة الطريقة (). فيما يلي ثلاثة أفكار لحل هذه المشكلة:
1.
نسخة الكود كما يلي:
حلقة: وظيفة () {
"استخدام صارم" ؛
var that = هذا ؛
this.friends.foreach (وظيفة (صديق) {
console.log (that.name+'Knowledge'+friend) ؛
}) ؛
}
2. bind (). استخدم BIND () لإنشاء وظيفة. تحتوي هذه الوظيفة دائمًا على القيمة التي تريد تمريرها (في المثال التالي ، هذه الطريقة):
نسخة الكود كما يلي:
حلقة: وظيفة () {
"استخدام صارم" ؛
this.friends.foreach (وظيفة (صديق) {
console.log (this.name+'يعرف'+صديق) ؛
} .bind (هذا)) ؛
}
3. استخدم المعلمة الثانية من foreach. سيتم تمرير المعلمة الثانية من foreach إلى وظيفة رد الاتصال واستخدامها كدالة رد الاتصال.
نسخة الكود كما يلي:
حلقة: وظيفة () {
"استخدام صارم" ؛
this.friends.foreach (وظيفة (صديق) {
console.log (this.name+'يعرف'+صديق) ؛
}، هذا)؛
}
5. أفضل الممارسات
من الناحية النظرية ، أعتقد أن الوظيفة الحقيقية لا تنتمي إلى هذا ، والحل أعلاه يعتمد أيضًا على هذه الفكرة. يستخدم ECMASCRIPT 6 وظيفة السهم لتحقيق هذا التأثير ، وهي وظيفة لا تحتوي على هذا. في مثل هذه الوظيفة ، يمكنك استخدام هذه الإرادة ، دون القلق بشأن ما إذا كان هناك أي وجود ضمني.
نسخة الكود كما يلي:
حلقة: وظيفة () {
"استخدام صارم" ؛
// معلمة foreach () هي وظيفة سهم
this.friends.foreach (صديق => {
// "هذا" هو حلقة "هذا"
console.log (this.name+'يعرف'+صديق) ؛
}) ؛
}
لا أحب بعض واجهات برمجة التطبيقات التي تعتبر هذا معلمة إضافية لدالة حقيقية:
نسخة الكود كما يلي:
قبل الوظيفة () {
this.addmatchers ({
tobeinrange: وظيفة (ابدأ ، نهاية) {
...
}
}) ؛
}) ؛
كتابة معلمة ضمنية كما تم تمريرها بشكل صريح ، سيبدو الكود أفضل لفهمه ، وهذا يتوافق مع متطلبات وظيفة السهم:
نسخة الكود كما يلي:
قبل api => {
api.addmatchers ({
tobeinrange (ابدأ ، نهاية) {
...
}
}) ؛
}) ؛