JS中this的4種綁定規則

this

  • ES6中的箭頭函數採用的是詞法做用域。
  • 爲何要使用this:使API設計得更簡潔且易於複用。
  • this即不指向自身,也不指向函數的詞法做用域。
  • this的指向只取決於函數的調用方式

this綁定規則

  • new > 顯示綁定 > 隱式綁定 > 默認綁定

默認綁定

  • 當獨立函數調用時,無論是否在調用棧中,this都指向全局對象(瀏覽器中爲window)
  • 嚴格模式下,不能將全局對象用於默認綁定。
var a = 2;
function foo(){
    console.log(this.a);
}
function bar(){
    var a = 5;
    foo();
}
bar(); // 2

隱式綁定

  • 當函數引用有上下文對象時,隱式綁定規則會把函數調用中的this綁定到這個上下文對象。
  • 對象屬性引用鏈中只有最後一層在調用位置中起做用。
  • 要求:對象內部必須包含一個指向函數的屬性,該對象可經過這個屬性間接引用函數。
function foo() {
    console.log( this.a );
}

var obj2 = {
    a: 42,
    foo: foo
};

var obj1 = {
    a: 2,
    obj2: obj2
};

obj1.obj2.foo(); // 42
  • 隱式丟失
function foo() {
    console.log( this.a );
}

var obj = {
    a: 2,
    foo: foo
};

var bar = obj.foo; // 這裏bar將引用foo函數自己,因此不帶有函數對象的上下文

var a = "oops, global"; // a是全局對象的屬性

bar(); // "oops, global"

和回調函數的狀況下(參數傳遞時的隱式賦值)git

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

function doFoo(fn) {
    // 參數傳遞時,至關於fn = obj.foo,就和上個例子同樣了
    fn(); // <-- call-site!
}

var obj = {
    a: 2,
    foo: foo
};

var a = "oops, global"; // `a` also property on global object

doFoo( obj.foo ); // "oops, global"

顯式綁定

  • 採用call()和apply(),經過傳入一個對象(若爲基本類型,會被封裝函數轉爲對象—裝箱),將this綁定到該對象。
  • 硬綁定
function foo() {
    console.log( this.a );
}

var obj = {
    a: 2
};

var bar = function() {
    foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

// 硬綁定後bar不管怎麼調用,都不會影響foo函數的this綁定
bar.call( window ); // 2

硬綁定的典型應用是以下的包裹函數:github

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}

var obj = {
    a: 2
};

var bar = function() {
    return foo.apply( obj, arguments ); // 將obj對象硬編碼進去
};

var b = bar( 3 ); // 2 3
console.log( b ); // 5

即將內部函數用apply硬綁定到某個對象,不管怎麼調用這個包裹函數,都不會影響內部函數的this。
bind輔助函數以下:瀏覽器

function foo(something) {
    console.log( this.a, something );
    return this.a + something;
}

// simple `bind` helper
function bind(fn, obj) {
    return function() {
        return fn.apply( obj, arguments ); // 利用參數將obj傳入進去
    };
}

var obj = {
    a: 2
};

var bar = bind( foo, obj ); // bind( foo, obj )會返回一個包裹函數

var b = bar( 3 ); // 2 3
console.log( b ); // 5

總結:上述包裹函數,想要包裹其餘函數,只能一個一個重複寫,硬編碼的方式致使不能被重用,當某種功能須要屢次重複使用時,將其抽象出來,成爲函數。app

new綁定

  • 任何函數均可能被用做構造函數,當函數被new操做符「構造調用」時,會執行下面操做:

1. 建立一個新對象(若該函數不是JS內置的,則建立一個新的Object對象);
2. 將this綁定到這個對象;
3. 執行構造函數中的代碼(爲這個新對象添加屬性);
4. 若函數沒有返回其餘對象,則自動返回這個新對象;若函數有return返回的是非對象,則仍是自動返回這個新對象,即覆蓋那個非對象。函數

function foo(a) {
    this.a = a;
}

var bar = new foo( 2 );
console.log( bar.a ); // 2

補充說明

  • 間接引用
function foo() {
    console.log( this.a );
}

var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };

o.foo(); // 3
(p.foo = o.foo)(); // 2,因爲p.foo = o.foo的返回值是目標函數的引用,因此調用位置是foo(),而不是p.foo()或o.foo()
  • 箭頭函數:不使用這四個this規則,根據詞法做用域來決定this。
function foo() {
    // 返回一個箭頭函數
    return (a) => {
        // `this` here is lexically adopted from `foo()`
        console.log( this.a );
    };
}

var obj1 = {
    a: 2
};

var obj2 = {
    a: 3
};

// foo()不是箭頭函數,他的this被綁定到obj1
var bar = foo.call( obj1 ); // foo.call( obj1 )返回箭頭函數,因此bar爲箭頭函數
bar.call( obj2 ); // 2! 箭頭函數的this沒法被修改,new也不行

以下爲和箭頭函數同樣的模式:oop

function foo() {
    var self = this; // lexical capture of `this`
    setTimeout( function(){
        console.log( self.a );
    }, 100 );
}

var obj = {
    a: 2
};

foo.call( obj ); // 2

this綁定的趣題:

知乎連接-arguments對象調用this

更多博客:https://github.com/Lmagic16/blog編碼

相關文章
相關標籤/搜索