JS this機(jī)制

目錄

this 是什么

this 的四種綁定規(guī)則

綁定規(guī)則的優(yōu)先級

綁定例外

擴(kuò)展:箭頭函數(shù)

this 是什么

理解this之前纺弊, 先糾正一個觀點(diǎn),this 既不指向函數(shù)自身转捕,也不指函數(shù)的詞法作用域饭入。如果僅通過this的英文解釋各吨,太容易產(chǎn)生誤導(dǎo)了。它實(shí)際是在函數(shù)被調(diào)用時才發(fā)生的綁定沦寂,也就是說this具體指向什么学密,取決于你是怎么調(diào)用的函數(shù)。

this 的四種綁定規(guī)則

this的4種綁定規(guī)則分別是:默認(rèn)綁定传藏、隱式綁定腻暮、顯示綁定彤守、new 綁定。優(yōu)先級從低到高哭靖。

默認(rèn)綁定

什么叫默認(rèn)綁定具垫,即沒有其他綁定規(guī)則存在時的默認(rèn)規(guī)則。這也是函數(shù)調(diào)用中最常用的規(guī)則试幽。

來看這段代碼:

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

var a = 2; 
foo(); //打印的是什么筝蚕?

foo() 打印的結(jié)果是2。

因?yàn)閒oo()是直接調(diào)用的(獨(dú)立函數(shù)調(diào)用)铺坞,沒有應(yīng)用其他的綁定規(guī)則起宽,這里進(jìn)行了默認(rèn)綁定,將全局對象綁定this上济榨,所以this.a 就解析成了全局變量中的a坯沪,即2。

注意:在嚴(yán)格模式下(strict mode)擒滑,全局對象將無法使用默認(rèn)綁定腐晾,即執(zhí)行會報(bào)undefined的錯誤

function foo() { 
    "use strict";
   console.log( this.a );
}

var a = 2; 
foo(); // Uncaught TypeError: Cannot read property 'a' of undefined

隱式綁定

除了直接對函數(shù)進(jìn)行調(diào)用外,有些情況是丐一,函數(shù)的調(diào)用是在某個對象上觸發(fā)的赴魁,即調(diào)用位置上存在上下文對象。

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

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

obj.foo(); // ?

obj.foo() 打印的結(jié)果是3钝诚。

這里foo函數(shù)被當(dāng)做引用屬性,被添加到obj對象上榄棵。這里的調(diào)用過程是這樣的:

獲取obj.foo屬性 -> 根據(jù)引用關(guān)系找到foo函數(shù)凝颇,執(zhí)行調(diào)用

所以這里對foo的調(diào)用存在上下文對象obj,this進(jìn)行了隱式綁定疹鳄,即this綁定到了obj上拧略,所以this.a被解析成了obj.a,即3瘪弓。

多層調(diào)用鏈

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

var a = 2;

var obj1 = { 
    a: 4,
    foo: foo 
};

var obj2 = { 
    a: 3,
    obj1: obj1
};

obj2.obj1.foo(); //?

obj2.obj1.foo() 打印的結(jié)果是4垫蛆。

同樣,我們看下函數(shù)的調(diào)用過程:

先獲取obj1.obj2 -> 通過引用獲取到obj2對象腺怯,再訪問 obj2.foo -> 最后執(zhí)行foo函數(shù)調(diào)用

這里調(diào)用鏈不只一層袱饭,存在obj1、obj2兩個對象呛占,那么隱式綁定具體會綁哪個對象虑乖。這里原則是獲取最后一層調(diào)用的上下文對象,即obj2晾虑,所以結(jié)果顯然是4(obj2.a)疹味。

隱式丟失(函數(shù)別名)

注意:這里存在一個陷阱仅叫,大家在分析調(diào)用過程時,要特別小心

先看個代碼:

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

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

var bar = obj.foo;
bar(); //?

bar() 打印的結(jié)果是2糙捺。

為什么會這樣诫咱,obj.foo 賦值給bar,那調(diào)用bar()為什么沒有觸發(fā)隱式綁定洪灯,使用的是默認(rèn)綁定呢坎缭。

這里有個概念要理解清楚,obj.foo 是引用屬性婴渡,賦值給bar的實(shí)際上就是foo函數(shù)(即:bar指向foo本身)幻锁。

那么,實(shí)際的調(diào)用關(guān)系是:通過bar找到foo函數(shù)边臼,進(jìn)行調(diào)用哄尔。整個調(diào)用過程并沒有obj的參數(shù),所以是默認(rèn)綁定柠并,全局屬性a岭接。

隱式丟失(回調(diào)函數(shù))

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

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

setTimeout( obj.foo, 100 ); // ?

打印的結(jié)果是2臼予。

