JavaScript-this綁定的優先級

1、調用位置

理解this綁定優先級的前提是理解this的綁定規則,理解綁定規則以前,咱們先來了解一下函數"調用位置"。編程

\color{red}{調用位置就是函數在代碼中被調用的位置,而不是函數聲明的位置。}

一般來講,要想找到調用位置,最重要的是分析調用棧(在有的編程模式下,真正的調用位置可能被隱藏起來了,經過調用棧來分析調用位置最爲直接)。bash

來個梨子:app

function baz() {
    // 當前調用棧是:baz
    // 調用位置:全局做用域
    console.log('baz');
    bar();
}

function bar() {
    // 當前調用棧是:baz -> bar
    // 調用位置:baz中
    console.log('bar');
    foo();
}

function bar() {
    // 當前調用棧是:baz -> bar -> foo
    // 調用位置:bar中
    console.log('foo');
}

baz(); // 全局調用
複製代碼

如咱們在梨子中標註的同樣,你能夠把調用棧理解成一個函數鏈式調用。其實咱們有一種更爲簡單的方式查找調用棧,那就是JavaScript開發者工具。如圖。函數

2、綁定規則

接下來咱們就看看函數在運行的過程當中調用位置如何決定this的綁定對象。工具

一、默認綁定(獨立函數調用)

var number = 1;
function baz() {
    console.log(this.number);
}
baz(); // 1
複製代碼

當函數baz被調用時,this.number被解析成全局變量number。函數在調用時,進行默認綁定,此時的this指向全局對象(非嚴格模式),嚴格模式下this爲undefined。ui

var number = 1;
function baz() {
    "use strict"
    console.log(this.number);
}
baz();
複製代碼

二、隱式綁定(對象方法調用)

function baz() {
    console.log(this.number);
}
var object = {
    number: 1,
    baz: baz
};
object.baz(); // 1
複製代碼

函數baz()的聲明方式,嚴格來講是不屬於object對象的,可是調用位置會使用object上下文來引用函數。因此咱們能夠說object對象"擁有"或者"包含"baz()函數的引用。this

隱式綁定,this丟失

function baz() {
    console.log(this.number);
}
var object = {
    number: 1,
    baz: baz
};
var bar = object.baz();
var number = 2;
bar(); // 2
複製代碼

雖然bar是object.baz的一個引用,可是它是引用foo函數自己,因應用了默認綁定。this指向全局變量。spa

function baz() {
    console.log(this.number);
}
function loadBaz(fn){
    // fn其實就是引用的baz
    fn(); // 回調函數的調用位置
}
var object = {
    number: 1,
    baz: baz
};
var number = 2;
loadBaz(object.baz); // 2
複製代碼

參數傳遞其實也是一種隱式的賦值,所以咱們在傳入函數時也會被隱藏賦值,因此,梨子2和梨子1是同樣的結果。prototype

三、顯示綁定(apply、call、bind)

function baz() {
    console.log(this.number);
}
var object = {
    number: 1,
    baz: baz
};
baz.call(object); // 1
// 或者baz.apply(object); // 1
複製代碼

四、new綁定

使用new來調用函數,或者說發生構造函數調用時,會執行下面的操做。code

  1. 建立或者說構造一個全新的對象。
  2. 這個新對象會被執行[[Prototype]]鏈接。
  3. 這個新對象會綁定到函數調用的this。
  4. 若是函數沒有返回其餘隊形,那麼new表達式中的函數調用會自動返回這個新對象。
function baz(number) {
    this.number = number;
}
var bar = new baz(1);
console.log(bar.a); // 1
複製代碼

使用new來調用baz()時,咱們會構造一個新的對象並綁定到baz()調用中的this上。

3、優先級(本文主角)

前面簡單講解了this綁定的四條規則,你須要作的就是找到調用位置,判斷使用那一條規則。可是,有時候,在一個調用位置可能使用了多條規則,應該若是判斷了。這裏就須要判斷規則的優先級(如CSS的權重同樣)。

判斷1:默認綁定的優先級最低

\color{red}{毫無疑問,在四條規則中,默認綁定的優先級是四條規則中最低的。咱們先能夠不用管它。}

判斷2:隱式綁定和顯示綁定誰的優先級高?

function foo() {
    console.log(this.a);
}
var obj1 = {
    a:2,
    foo: foo
};
var obj2 = {
    a:3,
    foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3

obj1.foo.call(obj2); // 3 
obj2.foo.call(obj1); // 2 
複製代碼

\color{red}{在梨子中咱們能夠看到,顯示綁定的優先級更高。}

判斷3:隱式綁定和new綁定誰的優先級高?

function foo(a) {
    this.a = a;
}
var obj1 = {
    foo: foo
};
var obj2 = {};

obj1.foo(2);
console.log(obj1.a); // 2

obj1.foo.call(obj2, 3);
console.log(obj2.a); // 3

var bar = new obj1.foo(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4
複製代碼

\color{red}{在梨子中咱們能夠看到,new綁定的優先級更高。}

判斷4:顯示綁定和new綁定誰的優先級高?

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

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

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3
複製代碼

what?出乎意料呀。bar被綁定到obj1上,可是new bar(3) 並無像咱們預計的那樣把obj1.a修改成3相反,new修改了綁定調用bar()中的this。那到底顯示綁定和new綁定誰的優先級高?

咱們來看看ES5內置的Function.prototype.bind()(顯示綁定-強綁定)的實現。

MDN:Function.prototype.bind()的實現

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true時,說明返回的fBound被當作new的構造函數調用
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 獲取調用時(fBound)的傳參.bind 返回的函數入參每每是這麼傳遞的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // 維護原型關係
    if (this.prototype) {
      // 當執行Function.prototype.bind()時, this爲Function.prototype 
      // this.prototype(即Function.prototype.prototype)爲undefined
      fNOP.prototype = this.prototype; 
    }
    // 下行的代碼使fBound.prototype是fNOP的實例,所以
    // 返回的fBound若做爲new的構造函數,new生成的新對象做爲this傳入fBound,新對象的__proto__就是fNOP的實例
    fBound.prototype = new fNOP();

    return fBound;
  };
}
複製代碼

在這段代碼中,會判斷綁定函數是否被new調用,\color{red}{若是是的話就使用新建立的this替換綁定的this}\color{red}{換句話說,在使用顯示綁定的時候,若是被new綁定調用}\color{red}{顯示綁定this優先級是低於new綁定this優先級}。 之因此要在new中綁定函數,緣由是預先設置函數的一些參數,這樣在使用時,只須要傳入剩餘的參數。

根據上面的梨子:總結一下:

new綁定優先級 > 顯示綁定優先級 > 隱式綁定優先級 > 默認綁定優先級

4、判斷this(根據調用位置判斷調用規則)

  1. 函數是否存在new綁定調用:若是是的話this綁定到新建立的對象上。
  2. 函數是否經過apply、call、bind顯示綁定:若是是的話,this綁定到指定對象上。
  3. 函數是否在對象方法隱式調用:若是是的話,this綁定到調用對象。
  4. 若是上面三條都不知足的話:在嚴格模型下,this綁定到undefined,在非嚴格模式下,this綁定到全局對象上。

5、總結

但願這個文章能對閱讀的你有所幫助。讓咱們一塊兒成長吧。謝謝!

參考:《你不知道的JavaScript》

相關文章
相關標籤/搜索