วิธีการเข้าถึง "นี่" ที่ถูกต้องในการติดต่อกลับ?


1425

ฉันมีฟังก์ชั่นการสร้างที่ลงทะเบียนจัดการเหตุการณ์:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', function () {
        alert(this.data);
    });
}

// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};

// called as
var obj = new MyConstructor('foo', transport);

อย่างไรก็ตามฉันไม่สามารถเข้าถึงdataคุณสมบัติของวัตถุที่สร้างขึ้นภายในการติดต่อกลับ ดูเหมือนthisว่าไม่ได้อ้างถึงวัตถุที่สร้างขึ้น แต่เป็นวัตถุอื่น

ฉันยังพยายามใช้วิธีวัตถุแทนฟังก์ชั่นที่ไม่ระบุชื่อ:

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', this.alert);
}

MyConstructor.prototype.alert = function() {
    alert(this.name);
};

แต่มันก็แสดงถึงปัญหาเดียวกัน

ฉันจะเข้าถึงวัตถุที่ถูกต้องได้อย่างไร


95
บางครั้งฉันก็เบื่อหน่ายกับคำถามบางประเภทฉันตัดสินใจที่จะเขียนคำตอบที่เป็นที่ยอมรับ แม้ว่าคำถามเหล่านี้จะได้รับคำตอบเหมือนล้านครั้ง แต่ก็เป็นไปไม่ได้เสมอที่จะพบคำถามที่ดี + คำตอบคู่ที่ไม่“ ปนเปื้อน” จากข้อมูลที่ไม่เกี่ยวข้อง นี่เป็นหนึ่งในช่วงเวลาเหล่านั้นและเป็นหนึ่งในคำถามเหล่านั้น (และฉันรู้สึกเบื่อ) หากคุณคิดว่าจริง ๆ แล้วมีคำถาม / คำตอบที่ดีสำหรับคำถามประเภทนี้โปรดแจ้งให้เราทราบและฉันจะลบคำถามนี้ออก ข้อเสนอแนะสำหรับการปรับปรุงยินดีต้อนรับ!
เฟลิกซ์คลิง



คำตอบ:


1790

สิ่งที่คุณควรรู้ this

this(aka "บริบท") เป็นคำหลักพิเศษภายในแต่ละฟังก์ชั่นและมูลค่าของมันขึ้นอยู่กับวิธีการเรียกใช้ฟังก์ชั่นไม่ใช่วิธี / เมื่อ / ที่มันถูกกำหนด มันไม่ได้รับผลกระทบจากขอบเขตศัพท์เช่นตัวแปรอื่น ๆ (ยกเว้นฟังก์ชั่นลูกศรดูด้านล่าง) นี่คือตัวอย่างบางส่วน:

function foo() {
    console.log(this);
}

// normal function call
foo(); // `this` will refer to `window`

// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`

// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`

ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการthisมีลักษณะที่เป็นเอกสาร MDN


วิธีการอ้างอิงที่ถูกต้อง this

อย่าใช้ this

จริงๆคุณไม่ต้องการที่จะเข้าถึงthisโดยเฉพาะอย่างยิ่ง แต่วัตถุที่มันหมายถึง นั่นเป็นสาเหตุที่ทางออกที่ง่ายคือเพียงสร้างตัวแปรใหม่ที่อ้างอิงถึงวัตถุนั้น ตัวแปรสามารถมีชื่อใด ๆ แต่คนทั่วไปและselfthat

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function() {
        alert(self.data);
    });
}

เนื่องจากselfเป็นตัวแปรปกติจึงเป็นไปตามกฎขอบเขตคำศัพท์และสามารถเข้าถึงได้ภายในการเรียกกลับ สิ่งนี้ยังมีข้อดีที่คุณสามารถเข้าถึงthisมูลค่าของการโทรกลับได้

ชุดthisโทรกลับ - ส่วนที่ 1 อย่างชัดเจน

อาจดูเหมือนว่าคุณไม่สามารถควบคุมค่าได้thisเนื่องจากค่าถูกตั้งค่าโดยอัตโนมัติ แต่นั่นไม่ใช่กรณี

ทุกฟังก์ชั่นมีเมธอด.bind [เอกสาร]ซึ่งจะส่งคืนฟังก์ชันใหม่ที่thisถูกผูกไว้กับค่า ฟังก์ชั่นนี้มีพฤติกรรมเช่นเดียวกับที่คุณเรียก.bindใช้เท่านั้นที่thisถูกกำหนดโดยคุณ ไม่ว่าจะเรียกฟังก์ชั่นนั้นหรือเมื่อใดก็ตามthisจะอ้างอิงถึงค่าที่ส่งผ่านเสมอ

function MyConstructor(data, transport) {
    this.data = data;
    var boundFunction = (function() { // parenthesis are not necessary
        alert(this.data);             // but might improve readability
    }).bind(this); // <- here we are calling `.bind()` 
    transport.on('data', boundFunction);
}

