1. 程式人生 > >JS之this的指向理解

JS之this的指向理解

1.this是什麼?

this是物件自動生成的一個內部物件,是在執行時基於函式的執行環境繫結的,因為函式的呼叫場合不同,this的值也有變化。
this指向什麼,完全取決於 什麼地方以什麼方式呼叫,而不是 建立時 。這句話目前也只能說在ES5中才是正確的,而在ES6的箭頭函式中,this的指向就是在定義的時候就確定的。

2. this的繫結規則

this的繫結總共差不多有下面五種:

  1. 預設繫結
  2. 隱式繫結
  3. 顯示繫結
  4. new繫結
  5. ES6箭頭函式中的this
2.1.預設繫結
function foo(){
    console.log(this.a);    // 10
}
var a = 10;
foo();

作為獨立函式的呼叫,this指向的是全域性物件window,而在嚴格模式下,不能將全域性物件用於預設繫結,this會繫結到undefined。只有函式執行在非嚴格模式下,預設繫結才能繫結到全域性物件。

2.2.隱式繫結
function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
foo();  //undefined
obj.foo(); // 2

foo() 這個就是預設繫結,等價於列印 window.a ,故輸出 undefined ;
obj.foo(),函式有上下文物件,即obj,這種情況下, 函式裡的this預設繫結為上下文物件 ,等價於列印 obj.a ,故輸出2 。如果是鏈性的關係,比如 xx.yy.obj.foo(); , 上下文取函式的直接上級,即緊挨著的那個,或者說物件鏈的最後一個。
再看下一個情況:

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; // 函式別名
var a =,10; // a是全域性物件的屬性
bar(); // 10

雖然bar為obj.foo的引用,但實際上引用的是foo函式本身,此時this指向為預設繫結,指向的window,所以輸出10

2.3.顯示繫結

顯示繫結就是call apply bind,call和apply它們的作用都是改變函式的this指向 , 第一個引數都是 設定this物件 。
兩個函式的區別:
call從第二個引數開始所有的引數都是 原函式的引數。
apply只接受兩個引數,且第二個引數必須是陣列,這個陣列代表原函式的引數列表。

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2
};
foo.call( obj ); // 2  呼叫foo時強制把foo的this繫結到obj上

bind只有一個函式,且不會立刻執行,只是將一個值繫結到函式的this上,並將繫結好的函式返回。

function foo(){
    console.log(this.a);
}
var obj = { a : 10 };
foo = foo.bind(obj);
foo();                    // 10
2.4.new繫結

js中的只要用new修飾的 函式就是’建構函式’,準確來說是 函式的 構造呼叫 ,因為在js中並不存在所謂的’建構函式’。
那麼用new 做到函式的 構造呼叫 後,js幫我們做了什麼工作呢:

function createNew() 
    let obj = new Object()    // 建立一個空的物件
    let Con = [].shift.call(arguments)    // 獲得建構函式
    obj.__proto__ = Con.prototype    // 連結到原型
    let result = Con.apply(obj, arguments)    // 繫結 this,執行建構函式
    return typeof result === 'object' ? result : obj    // 確保 new 出來的是個物件
}

1、建立(或者說構造)一個新物件。
2、這個新物件會被執行[[Prototype]]連線。
3、這個新物件會繫結到函式呼叫的this。
4、如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。

function foo(){
    this.a = 10;
    console.log(this);
}
foo();                    // window物件
console.log(window.a);    // 10   預設繫結
var obj = new foo();      //// 等價於 foo { a : 10 };  var obj = foo;                         
console.log(obj.a);       // 10    new繫結

特別注意 : 如果原函式返回一個物件型別,那麼將無法返回新物件,你將丟失繫結this的新物件,例:

function foo(){
    this.a = 10;
    return new String("啊哈哈");
}
var obj = new foo();
console.log(obj.a);       // undefined
console.log(obj);         // "啊哈哈"
2.5 ES6箭頭函式中的this

foo()內部建立的箭頭函式會捕獲呼叫時foo()的this。由於foo()的this繫結到obj1,bar(引用箭頭函式)的this也會繫結到obj1,箭頭函式的繫結無法被修改(new也不行)。

function foo() {
    return (a) => {         //返回一個箭頭函式
        console.log( this.a );        // this繼承自foo()
    };
}
var obj1 = {
    a: 2
};
var obj2 = {
    a: 3
}
var bar = foo.call( obj1 );
bar.call( obj2 );        // 2,不是3!

3. 優先順序

new 繫結 > 顯示繫結 > 隱式繫結 > 預設繫結

4. 總結

1.如果函式是在某個 上下文物件 下被呼叫:this繫結的是那個上下文物件,例 : var obj = { foo : foo }; obj.foo(); foo 中的 this 就是 obj
2.如果函式是使用 call,apply,bind 來呼叫的:this繫結的是 call,apply,bind 的第一個引數.例: foo.call(obj); , foo 中的 this 就是 obj
3.如果函式被 new 修飾:this繫結的是新建立的物件,例:var bar = new foo(); 函式 foo 中的 this 就是一個叫foo的新建立的物件 , 然後將這個物件賦給bar
4.如果都不是,則是預設繫結

5.常見題目分析

var x = 10;
var obj = {
    x: 20,
    f: function(){
        console.log(this.x);        //   20
                                            //隱式繫結,this指向上下文obj
        var foo = function(){ 
            console.log(this.x);    
            }
        foo();                      //10  預設繫結 指向的是window物件
    }
};
obj.f();
function foo(arg){
    this.a = arg;
    return this
};
var a = foo(1);
var b = foo(10);
console.log(a.a);    // ?undefined
console.log(b.a);    // ?10

分析:foo(1)執行,應該不難看出是預設繫結吧 , this指向了window,函式裡等價於 window . a = 1,return window;
var a = foo(1) 等價於 window . a = window , 很多人都忽略了 var a 就是window.a ,將剛剛賦值的 1 替換掉了。
所以這裡的 a 的值是 window , a . a 也是window , 即window . a = window ; window . a . a = window;
foo(10) 和第一次一樣,都是預設繫結,這個時候, 將window.a 賦值成 10 ,注意這裡是關鍵,原來window.a = window ,現在被賦值成了10,變成了值型別,所以現在 a.a = undefined。(驗證這一點只需要將var b = foo(10);刪掉,這裡的 a.a 還是window)
var b = foo(10); 等價於 window.b = window;
本題中所有變數的值,a = window.a = 10 , a.a = undefined , b = window , b.a = window.a = 10;

var num = 1;
var myObject = {
    num: 2,
    add: function() {
        this.num = 3;
        (function() {
            console.log(this.num); //預設繫結  指向的window   輸出1
            this.num = 4;
        })();
        console.log(this.num);   
    },
    sub: function() {
        console.log(this.num)
    }
}
myObject.add();         
console.log(myObject.num); 
console.log(num);
var sub = myObject.sub;
sub();

依次輸出 1 3 3 4 4