徹底弄懂Javascript中的this

在很長的一段時間之內,我一直以為作用域就是上下文,這也就對JavaScript中的this理解增加了很多麻煩,所以這篇文章開篇第一個要陳訴的概念就是作用域和上下文不是一個概念。作用域(scope) 是指變量的可訪問性,上下文是來決定this。(注意執(zhí)行期上下文指的是作用域,這是JavaScipt規(guī)范,所以得遵守)

在JavaScript中只有兩種作用域,一種是全局作用域,另一個就是函數(shù)作用域。上下文則會與this息息相關,而this是在運行的時候進行綁定的,它的上下文取決于函數(shù)在哪里被調用,this的綁定和函數(shù)聲明的位置沒有任何關系。

當一個函數(shù)被調用時,會創(chuàng)建一個活動記錄(即執(zhí)行上下文)。這個活動會包含函數(shù)在哪里被調用,函數(shù)的調用方法,傳入的參數(shù)信息等信息。this就是記錄的其中一個屬性,會在函數(shù)的執(zhí)行過程中用到。

以上引用出自《你不知道的JavaScript(上卷)》,在這里強烈推薦這本書,字字珠璣。

再次強調:this實際上是在函數(shù)被調用的時候發(fā)生綁定,它指向什么完全取決于函數(shù)在哪里調用。

調用位置

接下來我們看看函數(shù)調用包括哪幾種情況,只有正確的知道函數(shù)調用的位置,才能正確的明白this的指向問題。

默認綁定(全局調用)

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

以上就是默認綁定,foo函數(shù)是直接調用的。

隱式綁定

b = 2;
var obj = {
    b: 3,
    foo: foo
}

function foo () {
    console.log(this.b);
}
obj.foo(); // 3

這里為什么叫做隱式綁定,因為這個foo函數(shù)無論是在obj里面聲明還是在obj外面聲明,他實際上都是不屬于obj這個對象的(obj只是記錄了foo這個屬性的引用值),但是最后在執(zhí)行的時候this卻被綁定到了obj這個對象上下文中。當然如果有多個對象鏈式調用,this只會綁定到最后一層。obj2.obj1.foo(),this是綁定到obj1這個對象上下文中。

當然這里有一個注意點

var obj = {
    b: 3,
    foo: foo
}
function foo () {
    console.log(this.b);
}
var bar = obj.foo;

bar(); // 2 

這里實際上bar直接是foo的引用,就相當于var bar = obj.foo = foo,我們打印一下可以發(fā)現(xiàn)

console.log(bar === foo && foo === obj.foo && bar === obj.foo) // true

所以此時就和第一種默認綁定一樣,bar函數(shù)是直接在全局上下文中被調用的,所以this會指向全局。

還有一種就是嵌套函數(shù)了

b = 2;
var obj = {
    b: 3,
    foo: foo
}
function foo () {
    console.log('foo', this.b);// 3
    foo2();
}
function foo2() {
    console.log('foo2', this.b); // 2
}
obj.foo();
    

實際上foo2也是直接被(window)調用了。

顯示綁定call,apply,bind

通過call,apply,bind函數(shù)可以強制某個函數(shù)在哪個對象(或者上下文)中被調用

b = 2;

var obj = {
    b: 3,
    foo: foo
}

function foo () {
    console.log('foo', this.b);
}

foo.call(obj); // 3

當然如果你傳入的是一個基本類型的值,那么JavaScript會把它轉換成它的對象形式。

new綁定

說到new操作符,就不得不說它的內部工作原理了,我們在執(zhí)行new操作的時候究竟執(zhí)行了什么。

1 創(chuàng)建一個全新的對象 var obj = {}
2 這個新對象的原型會被執(zhí)行[[原型]]連接 obj[[prototype]] = Fun.prototye
3 這個新對象會綁定到函數(shù)調用的this Fun.bind(obj)
4 如果函數(shù)沒有返回其他對象,那么會返回這個新創(chuàng)建的對象 return obj;

所以new綁定實質還是顯式綁定。

總結一下我們可以按照下面的順序進行判斷

1 函數(shù)是否在new中調用(new 綁定),如果是this綁定的就是返回的新對象
2 函數(shù)是否通過call、apply(顯式綁定)如果是this綁定的是那個指定的對象
3 函數(shù)是否在某個上下文對象中調用(隱式綁定),如果是,this綁定的是那個上下文無關文法對象
4 如果都不是那么就是默認綁定,this綁定的就是全局對象或者undefined(嚴格模式)

例外

凡事總有例外,如果你把null、undefined作為this的綁定對象傳入call、apply或者bind那么實際上,這些值在執(zhí)行的時候會被忽略,實際使用的是默認綁定。那么什么情況下我們會去綁定一個null或者undefined的呢?一種就是用apply來展開一個數(shù)組,當然這種方法的確很實用(不過在ES6中出現(xiàn)了...操作符來展開數(shù)組)。

function foo(a, b) {return a + b}
foo.apply(null, [2, 3]);

箭頭函數(shù),箭頭函數(shù)中的this是根據(jù)外層作用域來決定this的,也就是說箭頭函數(shù)中的this就和箭頭函數(shù)在哪里聲明有關系了。

a = 2;

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

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


var fun = foo.call(obj);

fun(); // 3 此時箭頭函數(shù)的外層作用域為foo,foo函數(shù)被調用時,this被綁定在了obj對象上
a = 2;

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

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

function foo () {
    return arrowFun;    
}

var fun = foo.call(obj);

fun(); //2 箭頭函數(shù)的外層作用域為全局作用域,全局作用域中的this指向全局上下文

最后歡迎大家關注我的個人博客,將會有更多的精彩文檔,喜歡的話也可以給個star。

Github鏈接

DJL簫氏的博客

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 今天在朋友圈發(fā)了我的開心與焦慮,其實開心的事情不止是寶貝生日,還有德國簽證下來了,還有媽媽說家里后面的坪打好了,寶...
  • 我們遇到一點點事情總是抱怨,消極的情緒對待這件事情,一味的抱怨根本解決不了任何的事情,與其整天活在抱怨中,不...
    wh王輝閱讀 99評論 0 0

友情鏈接更多精彩內容