ในกรณีนี้เราจะมีผลผูกพันการเรียกกลับของthisค่าของ'sMyConstructorthis

หมายเหตุ:เมื่อเชื่อมโยงบริบทสำหรับ jQuery ให้ใช้jQuery.proxy [docs]แทน เหตุผลในการทำเช่นนี้คือเพื่อให้คุณไม่จำเป็นต้องจัดเก็บข้อมูลอ้างอิงไปยังฟังก์ชันเมื่อยกเลิกการเชื่อมโยงการเรียกกลับเหตุการณ์ jQuery จัดการภายในนั้น

ECMAScript 6: ใช้ฟังก์ชั่นลูกศร

ECMAScript 6 แนะนำฟังก์ชั่นลูกศรซึ่งถือได้ว่าเป็นฟังก์ชั่นแลมบ์ดา พวกเขาไม่มีthisผลผูกพัน แต่thisจะค้นหาในขอบเขตเหมือนตัวแปรปกติ .bindนั่นหมายความว่าคุณจะได้ไม่ต้องโทร นี่ไม่ใช่พฤติกรรมพิเศษเท่านั้นที่มีโปรดดูเอกสาร MDN สำหรับข้อมูลเพิ่มเติม

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', () => alert(this.data));
}

ชุดthisโทรกลับ - ส่วนที่ 2

ฟังก์ชั่น / วิธีการบางอย่างที่ยอมรับการเรียกกลับยังยอมรับค่าที่thisควรใช้ในการติดต่อกลับ นี่เป็นพื้นฐานเหมือนกับการผูกมันด้วยตัวคุณเอง แต่ฟังก์ชั่น / วิธีการทำเพื่อคุณ Array#map [เอกสาร]เป็นวิธีการดังกล่าว ลายเซ็นของมันคือ:

array.map(callback[, thisArg])

อาร์กิวเมนต์แรกคือการเรียกกลับและอาร์กิวเมนต์ที่สองคือค่าthisควรอ้างอิงถึง นี่คือตัวอย่างที่วางแผนไว้:

var arr = [1, 2, 3];
var obj = {multiplier: 42};

