深刻理解this機制系列第一篇——this的4種綁定規則

若是要問javascript中哪兩個知識點容易混淆,做用域查詢和this機制絕對名列前茅。前面的做用域系列已經詳細介紹過做用域的知識。本系列開始將介紹javascript的另外一大山脈——this機制。本文是該系列的第一篇——this的4種綁定規則javascript

 

默認綁定

  全局環境中,this默認綁定到windowhtml

console.log(this === window);//true

  函數獨立調用時,this默認綁定到windowjava

function foo(){
    console.log(this === window);
}
foo(); //true

  被嵌套的函數獨立調用時,this默認綁定到window數組

複製代碼
//雖然test()函數被嵌套在obj.foo()函數中,但test()函數是獨立調用,而不是方法調用。因此this默認綁定到window
var a = 0;
var obj = {
    a : 2,
    foo:function(){
            function test(){
                console.log(this.a);
            }
            test();
    }
}
obj.foo();//0
複製代碼

【IIFE】閉包

  IIFE當即執行函數其實是函數聲明後直接調用執行app

複製代碼
var a = 0;
function foo(){
    (function test(){
        console.log(this.a);
    })()
};
var obj = {
    a : 2,
    foo:foo
}
obj.foo();//0
複製代碼
複製代碼
//等價於上例
var a = 0;
var obj = {
    a : 2,
    foo:function(){
            function test(){
                console.log(this.a);
            }
            test();
    }
}
obj.foo();//0
複製代碼

【閉包】函數

  相似地,test()函數是獨立調用,而不是方法調用,因此this默認綁定到windowthis

  [注意]函數共有4種調用方式,函數調用相關內容移步至此spa

複製代碼
var a = 0;
function foo(){
    function test(){
        console.log(this.a);
    }
    return test;
};
var obj = {
    a : 2,
    foo:foo
}
obj.foo()();//0
複製代碼

  因爲閉包的this默認綁定到window對象,但又經常須要訪問嵌套函數的this,因此經常在嵌套函數中使用var that = this,而後在閉包中使用that替代this,使用做用域查找的方法來找到嵌套函數的this值 code

複製代碼
var a = 0;
function foo(){
    var that = this;
    function test(){
        console.log(that.a);
    }
    return test;
};
var obj = {
    a : 2,
    foo:foo
}
obj.foo()();//2
複製代碼

 

隱式綁定

  通常地,被直接對象所包含的函數調用時,也稱爲方法調用,this隱式綁定到該直接對象

複製代碼
function foo(){
    console.log(this.a);
};
var obj1 = {
    a:1,
    foo:foo,
    obj2:{
        a:2,
        foo:foo
    }
}

//foo()函數的直接對象是obj1,this隱式綁定到obj1
obj1.foo();//1

//foo()函數的直接對象是obj2,this隱式綁定到obj2
obj1.obj2.foo();//2
複製代碼

 

隱式丟失

  隱式丟失是指被隱式綁定的函數丟失綁定對象,從而默認綁定到window。這種狀況容易出錯卻又常見

【函數別名】

複製代碼
var a = 0;
function foo(){
    console.log(this.a);
};
var obj = {
    a : 2,
    foo:foo
}
//把obj.foo賦予別名bar,形成了隱式丟失,由於只是把foo()函數賦給了bar,而bar與obj對象則毫無關係
var bar = obj.foo;
bar();//0
複製代碼
複製代碼
//等價於
var a = 0;
var bar = function foo(){
    console.log(this.a);
}
bar();//0
複製代碼

【參數傳遞】

複製代碼
var a = 0;
function foo(){
    console.log(this.a);
};
function bar(fn){
    fn();
}
var obj = {
    a : 2,
    foo:foo
}
//把obj.foo看成參數傳遞給bar函數時,有隱式的函數賦值fn=obj.foo。與上例相似,只是把foo函數賦給了fn,而fn與obj對象則毫無關係
bar(obj.foo);//0
複製代碼
複製代碼
//等價於
var a = 0;
function bar(fn){
    fn();
}
bar(function foo(){
    console.log(this.a);
});
複製代碼

【內置函數】

  內置函數與上例相似,也會形成隱式丟失

複製代碼
var a = 0;
function foo(){
    console.log(this.a);
};
var obj = {
    a : 2,
    foo:foo
}
setTimeout(obj.foo,100);//0
複製代碼
//等價於
var a = 0;
setTimeout(function foo(){
    console.log(this.a);
},100);//0

【間接引用】

   函數的"間接引用"通常都在無心間建立,最容易在賦值時發生,會形成隱式丟失

