ผ่านบริบท“ นี่” ที่ถูกต้องไปยัง setTimeout callback หรือไม่


251

ฉันจะส่งผ่านบริบทได้setTimeoutอย่างไร ฉันต้องการโทรthis.tip.destroy()ถ้าเกินthis.options.destroyOnHide1,000 มิลลิวินาที ฉันจะทำสิ่งนั้นได้อย่างไร

if (this.options.destroyOnHide) {
     setTimeout(function() { this.tip.destroy() }, 1000);
} 

เมื่อฉันลองด้านบนthisหมายถึงหน้าต่าง


4
การตั้งค่าสถานะที่ซ้ำกันถูกต้องจริง ๆ หรือไม่ คำถามนี้ถูกถามจริงก่อนหน้านี้
ซุยฝัน

1
if (this.options.destroyOnHide) {setTimeout (function () {this.tip.destroy ()} .bind (นี้), 1,000); }
Zibri

คำตอบ:


353

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

var that = this;
if (this.options.destroyOnHide) {
     setTimeout(function(){ that.tip.destroy() }, 1000);
} 

ในข้อมูลจำเพาะ ES5 เพิ่งเปิดตัวเมื่อปีก่อนมันแนะนำbindวิธีการนี้ไม่แนะนำในคำตอบเดิมเพราะยังไม่ได้รับการสนับสนุนอย่างกว้างขวางและคุณต้องการ polyfills เพื่อใช้ แต่ตอนนี้มันอยู่ทุกที่:

if (this.options.destroyOnHide) {
     setTimeout(function(){ this.tip.destroy() }.bind(this), 1000);
}

bindฟังก์ชั่นสร้างฟังก์ชั่นใหม่ที่มีthisค่าก่อนที่เต็มไป

ตอนนี้ใน JS สมัยใหม่นี่เป็นปัญหาที่ลูกศรแก้ปัญหาในES6 :

if (this.options.destroyOnHide) {
     setTimeout(() => { this.tip.destroy() }, 1000);
}

ฟังก์ชั่นลูกศรไม่มีthisค่าเป็นของตัวเองเมื่อคุณเข้าถึงคุณกำลังเข้าถึงthisค่าของขอบเขตคำศัพท์ที่ล้อมรอบ

HTML5 ยังมีตัวนับมาตรฐานกลับมาในปี 2011 และคุณสามารถส่งต่ออาร์กิวเมนต์ไปยังฟังก์ชันการเรียกกลับได้ในขณะนี้:

if (this.options.destroyOnHide) {
     setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}

ดูสิ่งนี้ด้วย:


3
มันได้ผล. ฉันทดสอบแนวคิดด้วยสคริปต์jsbin
John K

1
รหัสนี้เกี่ยวข้องกับการสร้างตัวแปรที่ไม่จำเป็น (ซึ่งมีขอบเขตฟังก์ชันกว้าง); หากคุณส่งผ่านthisไปยังฟังก์ชันอย่างถูกต้องคุณจะต้องแก้ไขปัญหานี้สำหรับกรณีนี้สำหรับ map (), forEach () ฯลฯ ฯลฯ โดยใช้รหัสน้อยลงรอบการทำงานของ CPU ที่น้อยลงและหน่วยความจำน้อยลง *** ดู: คำตอบของ Misha Reyzlin
HoldOffHunger

222

มีทางลัดพร้อมใช้ (น้ำตาลประโยค) สำหรับฟังก์ชั่น wrapper @CMS ที่ตอบด้วย (ด้านล่างสมมติว่าบริบทที่คุณต้องการคือthis.tip)


ECMAScript 5 ( เบราว์เซอร์ปัจจุบัน , Node.js) และ Prototype.js

หากคุณกำหนดเป้าหมายเบราว์เซอร์ที่เข้ากันได้กับ ECMA-262, รุ่นที่ 5 (ECMAScript 5)หรือNode.jsFunction.prototype.bindคุณสามารถใช้ คุณสามารถเลือกฟังก์ชั่นผ่านข้อโต้แย้งใด ๆ ในการสร้างฟังก์ชั่นบางส่วน

fun.bind(thisArg[, arg1[, arg2[, ...]]])

ในกรณีของคุณลองสิ่งนี้:

if (this.options.destroyOnHide) {
    setTimeout(this.tip.destroy.bind(this.tip), 1000);
}

มีการใช้ฟังก์ชั่นเดียวกันใน Prototype (ไลบรารีอื่นใดบ้าง)

Function.prototype.bindสามารถนำไปใช้เช่นนี้หากคุณต้องการความเข้ากันได้แบบกำหนดเองย้อนหลัง (แต่โปรดสังเกตหมายเหตุ)


