該文章配有我的的視頻講解,但願看文章的同窗,能夠看文章,文章看不太明白的,結合視頻講解瞬間明白。《深刻學習JavaScript的this指向》
javascript
若是要問javascript中哪兩個知識點容易混淆,做用域查詢和this機制絕對名列前茅。前面章節已經詳細介紹過做用域的知識。本章將介紹this機制。java
全局環境下,this默認綁定到window數組
console.log(this === window); //true
函數獨立調用時,this默認綁定到window閉包
function foo(){ console.log(this === window);}foo();//true
被嵌套的函數獨立調用時,this默認綁定到windowapp
var a = 0;var obj = { a : 2, foo : function (){ function test(){ console.log(this); } test(); }}obj.foo();
上面代碼雖然test()
函數被嵌套在obj.foo()
函數中,但test()
函數是獨立調用,而不是方法調用。因此this默認綁定到windowide
IIFE函數
IIFE當即執行函數實際是函數聲明後當即調用執行,內部的this指向了window學習
var a = 0;function foo(){ (function test(){ console.log(this.a); })()};var obj = { a : 2, foo:foo}obj.foo();//0
等價於上例ui
var a = 0;var obj = { a : 2, foo : function(){ function test(){ console.log(this.a); } test(); }}obj.foo();//0
閉包this
相似地,test()
函數是獨立調用,而不是方法調用,因此this默認綁定到window
注意:函數共有4中調用方法
var a = 0;function foo() { function test() { console.log(this.a); } test();}var obj = { a: 2; foo: foo}obj.foo();//0
因爲閉包的this默認綁定到window對象,但又經常須要訪問嵌套函數的this,因此經常在嵌套函數中使用var that = this
,而後在閉包中使用that替代this,使用做用域查找的方法來找到嵌套函數的this值
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隱式綁定到obj1obj1.foo();//1//foo()函數的直接對象是obj2,this隱式綁定到obj2obj1.obj2.foo();//2
隱式丟失是指被隱式綁定的函數丟失綁定對象,從而默認綁定到window。這種狀況容易出錯卻又常見
【函數別名】
var a = 0;function foo(){ console.log(this.a);}var obj = { a: 1, 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
【間接調用】
函數的「間接引用」通常都在無心間建立,最容易在賦值時發生,會形成隱式丟失
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();//0foo.call(obj);//2
普通的顯示綁定沒法解決隱式丟失問題
var a = 0;function foo(){ console.log(this.a);}var obj1 = { a : 1};var obj2 = { a : 2}foo.call(obj1);//1foo.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上調用foobar();//2setTimeout(bar,2000);//2bar.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,它就構成構造函數調用。對於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);//{} falseconsole.log(obj.contructor === 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