一篇文章讓你搞定JavaScript的this指向問題

圖片

該文章配有我的的視頻講解,但願看文章的同窗,能夠看文章,文章看不太明白的,結合視頻講解瞬間明白。《深刻學習JavaScript的this指向》
javascript


若是要問javascript中哪兩個知識點容易混淆,做用域查詢和this機制絕對名列前茅。前面章節已經詳細介紹過做用域的知識。本章將介紹this機制。java

this綁定規則

默認綁定

全局環境下,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綁定


若是函數或者方法調用以前帶有關鍵字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

相關文章
相關標籤/搜索