var new_arr = arr.map(function(v) {
    return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument

หมายเหตุ:thisโดยปกติจะกล่าวถึงในเอกสารของฟังก์ชั่น / วิธีการนั้นหรือไม่คุณสามารถส่งค่าได้ ตัวอย่างเช่นวิธีการของ jQuery [docs]$.ajaxอธิบายถึงตัวเลือกที่เรียกว่าcontext:

วัตถุนี้จะทำให้บริบทของการเรียกกลับที่เกี่ยวข้องกับอาแจ็กซ์ทั้งหมด


ปัญหาทั่วไป: การใช้วิธีการวัตถุเป็นตัวเรียกกลับ / ตัวจัดการเหตุการณ์

การประกาศทั่วไปของปัญหานี้คือเมื่อมีการใช้วิธีการวัตถุเป็นตัวจัดการการติดต่อกลับ / เหตุการณ์ ฟังก์ชั่นเป็นพลเมืองชั้นหนึ่งใน JavaScript และคำว่า "method" เป็นเพียงคำศัพท์สำหรับฟังก์ชั่นที่เป็นค่าของคุณสมบัติวัตถุ แต่ฟังก์ชั่นนั้นไม่มีลิงค์เฉพาะไปยังวัตถุ "บรรจุ"

ลองพิจารณาตัวอย่างต่อไปนี้:

function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = function() {
    console.log(this.data);
};

ฟังก์ชั่นthis.methodที่ได้รับมอบหมายเป็นตัวจัดการเหตุการณ์ค แต่ถ้าdocument.bodyมีการคลิกค่าลงทะเบียนจะเป็นundefinedเพราะภายในตัวจัดการเหตุการณ์ที่thisหมายถึงไม่ได้ตัวอย่างของdocument.body ดังกล่าวแล้วที่จุดเริ่มต้นสิ่งที่หมายถึงขึ้นอยู่กับวิธีการทำงานจะเรียกว่าไม่ว่ามันถูกกำหนดไว้ หากรหัสเป็นเหมือนดังต่อไปนี้อาจเป็นที่ชัดเจนมากขึ้นว่าฟังก์ชั่นไม่มีการอ้างอิงโดยนัยไปยังวัตถุ:Foo
this

function method() {
    console.log(this.data);
}


function Foo() {
    this.data = 42,
    document.body.onclick = this.method;
}

Foo.prototype.method = method;

การแก้ปัญหาเป็นเช่นเดียวกับที่กล่าวข้างต้น: ถ้ามีใช้.bindเพื่อผูกอย่างชัดเจนthisกับค่าเฉพาะ

document.body.onclick = this.method.bind(this);

หรือเรียกฟังก์ชันอย่างชัดเจนว่าเป็น "วิธีการ" ของวัตถุโดยใช้ฟังก์ชันที่ไม่ระบุตัวตนเป็นตัวจัดการการเรียกกลับ / เหตุการณ์และกำหนดวัตถุ ( this) ให้กับตัวแปรอื่น:

var self = this;
document.body.onclick = function() {
    self.method();
};

หรือใช้ฟังก์ชั่นลูกศร:

document.body.onclick = () => this.method();

39
เฟลิกซ์ฉันเคยอ่านคำตอบนี้มาก่อน แต่ไม่เคยตอบ ผมโตกังวลว่าคนใช้selfและการอ้างถึงthat thisฉันรู้สึกแบบนี้เพราะthisเป็นตัวแปรที่ใช้มากเกินไปในบริบทที่แตกต่างกัน ในขณะที่selfมักจะสอดคล้องกับตัวอย่างท้องถิ่นและthatมักจะหมายถึงวัตถุอื่น ฉันรู้ว่าคุณไม่ได้ตั้งกฎนี้ตามที่ฉันเห็นมันปรากฏในสถานที่อื่น ๆ แต่มันก็เป็นสาเหตุที่ฉันเริ่มใช้งาน_thisแต่ไม่แน่ใจว่าคนอื่นรู้สึกอย่างไรยกเว้นการปฏิบัติที่ไม่เหมือนกัน ที่มีผล
vol7ron

3
@FelixKling จะปลอดภัยหรือไม่หากสมมติว่าการใช้ฟังก์ชันต้นแบบนี้ภายในจะมีพฤติกรรมตามที่คาดหวังเสมอ เมื่อใช้การเรียกกลับภายในฟังก์ชันต้นแบบมีทางเลือกอื่นในการผูก () ตัวเองหรือว่า?
andig

5
@FelixKling มันจะมีประโยชน์ในช่วงเวลาที่ต้องพึ่งพาและFunction.prototype.call () Function.prototype.apply ()โดยเฉพาะอย่างยิ่งกับapply ()ฉันได้รับไมล์สะสมจำนวนมาก ฉันมีแนวโน้มที่จะใช้น้อยกว่าbind ()นิสัย แต่ฉันรู้ (แต่ไม่แน่ใจ) ว่าอาจมีข้อได้เปรียบเล็กน้อยในการใช้ผูกเหนือตัวเลือกอื่น ๆ
Nolo

5
คำตอบที่ยอดเยี่ยม แต่ให้พิจารณาเพิ่มโซลูชันเพิ่มเติมเพิ่มเติมซึ่งไม่ใช้คลาสใหม่หรือสิ่งนี้เลย
Aluan Haddad

4
ฟังก์ชั่นลูกศรใหม่ "แต่จะเป็นการค้นหาในขอบเขตเหมือนกับตัวแปรปกติ" คลิกนี้ทั้งหมดทำให้ฉันขอขอบคุณ! () => this.clicked();)
alphanumeric0101

211

นี่คือหลายวิธีในการเข้าถึงบริบทหลักภายในบริบทลูก -

  1. คุณสามารถใช้bind()ฟังก์ชั่น
  2. เก็บการอ้างอิงถึงบริบท / ตัวแปรนี้ภายในตัวแปรอื่น (ดูตัวอย่างด้านล่าง)
  3. ใช้ฟังก์ชั่นลูกศร ES6
  4. การเปลี่ยนแปลงรหัส / ฟังก์ชั่นการออกแบบ / สถาปัตยกรรม - สำหรับสิ่งนี้คุณควรมีคำสั่งเหนือ รูปแบบการออกแบบในจาวาสคริปต์

1. ใช้bind()ฟังก์ชั่น

function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data', ( function () {
        alert(this.data);
    }).bind(this) );
}
// Mock transport object
var transport = {
    on: function(event, callback) {
        setTimeout(callback, 1000);
    }
};
// called as
var obj = new MyConstructor('foo', transport);

หากคุณกำลังใช้underscore.js- http://underscorejs.org/#bind

transport.on('data', _.bind(function () {
    alert(this.data);
}, this));

2 เก็บการอ้างอิงถึงบริบท / ตัวแปรนี้ภายในตัวแปรอื่น

function MyConstructor(data, transport) {
  var self = this;
  this.data = data;
  transport.on('data', function() {
    alert(self.data);
  });
}

3 ฟังก์ชั่นลูกศร

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}

1
ตัวเลือก bind () นั้นน่าทึ่งมันเพิ่งจะผ่านตัวชี้ของวัตถุนี้เพื่อเป็นสิ่งนี้ในอีกวัตถุหนึ่ง (: ขอบคุณ!
Stav Bodik

การผูก () ทำงานเหมือนมีเสน่ห์ ขอบคุณ +1 จากฉันมาก :)
Anjana Silva

56

มันคือทั้งหมดที่อยู่ในไวยากรณ์ "เวทย์มนตร์" ของการเรียกวิธีการ:

object.property();