複製代碼
function foo() {
    console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
//將o.foo函數賦值給p.foo函數,而後當即執行。至關於僅僅是foo()函數的當即執行
(p.foo = o.foo)(); // 2
複製代碼
複製代碼
function foo() {
    console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
//將o.foo函數賦值給p.foo函數,以後p.foo函數再執行,是屬於p對象的foo函數的執行
p.foo = o.foo;
p.foo();//4
複製代碼

 【其餘狀況】

  在javascript引擎內部,obj和obj.foo儲存在兩個內存地址,簡稱爲M1和M2。只有obj.foo()這樣調用時,是從M1調用M2,所以this指向obj。可是,下面三種狀況,都是直接取出M2進行運算,而後就在全局環境執行運算結果(仍是M2),所以this指向全局環境

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

(obj.foo = obj.foo)();//0

(false || obj.foo)();//0

(1, obj.foo)();//0
複製代碼

 

顯式綁定

  經過call()、apply()、bind()方法把對象綁定到this上,叫作顯式綁定。對於被調用的函數來講,叫作間接調用

複製代碼
var a = 0;
function foo(){
    console.log(this.a);
}
var obj = {
    a:2
};
foo();//0
foo.call(obj);//2
複製代碼

  普通的顯式綁定沒法解決隱式丟失問題

複製代碼
var a = 0;
function foo(){
    console.log(this.a);
}
var obj1 = {
    a:1
};
var obj2 = {
    a:2
};
foo.call(obj1);//1
foo.call(obj2);//2
複製代碼

【硬綁定】

  硬綁定是顯式綁定的一個變種,使this不能再被修改

複製代碼
var a = 0;
function foo(){
    console.log(this.a);
}
var obj = {
    a:2
};
var bar= function(){
    foo.call(obj);
}
//在bar函數內部手動調用foo.call(obj)。所以,不管以後如何調用函數bar,它總會手動在obj上調用foo
bar();//2
setTimeout(bar,100);//2
bar.call(window);//2
複製代碼

【API】

  javascript中新增了許多內置函數,具備顯式綁定的功能,如數組的5個迭代方法:map()、forEach()、filter()、some()、every()

複製代碼
var id = 'window';
function foo(el){
    console.log(el,this.id);
}
var obj = {
    id: 'fn'
};
[1,2,3].forEach(foo);//1 "window" 2 "window" 3 "window"
[1,2,3].forEach(foo,obj);//1 "fn" 2 "fn" 3 "fn"
複製代碼

 

new綁定

  若是函數或者方法調用以前帶有關鍵字new,它就構成構造函數調用。對於this綁定來講,稱爲new綁定

  【1】構造函數一般不使用return關鍵字,它們一般初始化新對象,當構造函數的函數體執行完畢時,它會顯式返回。在這種狀況下,構造函數調用表達式的計算結果就是這個新對象的值

function fn(){
    this.a = 2;
}
var test = new fn();
console.log(test);//{a:2}

  【2】若是構造函數使用return語句但沒有指定返回值,或者返回一個原始值,那麼這時將忽略返回值,同時使用這個新對象做爲調用結果

複製代碼
function fn(){
    this.a = 2;
    return;
}
var test = new fn();
console.log(test);//{a:2}
複製代碼

  【3】若是構造函數顯式地使用return語句返回一個對象,那麼調用表達式的值就是這個對象

複製代碼
var obj = {a:1};
function fn(){
    this.a = 2;
    return obj;
}
var test = new fn();
console.log(test);//{a:1}
複製代碼

  [注意]儘管有時候構造函數看起來像一個方法調用,它依然會使用這個新對象做爲this。也就是說,在表達式new o.m()中,this並非o

複製代碼
var o = {
    m: function(){
        return this;
    }
}
var obj = new o.m();
console.log(obj,obj === o);//{} false
console.log(obj.constructor === o.m);//true
複製代碼

 

嚴格模式

  【1】嚴格模式下,獨立調用的函數的this指向undefined

複製代碼
function fn(){
    'use strict';
    console.log(this);//undefined
}
fn();

function fn(){
    console.log(this);//window
}
fn();
複製代碼

  【2】在非嚴格模式下,使用函數的call()或apply()方法時,null或undefined值會被轉換爲全局對象。而在嚴格模式下,函數的this值始終是指定的值

複製代碼
var color = 'red';
function displayColor(){
    console.log(this.color);
}
displayColor.call(null);//red

var color = 'red';
function displayColor(){
    'use strict';
    console.log(this.color);
}
displayColor.call(null);//TypeError: Cannot read property 'color' of null
複製代碼

 

最後

  this的四種綁定規則:默認綁定、隱式綁定、顯式綁定和new綁定,分別對應函數的四種調用方式:獨立調用、方法調用、間接調用和構造函數調用。

  分清這四種綁定規則不算難,比較麻煩的是須要練就火眼金睛,識別出隱式丟失的狀況

  說到底,javascript如此複雜的緣由是由於函數過於強大。由於,函數是對象,因此原型鏈比較複雜;由於函數能夠做爲值被傳遞,因此執行環境棧比較複雜;一樣地,由於函數具備多種調用方式,因此this的綁定規則也比較複雜

  只有理解了函數,纔算理解了javascript

  以上

相關文章
相關標籤/搜索