js中綁定this的幾種方法及簡單比較

JavaScript(以下簡稱js)是一門動態(tài)語言农渊,與傳統(tǒng)的c,c++最大的區(qū)別就是js是在運行時動態(tài)檢測值的類型和變化。這一點有很大的好處吱雏,比如可以進行隱式類型轉換而不報錯艘策,書寫更加靈活而晒。當然也會造成很多問題碍论。this是js中的一個關鍵字康谆,它代表當前作用域的上下文環(huán)境,而且隨著上下文的改變而動態(tài)變化悬荣。

為什么需要綁定this

this代指當前的上下文環(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 當前上下文環(huán)境被修改了

在上面的例子中疙剑,我們在對象內部創(chuàng)建一個屬性getInfo氯迂,對全局作用域下的getInfo進行引用,而它的作用是打印當前上下文中info的值言缤,當我們使用obj.getInfo進行調用時嚼蚀,它會打印出對象內部的info的值,此時this指向該對象管挟。而當我們使用全局的函數(shù)時轿曙,它會打印全局環(huán)境下的info變量的值,此時this指向全局對象僻孝。

這個例子告訴我們:

  1. 同一個函數(shù)导帝,調用的方式不同,this的指向就會不同穿铆,結果就會不同您单。
  2. 對象內部的屬性的值為引用類型時,this的指向不會一直綁定在原對象上荞雏。

其次平酿,還有不經(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

上面的例子中,對象obj中定義了一個getInfo方法蜈彼,方法內定義了一個新的函數(shù),也希望拿到最外層的該對象的info屬性的值幸逆,但是事與愿違,函數(shù)內函數(shù)的this被錯誤的指向了window全局對象上面暮现,這就導致了錯誤。

解決的方法也很簡單送矩,在最外層定義一個變量,存儲當前詞法作用域內的this指向的位置栋荸,根據(jù)變量作用域的關系菇怀,之后的函數(shù)內部還能訪問這個變量,從而得到上層函數(shù)內部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

然而這樣也會有一些問題爱沟,上面的self變量等于重新引用了obj對象,這樣的話可能會在有些時候不經(jīng)意間修改了整個對象匆背,而且當需要取得多個環(huán)境下的this指向時呼伸,就需要聲明多個變量,不利于管理钝尸。

有一些方法括享,可以在不聲明類似于self這種變量的條件下,綁定當前環(huán)境下的上下文珍促,確保編程內容的安全铃辖。

如何綁定this

1. call, apply

call和apply是定義在Function.prototype上的兩個函數(shù),他們的作用就是修正函數(shù)執(zhí)行的上下文猪叙,也就是this的指向問題娇斩。

以call為例,上述anotherFun想要輸出local thing就要這樣修改:

...
var anotherFun = obj.getInfo;
anotherFun.call(obj)    //This is local info

函數(shù)調用的參數(shù):

  • Function.prototype.call(context [, argument1, argument2 ])
  • Function.prototype.apply(context [, [ arguments ] ])

從這里就可以看到穴翩,call和apply的第一參數(shù)是必須的犬第,接受一個重新修正的上下文,第二個參數(shù)都是可選的芒帕,他們兩個的區(qū)別在于歉嗓,call從第二個參數(shù)開始,接受傳入調用函數(shù)的值是一個一個單獨出現(xiàn)的副签,而apply是接受一個數(shù)組傳入遥椿。

function add(num1, num2, num3) {
  return num1 + num2 + num3;
}
add.call(null, 10, 20, 30);    //60
add.apply(null, [10, 20, 30]);    //60

當接受的context為undefined或null時基矮,會自動修正為全局對象,上述例子中為window

2. 使用Function.prototype.bind進行綁定

ES5中在Function.prototype新增了bind方法冠场,它接受一個需要綁定的上下文對象家浇,并返回一個調用的函數(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ù)的副本莺琳,并將函數(shù)永遠的綁定在傳入的上下文中。

...
var anotherFun = obj.getInfo.bind(obj)
anotherFun();    //This is local info
polyfill

polyfill是一種為了向下兼容的解決方案载慈,在不支持ES5的老舊瀏覽器上惭等,如何使用bind方法呢,就得需要使用舊的方法重寫一個bind方法办铡。

if (!Function.prototype.bind) {
  Function.prototype.bind = function (obj) {
  var self = this;
   return function () {
      self.call(obj);
   }
  }
}

上面的寫法實現(xiàn)了返回一個函數(shù)寡具,并且將上下文修正為傳入的參數(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)));
  }
}

當使用bind進行綁定之后厦坛,即不能再通過call粪般,apply進行修正this指向,所以bind綁定又稱為硬綁定。

3. 使用new關鍵字進行綁定

在js中凡橱,函數(shù)有兩種調用方式,一種是直接進行調用顾稀,一種是通過new關鍵字進行構造調用坝撑。

function fun(){console.log("function called")}
//直接調用
fun()    //function called
//構造調用
var obj = new fun()    //function called

那普通的調用和使用new關鍵字的構造調用之間粮揉,又有哪些區(qū)別呢扶认?