同樣的道理鸣戴,雖然參傳是obj.foo,因?yàn)槭且藐P(guān)系粘拾,所以傳參實(shí)際上傳的就是foo對象本身的引用窄锅。對于setTimeout的調(diào)用,還是 setTimeout -> 獲取參數(shù)中foo的引用參數(shù) -> 執(zhí)行 foo 函數(shù)缰雇,中間沒有obj的參與入偷。這里依舊進(jìn)行的是默認(rèn)綁定。

顯示綁定

相對隱式綁定械哟,this值在調(diào)用過程中會動態(tài)變化疏之,可是我們就想綁定指定的對象,這時就用到了顯示綁定暇咆。

顯示綁定主要是通過改變對象的prototype關(guān)聯(lián)對象锋爪,這里不展開講。具體使用上爸业,可以通過這兩個方法call(…)或apply(…)來實(shí)現(xiàn)(大多數(shù)函數(shù)及自己創(chuàng)建的函數(shù)默認(rèn)都提供這兩個方法)其骄。

call與apply是同樣的作用,區(qū)別只是其他參數(shù)的設(shè)置上

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

var a = 2;

var obj1 = { 
    a: 3,
};

var obj2 = { 
    a: 4,
};
foo.call( obj1 ); // ?
foo.call( obj2 ); // ?

打印的結(jié)果是3, 4扯旷。

這里因?yàn)轱@示的申明了要綁定的對象年栓,所以this就被綁定到了obj上,打印的結(jié)果自然就是obj1.a 和obj2.a薄霜。

硬綁定

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

var a = 2;

var obj1 = { 
    a: 3,
};

var obj2 = { 
    a: 4,
};

var bar = function(){
    foo.call( obj1 );
}

setTimeout( bar, 100 ); // 3

bar.call( obj2 ); // 這是多少

前面兩個(函數(shù)別名某抓、回調(diào)函數(shù))打印3纸兔,因?yàn)轱@示綁定了,沒什么問題否副。

最后一個打印是3汉矿。

這里需要注意下,雖然bar被顯示綁定到obj2上备禀,對于bar洲拇,function(){…} 中的this確實(shí)被綁定到了obj2,而foo因?yàn)橥ㄟ^foo.call( obj1 )已經(jīng)顯示綁定了obj1曲尸,所以在foo函數(shù)內(nèi)赋续,this指向的是obj1,不會因?yàn)閎ar函數(shù)內(nèi)指向obj2而改變自身另患。所以打印的是obj1.a(即3)纽乱。

new 綁定

js中的new操作符,和其他語言中(如JAVA)的new機(jī)制是不一樣的昆箕。js中鸦列,它就是一個普通函數(shù)調(diào)用,只是被new修飾了而已鹏倘。

使用new來調(diào)用函數(shù)薯嗤,會自動執(zhí)行如下操作:

如果函數(shù)沒有返回其他對象,那么new表達(dá)式中的函數(shù)調(diào)用會自動返回這個新對象。
從第三點(diǎn)可以看出纤泵,this指向的就是對象本身骆姐。

看個代碼:

function foo(a) { 
    this.a = a;
}

var a = 2;

var bar1 = new foo(3);
console.log(bar1.a); // ?

var bar2 = new foo(4);
console.log(bar2.a); // ?

最后一個打印是3, 4。

