完全弄懂Javascript中的this

在很長的一段時間以內,我一直覺得做用域就是上下文,這也就對JavaScript中的this理解增長了不少麻煩,因此這篇文章開篇第一個要陳訴的概念就是做用域和上下文不是一個概念。做用域(scope) 是指變量的可訪問性,上下文是來決定this。(注意執行期上下文指的是做用域,這是JavaScipt規範,因此得遵照)javascript

在JavaScript中只有兩種做用域,一種是全局做用域,另外一個就是函數做用域。上下文則會this息息相關,而this是在運行的時候進行綁定的,它的上下文取決於函數在哪裏被調用,this的綁定和函數聲明的位置沒有任何關係。java

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

固然這句話出自《你不知道的JavaScript(上卷)》,在這裏強烈推薦這本書,字字珠璣。github

再次強調:this其實是在函數被調用的時候發生綁定,它指向什麼徹底取決於函數在哪裏調用。數組

調用位置

接下來咱們看看函數調用包括哪幾種狀況,只有正確的知道函數調用的位置,才能正確的明白this的指向問題。bash

默認綁定(全局調用)

var a = 2;
function foo() {
    console.log(this.a)
}
foo();
複製代碼

以上就是默認綁定,foo函數是直接調用的。app

隱式綁定

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

function foo () {
    console.log(this.b);
}
obj.foo(); // 3
複製代碼

這裏爲何叫作隱式綁定,由於這個foo函數不管是在obj裏面聲明仍是在obj外面聲明,他實際上都是不屬於obj這個對象的(obj只是記錄了foo這個屬性的引用值),可是最後在執行的時候this卻被綁定到了obj這個對象上下文中。固然若是有多個對象鏈式調用,this只會綁定到最後一層。obj2.obj1.foo(),this是綁定到obj1這個對象上下文中。函數

固然這裏有一個注意點ui

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,咱們打印一下能夠發現this

console.log(bar === foo && foo === obj.foo && bar === obj.foo) // true
複製代碼

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

還有一種就是嵌套函數了

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函數能夠強制某個函數在哪一個對象(或者上下文)中被調用

b = 2;

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

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

foo.call(obj); // 3
複製代碼

固然若是你傳入的是一個基本類型的值,那麼JavaScript會把它轉換成它的對象形式。

new綁定

說到new操做符,就不得不說它的內部工做原理了,咱們在執行new操做的時候究竟執行了什麼。

1 建立一個全新的對象 var obj = {} 2 這個新對象的原型會被執行[[原型]]鏈接 obj[[prototype]] = Fun.prototye 3 這個新對象會綁定到函數調用的this Fun.bind(obj) 4 若是函數沒有返回其餘對象,那麼會返回這個新建立的對象 return obj;

因此new綁定實質仍是顯式綁定。

總結一下咱們能夠按照下面的順序進行判斷

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

例外

凡事總有例外,若是你把null、undefined做爲this的綁定對象傳入call、apply或者bind那麼實際上,這些值在執行的時候會被忽略,實際使用的是默認綁定。那麼什麼狀況下咱們會去綁定一個null或者undefined的呢?一種就是用apply來展開一個數組,固然這種方法的確很實用(不過在ES6中出現了...操做符來展開數組)。

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

箭頭函數,箭頭函數中的this是根據外層做用域來決定this的,也就是說箭頭函數中的this就和箭頭函數在哪裏聲明有關係了。

a = 2;

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

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

var fun = foo.call(obj);

fun(); // 3 此時箭頭函數的外層做用域爲foo,foo函數的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 箭頭函數的外層做用域爲全局做用域,全局做用域中的this指向全局上下文
複製代碼

最後

最後歡迎你們關注個人我的博客,將會有更多的精彩文檔,喜歡的話也能夠給個star。

Github連接

DJL簫氏的博客

相關文章
相關標籤/搜索