準確的來說辐宾,就是new關鍵字只是在調用函數(shù)的基礎上膨蛮,多增加了幾個步驟,其中就包括了修正this指針到return回去的對象上誉察。

var a = 5;
function Fun() {
  this.a = 10;
}
var obj = new Fun();
obj.a    //10

幾種綁定方式的優(yōu)先級比較

以下面這個例子來進行幾種綁定狀態(tài)的優(yōu)先級權重的比較

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和默認指向比較

首先很顯然冒窍,根據(jù)使用頻率來想综液,使用call和apply會比直接調用的優(yōu)先級更高儒飒。

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呢?

這個時候就會出現(xiàn)問題了附帽,因為我們沒辦法運行類似 new function.call(something)這樣的代碼蕉扮。所以颗圣,我們通過bind方法返回一個新的函數(shù),再通過new判斷優(yōu)先級奔则。

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

通過這個例子我們可以看出來易茬,使用new進行構造調用時及老,會返回一個新的對象范抓,并將this修正到這個對象上匕垫,但是它并不會改變之前的對象內容年缎。

那么問題來了铃慷,上面我們寫的bind的polyfill明顯不具備這樣的能力。而在MDN上有一個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首先判斷需要綁定的對象是否為函數(shù)扒腕,防止使用Function.prototype.bind.call(something)時瘾腰,something不是一個函數(shù)造成未知錯誤覆履。之后讓需要返回的fBound函數(shù)繼承自this,返回fBound栖雾。

特殊情況

當然析藕,在某些情況下凳厢,this指針指向也存在一些意外。

箭頭函數(shù)

ES6中新增了一種定義函數(shù)方式找爱,使用"=>"進行函數(shù)的定義泡孩,在它的內部仑鸥,this的指針不會改變,永遠指向最外層的詞法作用域意狠。

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ù)內部綁定外部getNum的this环戈,外部this指向調用的對象
  }
}
obj2.getNum()();    //2

軟綁定

上面提供的bind方法可以通過強制修正this指向澎灸,并且再不能通過call,apply進行修正拦止。如果我們希望即能有bind效果糜颠,但是也能通過call和apply對函數(shù)進行二次修正其兴,這個時候就需要我們重寫一個建立在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ù)組元素時朋譬,往往通過Array.prototype.slice方法,正如上面的實例那樣字柠。將arguments這個對象變?yōu)檎嬲臄?shù)組對象窑业,使用 Array.prototype.slice.call(arguments)進行轉化.枕屉。但是,每次使用這個方法太長而且繁瑣西潘。所以有時候我們就會這樣寫:

var slice = Array.prototype.slice;
slice(arguments);
//error

同樣的問題還出現(xiàn)在:

var qsa = document.querySelectorAll;
qsa(something);
//error

上面的問題就出現(xiàn)在喷市,內置的slice和querySelectorAll方法,內部使用了this寝并,當我們簡單引用時腹备,this在運行時成為了全局環(huán)境window馏谨,當然會造成錯誤。我們只需要簡單的使用bind哎媚,就能創(chuàng)建一個函數(shù)副本喊儡。

var qsa = document.querySelectorAll.bind(document);
qsa(something);

同樣的,使用因為call和apply也是一個函數(shù)买喧,所以也可以在它們上面調用bind方法淤毛。從而使返回的函數(shù)的副本本身就帶有修正指針的功能算柳。

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice(arguments);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末瞬项,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猪杭,更是在濱河造成了極大的恐慌皂吮,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稠鼻,死亡現(xiàn)場離奇詭異狂票,居然都是意外死亡熙暴,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門掂器,熙熙樓的掌柜王于貴愁眉苦臉地迎上來国瓮,“玉大人狞谱,你說我怎么就攤上這事》醪牵” “怎么了掰读?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵蹈集,是天一觀的道長雇初。 經(jīng)常有香客問我,道長善榛,這世上最難降的妖魔是什么呻畸? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任伤为,我火速辦了婚禮据途,結果婚禮上颖医,老公的妹妹穿的比我還像新娘裆蒸。我一直安慰自己,他們只是感情好佛致,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布俺榆。 她就那樣靜靜地躺著装哆,像睡著了一般蜕琴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梗夸,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天反症,我揣著相機與錄音畔派,去河邊找鬼。 笑死胞谈,一個胖子當著我的面吹牛憨愉,可吹牛的內容都是我干的。 我是一名探鬼主播径密,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼享扔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了惧眠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤暮顺,失蹤者是張志新(化名)和其女友劉穎拖云,沒想到半個月后应又,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體株扛,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡洞就,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年旬蟋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倾贰。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡匆浙,死狀恐怖厕妖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情软能,我是刑警寧澤举畸,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布俱恶,位于F島的核電站范舀,受9級特大地震影響锭环,放射性物質發(fā)生泄漏泊藕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一玫锋、第九天 我趴在偏房一處隱蔽的房頂上張望撩鹿。 院中可真熱鬧,春花似錦节沦、人聲如沸础爬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽渴逻。三九已至碱茁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纽竣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工聋袋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留幽勒,地道東北人港令。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓锈颗,卻偏偏與公主長得像击吱,于是被迫代替她去往敵國和親遥昧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內容