فهمیدن مقدار this
در جاوا اسکریپت می تواند مشکل باشد، در اینجا نحوه انجام آن آمده است…
جاوا اسکریپت this
موضوع بسیاری از جوکها است، و این به این دلیل است که، خوب، بسیار پیچیده است. با این حال، من دیده ام که توسعه دهندگان کارهای بسیار پیچیده تر و مختص دامنه را انجام می دهند تا از برخورد با این this
جلوگیری کنند. اگر در this
مورد مطمئن نیستید، امیدواریم این کمک کند. این this
من است.
من می خواهم با خاص ترین موقعیت شروع کنم و با کم خاص ترین وضعیت پایان دهم. این مقاله تا حدودی شبیه یک if (…) … else if () … else if (…) …
, بنابراین می توانید مستقیماً به اولین بخش بروید که با کد مورد نظر مطابقت دارد.
- اگر تابع به عنوان تابع فلش تعریف شده باشد
- در غیر این صورت، اگر تابع/کلاس با
new
فراخوانی شود - در غیر این صورت، اگر تابع دارای 'bound'
this
مقدار باشد - در غیر این صورت، اگر
this
در زمان تماس تنظیم شده باشد - در غیر این صورت، اگر تابع از طریق یک شی والد فراخوانی شود (
parent.func()
) - در غیر این صورت، اگر تابع یا محدوده والد در حالت سخت گیر باشد
- در غیر این صورت
اگر تابع به عنوان تابع فلش تعریف شده باشد:
const arrowFunction = () => {
console.log(this);
};
در این مورد، مقدار this
همیشه در محدوده والد this
است:
const outerThis = this;
const arrowFunction = () => {
// Always logs `true`:
console.log(this === outerThis);
};
توابع پیکان عالی هستند زیرا مقدار درونی this
نمی توان تغییر داد، همیشه مانند مقدار بیرونی this
است.
نمونه های دیگر
با توابع جهت دار، مقدار this
نمی توان با bind
تغییر داد:
// Logs `true` - bound `this` value is ignored:
arrowFunction.bind({foo: 'bar'})();
با توابع پیکان، مقدار this
نمی توان با call
یا apply
تغییر داد:
// Logs `true` - called `this` value is ignored:
arrowFunction.call({foo: 'bar'});
// Logs `true` - applied `this` value is ignored:
arrowFunction.apply({foo: 'bar'});
با توابع فلش، مقدار this
را نمی توان با فراخوانی تابع به عنوان عضوی از یک شی دیگر تغییر داد:
const obj = {arrowFunction};
// Logs `true` - parent object is ignored:
obj.arrowFunction();
با توابع فلش، مقدار this
نمی توان با فراخوانی تابع به عنوان سازنده تغییر داد:
// TypeError: arrowFunction is not a constructor
new arrowFunction();
روش های نمونه "محصول".
با متدهای نمونه، اگر می خواهید مطمئن شوید this
همیشه به نمونه کلاس اشاره می کند، بهترین راه استفاده از توابع پیکان و فیلدهای کلاس است:
class Whatever {
someMethod = () => {
// Always the instance of Whatever:
console.log(this);
};
}
این الگو هنگام استفاده از روشهای نمونه به عنوان شنونده رویداد در مؤلفهها (مانند مؤلفههای React یا مؤلفههای وب) واقعاً مفید است.
ممکن است در بالا به نظر برسد که قانون " this
همان خواهد بود this
در محدوده والد" است را زیر پا می گذارد، اما اگر فیلدهای کلاس را به عنوان قند نحوی برای تنظیم چیزها در سازنده در نظر بگیرید، منطقی به نظر می رسد:
class Whatever {
someMethod = (() => {
const outerThis = this;
return () => {
// Always logs `true`:
console.log(this === outerThis);
};
})();
}
// …is roughly equivalent to:
class Whatever {
constructor() {
const outerThis = this;
this.someMethod = () => {
// Always logs `true`:
console.log(this === outerThis);
};
}
}
پتن های جایگزین شامل اتصال یک تابع موجود در سازنده یا تخصیص تابع در سازنده است. اگر به دلایلی نمی توانید از فیلدهای کلاس استفاده کنید، تخصیص توابع در سازنده یک جایگزین منطقی است:
class Whatever {
constructor() {
this.someMethod = () => {
// …
};
}
}
در غیر این صورت، اگر تابع/کلاس با new
فراخوانی شود:
new Whatever();
موارد بالا Whatever
(یا تابع سازنده آن اگر یک کلاس باشد) را با this
مجموعه به نتیجه Object.create(Whatever.prototype)
فراخوانی می کند.
class MyClass {
constructor() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
}
// Logs `true`:
new MyClass();
همین امر برای سازندگان سبک قدیمیتر نیز صادق است:
function MyClass() {
console.log(
this.constructor === Object.create(MyClass.prototype).constructor,
);
}
// Logs `true`:
new MyClass();
نمونه های دیگر
وقتی با new
فراخوانی می شود، مقدار this
نمی توان با bind
تغییر داد:
const BoundMyClass = MyClass.bind({foo: 'bar'});
// Logs `true` - bound `this` value is ignored:
new BoundMyClass();
هنگامی که با new
فراخوانی می شود، مقدار this
را نمی توان با فراخوانی تابع به عنوان عضوی از یک شی دیگر تغییر داد:
const obj = {MyClass};
// Logs `true` - parent object is ignored:
new obj.MyClass();
در غیر این صورت، اگر تابع دارای 'bound' باشد، this
مقدار:
function someFunction() {
return this;
}
const boundObject = {hello: 'world'};
const boundFunction = someFunction.bind(boundObject);
هر زمان که boundFunction
فراخوانی شود، this
مقدار شیء ارسال شده برای bind
( boundObject
) خواهد بود.
// Logs `false`:
console.log(someFunction() === boundObject);
// Logs `true`:
console.log(boundFunction() === boundObject);
نمونه های دیگر
هنگام فراخوانی یک تابع محدود، مقدار this
نمی توان با call
یا apply
تغییر داد:
// Logs `true` - called `this` value is ignored:
console.log(boundFunction.call({foo: 'bar'}) === boundObject);
// Logs `true` - applied `this` value is ignored:
console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
هنگام فراخوانی یک تابع محدود، مقدار this
را نمی توان با فراخوانی تابع به عنوان عضوی از یک شی دیگر تغییر داد:
const obj = {boundFunction};
// Logs `true` - parent object is ignored:
console.log(obj.boundFunction() === boundObject);
در غیر این صورت، اگر this
در زمان تماس تنظیم شده باشد:
function someFunction() {
return this;
}
const someObject = {hello: 'world'};
// Logs `true`:
console.log(someFunction.call(someObject) === someObject);
// Logs `true`:
console.log(someFunction.apply(someObject) === someObject);
مقدار this
شیء ارسال شده برای call
/ apply
است.
متأسفانه this
توسط چیزهایی مانند شنوندگان رویداد DOM روی مقدار دیگری تنظیم شده است و استفاده از آن می تواند منجر به درک کدهای دشوار شود:
element.addEventListener('click', function (event) { // Logs `element`, since the DOM spec sets `this` to // the element the handler is attached to. console.log(this); });
من از استفاده this
در مواردی مانند بالا اجتناب می کنم و در عوض:
element.addEventListener('click', (event) => { // Ideally, grab it from a parent scope: console.log(element); // But if you can't do that, get it from the event object: console.log(event.currentTarget); });
در غیر این صورت، اگر تابع از طریق یک شی والد ( parent.func()
فراخوانی شود:
const obj = {
someMethod() {
return this;
},
};
// Logs `true`:
console.log(obj.someMethod() === obj);
در این حالت تابع به عنوان عضوی از obj
فراخوانی می شود، بنابراین this
obj
خواهد بود. این در زمان تماس اتفاق میافتد، بنابراین اگر تابع بدون شی والد یا با شی والد متفاوت فراخوانی شود، پیوند خراب میشود:
const {someMethod} = obj;
// Logs `false`:
console.log(someMethod() === obj);
const anotherObj = {someMethod};
// Logs `false`:
console.log(anotherObj.someMethod() === obj);
// Logs `true`:
console.log(anotherObj.someMethod() === anotherObj);
someMethod() === obj
نادرست است زیرا someMethod
به عنوان عضوی از obj
فراخوانی نمی شود . ممکن است هنگام امتحان کردن چیزی شبیه به این با این مشکل روبرو شده باشید:
const $ = document.querySelector;
// TypeError: Illegal invocation
const el = $('.some-element');
این مشکل به این دلیل است که اجرای querySelector
به this
مقدار نگاه میکند و انتظار دارد که یک نوع گره DOM باشد، و موارد بالا این ارتباط را قطع میکند. برای رسیدن به موارد فوق به درستی:
const $ = document.querySelector.bind(document);
// Or:
const $ = (...args) => document.querySelector(...args);
واقعیت جالب: همه APIها this
به صورت داخلی استفاده نمی کنند. روشهای کنسول مانند console.log
برای اجتناب از this
ارجاعها تغییر کردند، بنابراین لازم نیست log
به console
متصل شود.
در غیر این صورت، اگر تابع یا محدوده والد در حالت سختگیرانه باشد:
function someFunction() {
'use strict';
return this;
}
// Logs `true`:
console.log(someFunction() === undefined);
در این مورد، ارزش this
تعریف نشده است. اگر دامنه والد در حالت سخت گیر باشد (و همه ماژول ها در حالت سخت گیر هستند) 'use strict'
در تابع نیازی نیست.
در غیر این صورت:
function someFunction() {
return this;
}
// Logs `true`:
console.log(someFunction() === globalThis);
در این مورد، مقدار this
برابر با globalThis
است.
اوه!
و بس! این همه چیزی است که من در مورد this
می دانم. هر سوالی دارید؟ چیزی که من از دست داده ام؟ با خیال راحت برای من توییت کنید .
با تشکر از Mathias Bynens ، Ingvar Stepanyan ، و Thomas Steiner برای بررسی.