因?yàn)槊看握{(diào)用生成的是全新的對象捏题,該對象又會自動綁定到this上玻褪,所以答案顯而易見。

綁定規(guī)則優(yōu)先級

上面也說過涉馅,這里在重復(fù)一下。優(yōu)先級是這樣的黄虱,以按照下面的順序來進(jìn)行判斷:
數(shù)是否在new中調(diào)用(new綁定)?如果是的話this綁定的是新創(chuàng)建的對象稚矿。
數(shù)是否通過callapply(顯式綁定)或者硬綁定調(diào)用?如果是的話,this綁定的是 指定的對象捻浦。
數(shù)是否在某個上下文對象中調(diào)用(隱式綁定)?如果是的話,this綁定的是那個上下文對象晤揣。
果都不是的話,使用默認(rèn)綁定。如果在嚴(yán)格模式下,就綁定到undefined,否則綁定到 全局對象朱灿。
var bar = foo()

規(guī)則例外

在顯示綁定中昧识,對于nullundefined的綁定將不會生效。

代碼如下:

function foo() { 
    console.log( this.a );
}
foo.call( null ); // 2
foo.call( undefined ); // 2

這種情況主要是用在不關(guān)心this的具體綁定對象(用來忽略this)盗扒,而傳入null實(shí)際上會進(jìn)行默認(rèn)綁定跪楞,導(dǎo)致函數(shù)中可能會使用到全局變量缀去,與預(yù)期不符。

所以對于要忽略this的情況甸祭,可以傳入一個空對象?缕碎,該對象通過Object.create(null)創(chuàng)建。這里不用{}的原因是池户,?是真正意義上的空對象咏雌,它不創(chuàng)建Object.prototype委托,{}和普通對象一樣校焦,有原型鏈委托關(guān)系赊抖。

這里傳null的一種具體使用場景是函數(shù)柯里化的使用

擴(kuò)展:箭頭函數(shù)

最后,介紹一下ES6中的箭頭函數(shù)寨典。通過“=>”而不是function創(chuàng)建的函數(shù)氛雪,叫做箭頭函數(shù)。它的this綁定取決于外層(函數(shù)或全局)作用域凝赛。

case 1 (正常調(diào)用)

普通函數(shù)

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

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

obj.foo(); //3

箭頭函數(shù)

var foo = () => {   
    console.log( this.a );
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

obj.foo(); //2
foo.call(obj); //2 注暗,箭頭函數(shù)中顯示綁定不會生效

case 2 (函數(shù)回調(diào))

普通函數(shù)

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

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

var bar = obj.foo();
bar(); //2

箭頭函數(shù)

function foo(){ 
    return () => {
        console.log( this.a );
    }   
}



var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

var bar = obj.foo();
bar(); //3

通過上面兩個列子,我們看到箭頭函數(shù)的this綁定只取決于外層(函數(shù)或全局)的作用域墓猎,對于前面的4種綁定規(guī)則是不會生效的捆昏。它也是作為this機(jī)制的一種替換,解決之前this綁定過程各種規(guī)則帶來的復(fù)雜性毙沾。

注意:對于ES6之前骗卜,箭頭函數(shù)的替換版本是這樣的

// es6
function foo(){ 
    return () => {
        console.log( this.a );
    }   
}

var a = 2;

var obj = { 
    a: 3,
    foo: foo 
};

var bar = obj.foo();
bar(); //3

通過上面兩個列子,我們看到箭頭函數(shù)的this綁定只取決于外層(函數(shù)或全局)的作用域左胞,對于前面的4種綁定規(guī)則是不會生效的寇仓。它也是作為this機(jī)制的一種替換,解決之前this綁定過程各種規(guī)則帶來的復(fù)雜性烤宙。

注意:對于ES6之前遍烦,箭頭函數(shù)的替換版本是這樣的



// es6
function foo(){ 
    return () => {
        console.log( this.a );
    }   
}

// es6之前的替代方法
function foo(){ 
     var self = this;
    return () => {
        console.log( self.a );
    }   
}

總結(jié)

我們在使用js的過程中,對于this的理解往往覺得比較困難躺枕,再調(diào)試過程中有時也會出現(xiàn)一些不符合預(yù)期的現(xiàn)象服猪。很多時候,我們都是通過一些變通的方式(如:使用具體對象替換this)來規(guī)避的問題拐云“罩恚可問題一直存在那兒,我們沒有真正的去理解和解決它叉瘩。

本文主要參考了《你不知道的JavaScript(上卷)》膳帕,對this到底是什么,具體怎么綁定的薇缅,有什么例外情況以及ES6中的一個優(yōu)化方向危彩,來徹底搞清楚我們一直使用的this到底是怎么玩的攒磨。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恬砂,隨后出現(xiàn)的幾起案子咧纠,更是在濱河造成了極大的恐慌,老刑警劉巖泻骤,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漆羔,死亡現(xiàn)場離奇詭異,居然都是意外死亡狱掂,警方通過查閱死者的電腦和手機(jī)演痒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趋惨,“玉大人鸟顺,你說我怎么就攤上這事∑飨海” “怎么了讯嫂?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長兆沙。 經(jīng)常有香客問我欧芽,道長,這世上最難降的妖魔是什么葛圃? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任千扔,我火速辦了婚禮,結(jié)果婚禮上库正,老公的妹妹穿的比我還像新娘曲楚。我一直安慰自己,他們只是感情好褥符,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布龙誊。 她就那樣靜靜地躺著,像睡著了一般喷楣。 火紅的嫁衣襯著肌膚如雪趟大。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天抡蛙,我揣著相機(jī)與錄音护昧,去河邊找鬼魂迄。 笑死粗截,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捣炬。 我是一名探鬼主播熊昌,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绽榛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了婿屹?” 一聲冷哼從身側(cè)響起灭美,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昂利,沒想到半個月后届腐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜂奸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年犁苏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扩所。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡围详,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祖屏,到底是詐尸還是另有隱情助赞,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布袁勺,位于F島的核電站雹食,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏魁兼。R本人自食惡果不足惜婉徘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咐汞。 院中可真熱鬧盖呼,春花似錦、人聲如沸化撕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽植阴。三九已至蟹瘾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掠手,已是汗流浹背憾朴。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喷鸽,地道東北人众雷。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親砾省。 傳聞我的和親對象是個殘疾皇子鸡岗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容