การคำนวณค่าของ this
อาจเป็นเรื่องยากใน JavaScript วิธีการมีดังนี้...
this
ของ JavaScript เป็นมุกตลกหลายๆ เรื่อง ซึ่งก็เพราะว่ามันค่อนข้างซับซ้อน
อย่างไรก็ตาม เราเห็นว่านักพัฒนาแอปดำเนินการบางอย่างที่ซับซ้อนและเจาะจงโดเมนมากเพื่อหลีกเลี่ยงไม่ให้ต้องจัดการกับ this
นี้ หากคุณไม่แน่ใจเกี่ยวกับ this
หวังว่าข้อมูลนี้จะช่วยได้ นี่คือคู่มือปี this
ของฉัน
ฉันจะเริ่มด้วยสถานการณ์ที่เฉพาะเจาะจงมากที่สุด และจบลงด้วยสถานการณ์ที่เฉพาะเจาะจงน้อยที่สุด บทความนี้เป็นเหมือน if (…) … else if () … else if (…) …
ขนาดใหญ่ คุณจึงไปที่ส่วนแรกที่ตรงกับโค้ดที่กำลังดูได้ทันที
- หากกำหนดให้ฟังก์ชันเป็นฟังก์ชันลูกศร
- ไม่เช่นนั้น หากมีการเรียกฟังก์ชัน/คลาสด้วย
new
- หรือในกรณีที่ฟังก์ชันมีค่า
this
เป็น "bound" - มิเช่นนั้น หากตั้งค่า
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);
};
}
รูปแบบนี้มีประโยชน์มากเมื่อใช้เมธอดอินสแตนซ์เป็น Listener เหตุการณ์ในคอมโพเนนต์ (เช่น คอมโพเนนต์รีแอ็กชันหรือคอมโพเนนต์เว็บ)
การดำเนินการข้างต้นอาจดูเหมือนว่ามีการละเมิดกฎ "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();
หากฟังก์ชันมีค่า this
เป็น "bound" ให้ทำดังนี้
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
เป็นค่าอื่นตามบางสิ่ง เช่น Listener เหตุการณ์ 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
คุณอาจเคยเห็น Gotcha นี้เมื่อลองสิ่งต่อไปนี้
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 ที่ร่วมรีวิว