เมื่อคุณได้รับคุณสมบัติจากวัตถุและเรียกมันในครั้งเดียววัตถุจะเป็นบริบทสำหรับวิธีการ หากคุณเรียกใช้วิธีการเดียวกัน แต่ในขั้นตอนแยกบริบทจะเป็นขอบเขตทั่วโลก (หน้าต่าง) แทน:

var f = object.property;
f();

เมื่อคุณได้รับการอ้างอิงของวิธีการมันไม่ได้ถูกแนบไปกับวัตถุอีกต่อไปมันเป็นเพียงการอ้างอิงถึงฟังก์ชั่นธรรมดา สิ่งเดียวกันนี้เกิดขึ้นเมื่อคุณได้รับการอ้างอิงเพื่อใช้เป็นโทรกลับ:

this.saveNextLevelData(this.setAll);

นั่นคือที่ที่คุณจะผูกบริบทกับฟังก์ชัน:

this.saveNextLevelData(this.setAll.bind(this));

หากคุณใช้ jQuery คุณควรใช้$.proxyวิธีแทนเนื่องจากbindเบราว์เซอร์ทั้งหมดไม่รองรับ:

this.saveNextLevelData($.proxy(this.setAll, this));

33

ปัญหาเกี่ยวกับ "บริบท"

คำว่า "บริบท" บางครั้งใช้ในการอ้างถึงวัตถุอ้างอิงจากนี้ การใช้งานที่ไม่เหมาะสมเพราะมันไม่พอดีทั้งความหมายในทางเทคนิคหรือมีECMAScript ของนี้

"บริบท"หมายถึงสถานการณ์รอบ ๆ สิ่งที่เพิ่มความหมายหรือข้อมูลที่นำหน้าและต่อไปนี้ที่ให้ความหมายเพิ่มเติม คำว่า "บริบท" ถูกใช้ใน ECMAScript เพื่ออ้างถึงบริบทการดำเนินการซึ่งเป็นพารามิเตอร์ขอบเขตทั้งหมดและสิ่งนี้อยู่ในขอบเขตของรหัสการดำเนินการบางอย่าง

ดังแสดงในส่วนECMA-262 10.4.2 :

ตั้งค่า ThisBinding เป็นค่าเดียวกันกับ ThisBinding ของบริบทการเรียกใช้

ซึ่งระบุอย่างชัดเจนว่านี่เป็นส่วนหนึ่งของบริบทการดำเนินการ

บริบทการดำเนินการให้ข้อมูลโดยรอบที่เพิ่มความหมายให้กับรหัสที่กำลังดำเนินการ มันมีข้อมูลมากขึ้นกว่าเพียงแค่การผูกนี้

ดังนั้นคุณค่าของสิ่งนี้ไม่ใช่ "บริบท" เป็นเพียงส่วนหนึ่งของบริบทการดำเนินการ เป็นตัวแปรท้องถิ่นที่สามารถตั้งค่าโดยการเรียกไปยังวัตถุใด ๆ และในโหมดที่เข้มงวดเพื่อค่าใด ๆ เลย


ไม่เห็นด้วยกับคำตอบนี้ การมีอยู่ของคำว่า "บริบทการดำเนินการ" ไม่ผิดกฎหมายการใช้ "บริบท" อื่นใดนอกเหนือไปจากกฎหมายการใช้ "การดำเนินการ" อื่น ๆ อาจจะมีคำศัพท์ที่ใช้อธิบายได้ดีกว่าthisแต่ก็ไม่มีใครเสนอและมันก็สายเกินไปที่จะปิดประตูใน "บริบท"
Roamer-1888

@ Roamer-1888- ขอบคุณสำหรับการแก้ไข คุณพูดถูก แต่การโต้เถียงของฉันไม่ได้ขึ้นอยู่กับ "บริบทการดำเนินการ" ที่ตัดตอน "บริบท" เพื่อจุดประสงค์อื่น แต่มันขึ้นอยู่กับ "บริบท" ที่ไม่เหมาะสมจากทั้งมุมมองทางเทคนิคและความหมาย ฉันคิดว่าการใช้ "บริบท" แทน "สิ่งนี้" กำลังจะหมดไป ฉันไม่เห็นเหตุผลใด ๆ ที่จะหาคำศัพท์ทางเลือกนี้หรือผูกพันนี้มันแค่ทำให้งงงวยและหมายความว่าในบางจุดที่คุณต้องอธิบายว่า "บริบท" เป็นจริงสิ่งนี้และมันไม่ได้อยู่ใน "บริบท" :-)
RobG

ฉันไม่คิดว่าคุณสามารถพูดได้ว่านี่ไม่ใช่ "บริบท" ในทางใดทางหนึ่งเมื่อคุณยอมรับแล้วว่าเป็นส่วนหนึ่งของบริบทการดำเนินการที่ "การดำเนินการ" เป็นเพียงคำคุณศัพท์
Roamer-1888