ECMAScript 2015 ( เบราว์เซอร์บางตัว Node.js 5.0.0+)

สำหรับการพัฒนาที่ล้ำสมัย (2015) คุณสามารถใช้ฟังก์ชั่นลูกศรไขมันซึ่งเป็นส่วนหนึ่งของข้อกำหนด ECMAScript 2015 (Harmony / ES6 / ES2015) ( ตัวอย่าง )

แสดงออกฟังก์ชั่นลูกศร (หรือเรียกว่าฟังก์ชั่นลูกศรไขมัน ) มีไวยากรณ์สั้นเมื่อเทียบกับการแสดงออกและฟังก์ชั่น lexically ผูกthisค่า [ ... ]

(param1, param2, ...rest) => { statements }

ในกรณีของคุณลองสิ่งนี้:

if (this.options.destroyOnHide) {
    setTimeout(() => { this.tip.destroy(); }, 1000);
}

jQuery

หากคุณใช้ jQuery 1.4+ อยู่แล้วจะมีฟังก์ชันสำเร็จรูปสำหรับการตั้งค่าthisบริบทของฟังก์ชันอย่างชัดเจน

jQuery.proxy () : รับฟังก์ชั่นและคืนค่าใหม่ที่มักจะมีบริบทเฉพาะ

$.proxy(function, context[, additionalArguments])

ในกรณีของคุณลองสิ่งนี้:

if (this.options.destroyOnHide) {
    setTimeout($.proxy(this.tip.destroy, this.tip), 1000);
}

Underscore.js , lodash

มันมีอยู่ใน Underscore.js เช่นเดียวกับ lodash เช่น_.bind(...)1 , 2

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

_.bind(function, object, [*arguments])

ในกรณีของคุณลองสิ่งนี้:

if (this.options.destroyOnHide) {
    setTimeout(_.bind(this.tip.destroy, this.tip), 1000);
}


ทำไมไม่เริ่มต้นfunc.bind(context...)? ฉันจะพลาดอะไรบางอย่าง?
aTei

มันเป็นหน้าที่ที่จะต้องสร้างฟังก์ชั่นใหม่ ๆ อยู่เสมอ (ซึ่งจะผูกมัด) ทุกครั้งที่คุณเรียกสิ่งนี้? ฉันหมดเวลาค้นหาที่รีเซ็ตหลังจากกดปุ่มทุกครั้งและดูเหมือนว่าฉันควรจะแคชเมธอด 'bound' นี้เพื่อนำมาใช้ใหม่
Triynko

@Triynko: ฉันจะไม่เห็นการผูกฟังก์ชั่นเป็นการดำเนินการที่มีราคาแพง แต่ถ้าคุณเรียกฟังก์ชั่นที่ถูกผูกไว้เหมือนกันหลายครั้งคุณอาจจะเก็บการอ้างอิงvar boundFn = fn.bind(this); boundFn(); boundFn();เช่นกัน
Joel Purra

30

ในเบราว์เซอร์อื่นที่ไม่ใช่ Internet Explorer คุณสามารถส่งพารามิเตอร์ไปยังฟังก์ชั่นร่วมกันได้หลังจากเกิดความล่าช้า:

var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);

ดังนั้นคุณสามารถทำสิ่งนี้:

var timeoutID = window.setTimeout(function (self) {
  console.log(self); 
}, 500, this);

สิ่งนี้ดีกว่าในแง่ของประสิทธิภาพมากกว่าการค้นหาขอบเขต (การแคชthisลงในตัวแปรนอกการหมดเวลา / นิพจน์ช่วงเวลา) แล้วสร้างการปิด (โดยใช้$.proxyหรือFunction.prototype.bind)

รหัสที่จะทำให้มันทำงานใน IEs จากWebreflection :

/*@cc_on
(function (modifierFn) {
  // you have to invoke it as `window`'s property so, `window.setTimeout`
  window.setTimeout = modifierFn(window.setTimeout);
  window.setInterval = modifierFn(window.setInterval);
})(function (originalTimerFn) {
    return function (callback, timeout){
      var args = [].slice.call(arguments, 2);
      return originalTimerFn(function () { 
        callback.apply(this, args) 
      }, timeout);
    }
});
@*/

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

4

หมายเหตุ: สิ่งนี้จะไม่ทำงานใน IE

var ob = {
    p: "ob.p"
}

var p = "window.p";

setTimeout(function(){
    console.log(this.p); // will print "window.p"
},1000); 

setTimeout(function(){
    console.log(this.p); // will print "ob.p"
}.bind(ob),1000);

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.