Javascript中this綁定的3種方法與比較
介紹
this 可以說(shuō)是 javascript 中最耐人尋味的一個(gè)特性,學(xué)習(xí)this 的第一步就是明白 this 既不是指向函數(shù)自身也不指向函數(shù)的作用域。 this 實(shí)際上是在函數(shù)被調(diào)用時(shí)發(fā)生的綁定,它指向什么地方完全取決于函數(shù)在哪里被調(diào)用。
為什么需要綁定this
this代指當(dāng)前的上下文環(huán)境,在不經(jīng)意間容易改變:
var info = "This is global info"; var obj = { info: 'This is local info', getInfo: getInfo } function getInfo() { console.log(this.info); } obj.getInfo() //This is local info getInfo() //This is global info 當(dāng)前上下文環(huán)境被修改了
在上面的例子中,我們?cè)趯?duì)象內(nèi)部創(chuàng)建一個(gè)屬性getInfo,對(duì)全局作用域下的getInfo進(jìn)行引用,而它的作用是打印當(dāng)前上下文中info的值,當(dāng)我們使用obj.getInfo
進(jìn)行調(diào)用時(shí),它會(huì)打印出對(duì)象內(nèi)部的info的值,此時(shí)this指向該對(duì)象。而當(dāng)我們使用全局的函數(shù)時(shí),它會(huì)打印全局環(huán)境下的info變量的值,此時(shí)this指向全局對(duì)象。
這個(gè)例子告訴我們:
1、同一個(gè)函數(shù),調(diào)用的方式不同,this的指向就會(huì)不同,結(jié)果就會(huì)不同。
2、對(duì)象內(nèi)部的屬性的值為引用類型時(shí),this的指向不會(huì)一直綁定在原對(duì)象上。
其次,還有不經(jīng)意間this丟失的情況:
var info = "This is global info"; var obj = { info: 'This is local info', getInfo: function getInfo() { console.log(this.info); var getInfo2 = function getInfo2() { console.log(this.info); } getInfo2(); } } obj.getInfo(); //This is local info //This is global info
上面的例子中,對(duì)象obj中定義了一個(gè)getInfo方法,方法內(nèi)定義了一個(gè)新的函數(shù),也希望拿到最外層的該對(duì)象的info屬性的值,但是事與愿違,函數(shù)內(nèi)函數(shù)的this被錯(cuò)誤的指向了window全局對(duì)象上面,這就導(dǎo)致了錯(cuò)誤。
解決的方法也很簡(jiǎn)單,在最外層定義一個(gè)變量,存儲(chǔ)當(dāng)前詞法作用域內(nèi)的this指向的位置,根據(jù)變量作用域的關(guān)系,之后的函數(shù)內(nèi)部還能訪問(wèn)這個(gè)變量,從而得到上層函數(shù)內(nèi)部this的真正指向。
var info = "This is global info"; var obj = { info: 'This is local info', getInfo: function getInfo() { console.log(this.info); var self = this; //將外層this保存到變量中 var getInfo2 = function getInfo2() { console.log(self.info); //指向外層變量代表的this } getInfo2(); } } obj.getInfo(); //This is local info //This is local info
然而這樣也會(huì)有一些問(wèn)題,上面的self變量等于重新引用了obj對(duì)象,這樣的話可能會(huì)在有些時(shí)候不經(jīng)意間修改了整個(gè)對(duì)象,而且當(dāng)需要取得多個(gè)環(huán)境下的this指向時(shí),就需要聲明多個(gè)變量,不利于管理。
有一些方法,可以在不聲明類似于self這種變量的條件下,綁定當(dāng)前環(huán)境下的上下文,確保編程內(nèi)容的安全。
如何綁定this
1. call, apply
call和apply是定義在Function.prototype
上的兩個(gè)函數(shù),他們的作用就是修正函數(shù)執(zhí)行的上下文,也就是this的指向問(wèn)題。
以call為例,上述anotherFun想要輸出local thing就要這樣修改:
... var anotherFun = obj.getInfo; anotherFun.call(obj) //This is local info
函數(shù)調(diào)用的參數(shù):
Function.prototype.call(context [, argument1, argument2 ])
Function.prototype.apply(context [, [ arguments ] ])
從這里就可以看到,call和apply的第一參數(shù)是必須的,接受一個(gè)重新修正的上下文,第二個(gè)參數(shù)都是可選的,他們兩個(gè)的區(qū)別在于,call從第二個(gè)參數(shù)開(kāi)始,接受傳入調(diào)用函數(shù)的值是一個(gè)一個(gè)單獨(dú)出現(xiàn)的,而apply是接受一個(gè)數(shù)組傳入。
function add(num1, num2, num3) { return num1 + num2 + num3; } add.call(null, 10, 20, 30); //60 add.apply(null, [10, 20, 30]); //60
當(dāng)接受的context為undefined或null時(shí),會(huì)自動(dòng)修正為全局對(duì)象,上述例子中為window
2. 使用Function.prototype.bind進(jìn)行綁定
ES5中在Function.prototype
新增了bind
方法,它接受一個(gè)需要綁定的上下文對(duì)象,并返回一個(gè)調(diào)用的函數(shù)的副本,同樣的,它也可以在后面追加參數(shù),實(shí)現(xiàn)函數(shù)的柯里化。
Function.prototype.bind(context[, argument1, argument2]) //函數(shù)柯里化部分 function add(num1 ,num2) { return num1 + num2; } var anotherFun = window.add.bind(window, 10); anotherFun(20); //30
同時(shí),他返回一個(gè)函數(shù)的副本,并將函數(shù)永遠(yuǎn)的綁定在傳入的上下文中。
... var anotherFun = obj.getInfo.bind(obj) anotherFun(); //This is local info
polyfill
polyfill是一種為了向下兼容的解決方案,在不支持ES5的老舊瀏覽器上,如何使用bind方法呢,就得需要使用舊的方法重寫一個(gè)bind方法。
if (!Function.prototype.bind) { Function.prototype.bind = function (obj) { var self = this; return function () { self.call(obj); } } }
上面的寫法實(shí)現(xiàn)了返回一個(gè)函數(shù),并且將上下文修正為傳入的參數(shù),但是沒(méi)有實(shí)現(xiàn)柯里化部分。
... Function.prototype.bind = function(obj) { var args = Array.prototype.slice.call(arguments, 1); //記錄下所有第一次傳入的參數(shù) var self = this; return function () { self.apply(obj, args.concat(Array.prototype.slice.call(arguments))); } }
當(dāng)使用bind進(jìn)行綁定之后,即不能再通過(guò)call,apply進(jìn)行修正this指向,所以bind綁定又稱為硬綁定。
3. 使用new關(guān)鍵字進(jìn)行綁定
在js中,函數(shù)有兩種調(diào)用方式,一種是直接進(jìn)行調(diào)用,一種是通過(guò)new關(guān)鍵字進(jìn)行構(gòu)造調(diào)用。
function fun(){console.log("function called")} //直接調(diào)用 fun() //function called //構(gòu)造調(diào)用 var obj = new fun() //function called
那普通的調(diào)用和使用new關(guān)鍵字的構(gòu)造調(diào)用之間,又有哪些區(qū)別呢?
準(zhǔn)確的來(lái)說(shuō),就是new關(guān)鍵字只是在調(diào)用函數(shù)的基礎(chǔ)上,多增加了幾個(gè)步驟,其中就包括了修正this指針到return回去的對(duì)象上。
var a = 5; function Fun() { this.a = 10; } var obj = new Fun(); obj.a //10
幾種綁定方式的優(yōu)先級(jí)比較
以下面這個(gè)例子來(lái)進(jìn)行幾種綁定狀態(tài)的優(yōu)先級(jí)權(quán)重的比較
var obj1 = { info: "this is obj1", getInfo: () => console.log(this.info) } var obj2 = { info: "this is obj2", getInfo: () => console.log(this.info) }
1. call,apply和默認(rèn)指向比較
首先很顯然,根據(jù)使用頻率來(lái)想,使用call和apply會(huì)比直接調(diào)用的優(yōu)先級(jí)更高。
obj1.getInfo() //this is obj1 obj2.getInfo() //this is obj2 obj1.getInfo.call(obj2) //this is obj2 obj2.getInfo.call(obj1) //this is obj1
使用call和apply相比于使用new呢?
這個(gè)時(shí)候就會(huì)出現(xiàn)問(wèn)題了,因?yàn)槲覀儧](méi)辦法運(yùn)行類似 new function.call(something)
這樣的代碼。所以,我們通過(guò)bind方法返回一個(gè)新的函數(shù),再通過(guò)new判斷優(yōu)先級(jí)。
var obj = {} function foo(num){ this.num = num; } var setNum = foo.bind(obj); setNum(10); obj.num //10 var obj2 = new setNum(20); obj.num //10 obj2.num //20
通過(guò)這個(gè)例子我們可以看出來(lái),使用new進(jìn)行構(gòu)造調(diào)用時(shí),會(huì)返回一個(gè)新的對(duì)象,并將this修正到這個(gè)對(duì)象上,但是它并不會(huì)改變之前的對(duì)象內(nèi)容。
那么問(wèn)題來(lái)了,上面我們寫的bind的polyfill明顯不具備這樣的能力。而在MDN上有一個(gè)bind的polyfill方法,它的方法如下:
if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP ? this : oThis || this, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }
上面的polyfill首先判斷需要綁定的對(duì)象是否為函數(shù),防止使用Function.prototype.bind.call(something)
時(shí),something不是一個(gè)函數(shù)造成未知錯(cuò)誤。之后讓需要返回的fBound函數(shù)繼承自this,返回fBound。
特殊情況
當(dāng)然,在某些情況下,this指針指向也存在一些意外。
箭頭函數(shù)
ES6中新增了一種定義函數(shù)方式,使用"=>"進(jìn)行函數(shù)的定義,在它的內(nèi)部,this的指針不會(huì)改變,永遠(yuǎn)指向最外層的詞法作用域。
var obj = { num: 1, getNum: function () { return function () { //this丟失 console.log(this.num); //此處的this指向window } } } obj.getNum()(); //undefined var obj2 = { num: 2, getNum: function () { return () => console.log(this.num); //箭頭函數(shù)內(nèi)部綁定外部getNum的this,外部this指向調(diào)用的對(duì)象 } } obj2.getNum()(); //2
軟綁定
上面提供的bind方法可以通過(guò)強(qiáng)制修正this指向,并且再不能通過(guò)call,apply進(jìn)行修正。如果我們希望即能有bind效果,但是也能通過(guò)call和apply對(duì)函數(shù)進(jìn)行二次修正,這個(gè)時(shí)候就需要我們重寫一個(gè)建立在Function.prototype
上的方法,我們給它起名為"軟綁定"。
if (!Function.prototype.softBind) { Function.prototype.softbind = function (obj) { var self = this; var args = Array.prototype.slice.call(arguments, 1); return function () { return self.apply((!this || this === (window || global)) ? obj : this, args.concat(Array.prototype.slice.call(arguments))); } } }
bind,call的妙用
在平日里我們需要將偽數(shù)組元素變?yōu)檎5臄?shù)組元素時(shí),往往通過(guò)Array.prototype.slice
方法,正如上面的實(shí)例那樣。將arguments這個(gè)對(duì)象變?yōu)檎嬲臄?shù)組對(duì)象,使用 Array.prototype.slice.call(arguments)
進(jìn)行轉(zhuǎn)化.。但是,每次使用這個(gè)方法太長(zhǎng)而且繁瑣。所以有時(shí)候我們就會(huì)這樣寫:
var slice = Array.prototype.slice; slice(arguments); //error
同樣的問(wèn)題還出現(xiàn)在:
var qsa = document.querySelectorAll; qsa(something); //error
上面的問(wèn)題就出現(xiàn)在,內(nèi)置的slice和querySelectorAll方法,內(nèi)部使用了this,當(dāng)我們簡(jiǎn)單引用時(shí),this在運(yùn)行時(shí)成為了全局環(huán)境window,當(dāng)然會(huì)造成錯(cuò)誤。我們只需要簡(jiǎn)單的使用bind,就能創(chuàng)建一個(gè)函數(shù)副本。
var qsa = document.querySelectorAll.bind(document); qsa(something);
同樣的,使用因?yàn)閏all和apply也是一個(gè)函數(shù),所以也可以在它們上面調(diào)用bind方法。從而使返回的函數(shù)的副本本身就帶有修正指針的功能。
var slice = Function.prototype.call.bind(Array.prototype.slice); slice(arguments);
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
- JS中this的4種綁定規(guī)則詳解
- JavaScript this綁定過(guò)程深入詳解
- JavaScript調(diào)用模式與this關(guān)鍵字綁定的關(guān)系
- JavaScript函數(shù)中的this四種綁定形式
- 詳細(xì)講解JavaScript中的this綁定
- JavaScript中this的四個(gè)綁定規(guī)則總結(jié)
- js綁定事件this指向發(fā)生改變的問(wèn)題解決方法
- Javascript中的this綁定介紹
- javascript下動(dòng)態(tài)this與動(dòng)態(tài)綁定實(shí)例代碼
- js this 綁定機(jī)制深入詳解
相關(guān)文章
JS實(shí)現(xiàn)讓網(wǎng)頁(yè)背景圖片斜向移動(dòng)的方法
這篇文章主要介紹了JS實(shí)現(xiàn)讓網(wǎng)頁(yè)背景圖片斜向移動(dòng)的方法,涉及javascript操作背景圖片特效的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02JavaScript中對(duì)于SPA單頁(yè)面的理解
這篇文章主要介紹了JavaScript中對(duì)于SPA單頁(yè)面的理解,單頁(yè)應(yīng)用SPA是一種網(wǎng)絡(luò)應(yīng)用程序或網(wǎng)站的模型,它通過(guò)動(dòng)態(tài)重寫當(dāng)前頁(yè)面來(lái)與用戶交互2023-02-02javascript實(shí)現(xiàn)5秒倒計(jì)時(shí)并跳轉(zhuǎn)功能
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)5秒倒計(jì)時(shí)并跳轉(zhuǎn)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06JavaScript基礎(chǔ)篇(3)之Object、Function等引用類型
這篇文章主要介紹了JavaScript基礎(chǔ)篇(3)之Object、Function等引用類型的相關(guān)資料,需要的朋友可以參考下2015-11-11解讀new?Object()和Object.create()的區(qū)別
這篇文章主要介紹了解讀new?Object()和Object.create()的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02JS co 函數(shù)庫(kù)的含義和用法實(shí)例總結(jié)
這篇文章主要介紹了JS co 函數(shù)庫(kù)的含義和用法,結(jié)合實(shí)例形式總結(jié)分析了JS co 函數(shù)庫(kù)的基本含義、功能、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04JavaScript實(shí)現(xiàn)各種排序的代碼詳解
這篇文章給大家介紹了js實(shí)現(xiàn)各種排序功能,包括冒泡排序,選擇排序和插入排序,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-08-08