@ Roamer-1888— ฉันจะไม่ดำเนินการสนทนาต่อไปจนกว่าจะถึงจุดนี้ ใช่นี่เป็นส่วนหนึ่งของบริบทการดำเนินการ บอกว่ามันเป็นบริบทเป็นเหมือนว่าผู้เล่นคนหนึ่งของทีมงานเป็นทีม
RobG

RobG น่าเสียดายที่คุณไม่ต้องการดำเนินการต่อ มันเป็นการอภิปรายที่น่าสนใจ ขอบคุณที่ให้เวลากับฉัน
Roamer-1888

31

คุณควรรู้เกี่ยวกับคำสำคัญ "นี้"

ตามมุมมองของฉันคุณสามารถใช้ "นี้" ในสามวิธี (ฟังก์ชั่นตนเอง / ลูกศร / วิธีผูก)

ฟังก์ชันของคำหลักนี้ทำงานแตกต่างกันเล็กน้อยใน JavaScript เมื่อเทียบกับภาษาอื่น

นอกจากนี้ยังมีความแตกต่างบางอย่างระหว่างโหมดเข้มงวดและโหมดไม่เข้มงวด

ในกรณีส่วนใหญ่ค่าของสิ่งนี้จะถูกกำหนดโดยวิธีการเรียกใช้ฟังก์ชัน

ไม่สามารถตั้งค่าโดยการมอบหมายในระหว่างการดำเนินการและอาจแตกต่างกันในแต่ละครั้งที่เรียกใช้ฟังก์ชัน

ES5 แนะนำวิธีการผูก () เพื่อตั้งค่าฟังก์ชั่นนี้โดยไม่คำนึงถึงวิธีการที่มันเรียกว่า

และ ES2015 แนะนำฟังก์ชั่นลูกศรซึ่งไม่ได้ให้การเชื่อมโยงนี้กับตัวเอง (มันจะเก็บค่านี้ของบริบทศัพท์ที่ล้อมรอบ)

วิธีที่ 1:ตัวเอง - ตัวเองกำลังถูกใช้เพื่อรักษาการอ้างอิงถึงต้นฉบับนี้แม้ว่าบริบทจะเปลี่ยนแปลง เป็นเทคนิคที่มักใช้ในตัวจัดการเหตุการณ์ (โดยเฉพาะอย่างยิ่งในการปิด)

การอ้างอิง : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

function MyConstructor(data, transport) {
    this.data = data;
    var self = this;
    transport.on('data', function () {
        alert(self.data);
    });
}

วิธีที่ 2 : ฟังก์ชั่นลูกศร - การแสดงออกฟังก์ชั่นลูกศรเป็นทางเลือกที่มีขนาดกะทัดรัด syntactically กับการแสดงออกฟังก์ชั่นปกติ

แม้ว่าจะไม่มีการเชื่อมโยงคำหลักข้อโต้แย้งสุดยอดหรือ new.target

การแสดงออกของฟังก์ชั่นลูกศรไม่เหมาะกับวิธีการและไม่สามารถใช้เป็นตัวสร้างได้

การอ้างอิง : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',()=> {
        alert(this.data);
    });
}

วิธีที่ 3 : ผูก - วิธีผูก () สร้างฟังก์ชั่นใหม่ที่

เมื่อถูกเรียกให้ตั้งค่าคีย์เวิร์ดนี้เป็นค่าที่ให้ไว้

ด้วยลำดับของอาร์กิวเมนต์ที่ได้รับก่อนหน้าใด ๆ ที่จัดไว้ให้เมื่อเรียกใช้ฟังก์ชันใหม่

การอ้างอิง: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

  function MyConstructor(data, transport) {
    this.data = data;
    transport.on('data',(function() {
        alert(this.data);
    }).bind(this);

25

ครั้งแรกที่คุณจะต้องมีความเข้าใจอย่างชัดเจนscopeและพฤติกรรมของคำหลักในบริบทของthisscope

this& scope:


there are two types of scope in javascript. They are :

   1) Global Scope

   2) Function Scope

ในระยะสั้นขอบเขตทั่วโลกหมายถึงวัตถุหน้าต่างตัวแปรที่ประกาศในขอบเขตทั่วโลกสามารถเข้าถึงได้จากทุกที่ในขอบเขตฟังก์ชั่นมืออื่น ๆ ที่อยู่ภายในของฟังก์ชั่นตัวแปรที่ประกาศภายในฟังก์ชั่นไม่สามารถเข้าถึงได้จากโลกภายนอกตามปกติ thisคำสำคัญในขอบเขตทั่วโลกหมายถึงวัตถุหน้าต่าง thisฟังก์ชั่นภายในยังหมายถึงวัตถุหน้าต่างดังนั้นthisมักจะอ้างถึงหน้าต่างจนกว่าเราจะหาวิธีการจัดการthisเพื่อระบุบริบทของการเลือกของเราเอง

--------------------------------------------------------------------------------
-                                                                              -
-   Global Scope                                                               -
-   ( globally "this" refers to window object)                                 -     
-                                                                              -
-         function outer_function(callback){                                   -
-                                                                              -
-               // outer function scope                                        -
-               // inside outer function"this" keyword refers to window object -                                                                              -
-              callback() // "this" inside callback also refers window object  -

-         }                                                                    -
-                                                                              -
-         function callback_function(){                                        -
-                                                                              -
-                //  function to be passed as callback                         -
-                                                                              -
-                // here "THIS" refers to window object also                   -
-                                                                              -
-         }                                                                    -
-                                                                              -
-         outer_function(callback_function)                                    -
-         // invoke with callback                                              -
--------------------------------------------------------------------------------

วิธีต่างๆในการจัดการกับthisฟังก์ชั่นการโทรกลับ:

ที่นี่ฉันมีฟังก์ชั่นตัวสร้างที่เรียกว่าบุคคล มันมีคุณสมบัติที่เรียกว่าnameสี่และวิธีการที่เรียกว่าsayNameVersion1, sayNameVersion2, ,sayNameVersion3 sayNameVersion4ทั้งสี่ของพวกเขามีงานเฉพาะอย่างหนึ่งรับโทรกลับและเรียกมันโทรกลับมีงานเฉพาะซึ่งจะบันทึกคุณสมบัติชื่อของอินสแตนซ์ของฟังก์ชั่นตัวสร้างบุคคล

function Person(name){

    this.name = name

    this.sayNameVersion1 = function(callback){
        callback.bind(this)()
    }
    this.sayNameVersion2 = function(callback){
        callback()
    }

    this.sayNameVersion3 = function(callback){
        callback.call(this)
    }

    this.sayNameVersion4 = function(callback){
        callback.apply(this)
    }

}

function niceCallback(){

    // function to be used as callback

    var parentObject = this

    console.log(parentObject)

}

ตอนนี้เรามาสร้างตัวอย่างจาก person constructor และเรียกใช้sayNameVersionXเมธอด (X อ้างถึง 1,2,3,4) รุ่นต่างๆniceCallbackเพื่อดูว่าเราสามารถจัดการกับthiscallback ภายในเพื่ออ้างอิงpersonได้อย่างไร

var p1 = new Person('zami') // create an instance of Person constructor

ผูก:

สิ่งที่ต้องทำคือการสร้างฟังก์ชั่นใหม่ที่มีthisชุดคำหลักเป็นค่าที่ให้ไว้

sayNameVersion1และsayNameVersion2ใช้ผูกเพื่อจัดการthisกับฟังก์ชั่นการโทรกลับ

this.sayNameVersion1 = function(callback){
    callback.bind(this)()
}
this.sayNameVersion2 = function(callback){
    callback()
}

คนแรกผูกthisกับการโทรกลับภายในวิธีการเองและสำหรับการโทรกลับครั้งที่สองจะถูกส่งผ่านกับวัตถุที่ถูกผูกไว้กับมัน

p1.sayNameVersion1(niceCallback) // pass simply the callback and bind happens inside the sayNameVersion1 method

p1.sayNameVersion2(niceCallback.bind(p1)) // uses bind before passing callback

โทร :

first argumentของcallวิธีการที่จะใช้เป็นthisภายในฟังก์ชันที่ถูกเรียกด้วยcallแนบมากับมัน

sayNameVersion3ใช้ callเพื่อจัดการการthisอ้างอิงถึงวัตถุบุคคลที่เราสร้างขึ้นแทนวัตถุหน้าต่าง

this.sayNameVersion3 = function(callback){
    callback.call(this)
}

และมันถูกเรียกว่าต่อไปนี้:

p1.sayNameVersion3(niceCallback)

ใช้:

คล้ายกับcallอาร์กิวเมนต์แรกของการapplyอ้างอิงถึงวัตถุที่จะถูกระบุด้วยthisคำหลัก

sayNameVersion4ใช้applyเพื่อจัดการthisเพื่ออ้างถึงวัตถุบุคคล

this.sayNameVersion4 = function(callback){
    callback.apply(this)
}

และมันถูกเรียกว่าดังต่อไปนี้เพียงแค่โทรกลับผ่าน

p1.sayNameVersion4(niceCallback)

1
คำติชมที่สร้างสรรค์ใด ๆ เกี่ยวกับคำตอบจะได้รับการชื่นชม!
AL-zami

1
คำหลักนี้ในขอบเขตทั่วโลกไม่จำเป็นต้องอ้างถึงวัตถุหน้าต่าง นั่นเป็นเรื่องจริงในเบราว์เซอร์เท่านั้น
Randall Flagg

1
@RandallFlagg ผมเขียนคำตอบนี้จากเบราว์เซอร์ perspective.Fell ฟรีเพื่อ inhance คำตอบนี้หากจำเป็น :)
AL-zami

19

เราไม่สามารถผูกสิ่งนี้ไว้setTimeout()ได้เพราะมันจะทำงานกับวัตถุทั่วโลก (หน้าต่าง)หากคุณต้องการเข้าถึงthisบริบทในฟังก์ชั่นการโทรกลับโดยใช้bind()ฟังก์ชันการโทรกลับที่เราสามารถทำได้ดังนี้:

setTimeout(function(){
    this.methodName();
}.bind(this), 2000);

9
สิ่งนี้แตกต่างจากคำตอบที่มีอยู่อย่างไร
เฟลิกซ์คลิง

13

คำถามนี้เกี่ยวกับการthisทำงานของคำหลักใน javascript thisพฤติกรรมแตกต่างกันดังต่อไปนี้

  1. ค่าของthisจะถูกกำหนดโดยบริบทการทำงานของฟังก์ชัน
  2. ในขอบเขตส่วนกลางthisหมายถึงวัตถุส่วนกลาง ( windowวัตถุ)
  3. หากเปิดใช้งานโหมดเข้มงวดสำหรับฟังก์ชั่นใด ๆ ค่าของthisจะundefinedเป็นในโหมดเข้มงวดวัตถุทั่วโลกหมายถึงundefinedสถานที่ของwindowวัตถุ
  4. วัตถุที่ยืนอยู่ตรงหน้าจุดนั้นเป็นสิ่งที่คำสำคัญนี้จะถูกผูกไว้กับ
  5. เราสามารถตั้งค่านี้อย่างชัดเจนด้วยcall(), bind()และapply()
  6. เมื่อใช้newคำสำคัญ (คอนสตรัคเตอร์) สิ่งนี้จะถูกผูกไว้กับวัตถุใหม่ที่กำลังสร้าง
  7. ฟังก์ชั่นลูกศรไม่ได้ผูกthis - แต่thisถูกผูกไว้ด้วยคำศัพท์ (เช่นตามบริบทดั้งเดิม)

เป็นที่สุดของคำตอบที่แนะนำเราสามารถใช้ฟังก์ชั่นลูกศรหรือbind()วิธีการหรือตนเอง var ฉันจะพูดถึงประเด็นเกี่ยวกับ lambdas (ฟังก์ชั่นลูกศร) จากคู่มือสไตล์ Google JavaScript

แนะนำให้ใช้ฟังก์ชั่นลูกศรบน f.bind (นี่) และโดยเฉพาะอย่างยิ่งใน goog.bind (f นี่คือสิ่งนี้) หลีกเลี่ยงการเขียน const self = สิ่งนี้ ฟังก์ชั่นลูกศรมีประโยชน์อย่างยิ่งสำหรับการโทรกลับซึ่งบางครั้งผ่านการขัดแย้งเพิ่มเติมที่ไม่คาดคิด

Google แนะนำให้ใช้ lambdas อย่างชัดเจนมากกว่าผูกหรือ const self = this

ดังนั้นทางออกที่ดีที่สุดคือใช้ lambdas ดังนี้

function MyConstructor(data, transport) {
  this.data = data;
  transport.on('data', () => {
    alert(this.data);
  });
}

อ้างอิง:

  1. https://medium.com/tech-tajawal/javascript-this-4-rules-7354abdb274c
  2. ลูกศรฟังก์ชั่น-VS-ผูก

คำถามนี้เกี่ยวกับการใช้ฟังก์ชั่น / วิธีเป็นการโทรกลับโดยเฉพาะ คำตอบของคุณอาจจะเป็นแบบที่ดีกว่าสำหรับstackoverflow.com/q/3127429/218196
เฟลิกซ์คลิง

@ FelixKling ใช่คำถามเกี่ยวกับการใช้ฟังก์ชั่น / วิธีการโทรกลับในปัญหาหลักนั้นเกิดจากการจัดการของthisคำหลักที่ว่าทำไมฉันแบ่งคำตอบของฉันในสองส่วนหนึ่งเกี่ยวกับthisและที่สองเกี่ยวกับการใช้ฟังก์ชั่น / วิธีการโทรกลับ อย่าลังเลที่จะแก้ไขคำตอบ
Code_Mode

ฉันพบว่าจุดที่สี่ของคุณพูดอย่างคลุมเครือ ลองพิจารณาตัวอย่าง“ ปัญหาเมื่อใช้วิธีการกับวัตถุนี้เป็นการเรียกกลับ”ที่วัตถุด้านขวายืนอยู่หน้าจุด แต่ยังบริบทไม่ใช่วัตถุนั้น
bleistift2

7

ปัจจุบันมีวิธีการอื่นที่เป็นไปได้หากมีการใช้คลาสในรหัส

ด้วยการสนับสนุนของคลาสฟิลด์ เป็นไปได้ที่จะทำให้เป็นวิธีถัดไป:

class someView {
    onSomeInputKeyUp = (event) => {
        console.log(this); // this refers to correct value
    // ....
    someInitMethod() {
        //...
        someInput.addEventListener('input', this.onSomeInputKeyUp)

แน่นอนภายใต้ฝากระโปรงมันเป็นฟังก์ชั่นลูกศรที่ดีเก่า ๆ ที่เชื่อมโยงบริบท แต่ในรูปแบบนี้มันดูชัดเจนกว่า

เนื่องจากมันเป็นขั้นตอนที่ 3 ข้อเสนอคุณจะต้องใช้บาเบลและปลั๊กอินที่เหมาะสมในการประมวลผลเป็นตอนนี้ (08/2018)


2
นี่คือวิธีที่ฉันทำให้มันทำงานใน typescript: public methodName = (params) => { body }ภายในชั้นเรียน
yeyeyerman

5

อีกวิธีหนึ่งซึ่งเป็นวิธีมาตรฐานตั้งแต่ DOM2ไปผูกthisไว้ภายใน listener เหตุการณ์ที่ช่วยให้คุณลบ listener (ท่ามกลางประโยชน์อื่น ๆ ) เป็นhandleEvent(evt)วิธีจากEventListenerinterface:

var obj = {
  handleEvent(e) {
    // always true
    console.log(this === obj);
  }
};

document.body.addEventListener('click', obj);

ข้อมูลรายละเอียดเกี่ยวกับการใช้handleEventสามารถดูได้ที่นี่: https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38


0

this ใน JS:

ค่าของthisใน JS คือ 100% ที่กำหนดโดยวิธีการเรียกใช้ฟังก์ชั่นและไม่ได้กำหนดไว้ เราสามารถที่ค่อนข้างง่ายหาค่าของthisโดย'ซ้ายของกฎ dot' :

  1. เมื่อฟังก์ชั่นถูกสร้างขึ้นโดยใช้คำหลักฟังก์ชั่นค่าของthisคือวัตถุที่เหลือของจุดของฟังก์ชั่นที่เรียกว่า
  2. หากไม่มีวัตถุที่เหลืออยู่ของจุดแล้วค่าของthisฟังก์ชั่นภายในมักจะเป็นวัตถุทั่วโลก ( globalในโหนดwindowในเบราว์เซอร์) ฉันจะไม่แนะนำให้ใช้thisคำหลักที่นี่เพราะมันมีความชัดเจนน้อยกว่าการใช้สิ่งที่ชอบwindow!
  3. มีอยู่สร้างฟังก์ชั่นบางอย่างเช่นลูกศรและฟังก์ชั่นสร้างขึ้นโดยใช้ฟังก์ชั่นที่สามารถแก้ไขค่าของFunction.prototype.bind() thisเหล่านี้เป็นข้อยกเว้นของกฎ thisแต่จริงๆเป็นประโยชน์ในการแก้ไขปัญหาค่าของ

ตัวอย่างใน nodeJS

module.exports.data = 'module data';
// This outside a function in node refers to module.exports object
console.log(this);

const obj1 = {
    data: "obj1 data",
    met1: function () {
        console.log(this.data);
    },
    met2: () => {
        console.log(this.data);
    },
};

const obj2 = {
    data: "obj2 data",
    test1: function () {
        console.log(this.data);
    },
    test2: function () {
        console.log(this.data);
    }.bind(obj1),
    test3: obj1.met1,
    test4: obj1.met2,
};

obj2.test1();
obj2.test2();
obj2.test3();
obj2.test4();
obj1.met1.call(obj2);

เอาท์พุท:

ป้อนคำอธิบายรูปภาพที่นี่

ให้ฉันแนะนำคุณผ่านผลลัพธ์ 1 ต่อ 1 (ละเว้นบันทึกแรกที่เริ่มจากวินาที):

  1. thisเป็นobj2เพราะด้านซ้ายของกฎจุดเราจะเห็นว่าtest1เรียกว่าobj2.test1();อย่างไร obj2เหลือของจุดและทำให้thisค่า
  2. แม้ว่าจะobj2เป็นจุดที่เหลือtest2จะถูกผูกไว้obj1ผ่านbind()วิธีการ ดังนั้นค่าthisobj1
  3. obj2เหลือจากจุดจากฟังก์ชันที่เรียกว่า: obj2.test3(). ดังนั้นจะมีค่าของobj2this
  4. ในกรณีนี้: obj2.test4() obj2เหลือของจุด อย่างไรก็ตามฟังก์ชั่นลูกศรไม่มีthisผลผูกพันของตนเอง ดังนั้นมันจะผูกกับthisค่าของขอบเขตด้านนอกซึ่งเป็นmodule.exportsวัตถุที่ถูกบันทึกไว้ในการเริ่มต้น
  5. นอกจากนี้เรายังสามารถระบุค่าthisโดยใช้callฟังก์ชั่น ที่นี่เราสามารถส่งผ่านthisค่าที่ต้องการเป็นอาร์กิวเมนต์ซึ่งobj2ในกรณีนี้
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.