一文搞懂 this、apply、call、bind

碼文不易,轉載請帶上本文鏈接,感謝~ //www.cnblogs.com/echoyya/p/14506269.html

this 的指向

「this」 關鍵字允許在調用函數或方法時決定哪個對象應該是焦點。

在JavaScript中this可以是全局對象、當前對象或者任意對象,這完全取決於函數的調用方式,this 綁定的對象即函數執行的上下文環境,在 ES5 中,其實 this 的指向,始終堅持一個原理:this 永遠指向最後調用它的那個對象,正是由於調用function的對象不同,才導致了this的指向不同。

關於this的指向及綁定,請關注博文 JS五種綁定徹底弄懂this,默認綁定、隱式綁定、顯式綁定、new綁定、箭頭函數綁定詳解

// e.g.1
var test = {
  a: 5,
  b: 6,
  sum: function (a, b) {
    function getA(a) {
      this.a = a;    // 在window上增加了一個全局變數a
      return this.a; // 此處this = window
    }

    function getB(b) {
      this.b = b;    //在window上增加了一個全局變數b
      return this.b; // 此處this = window
    }
    return getA(a) + getB(b);
  }
}
console.log(test.sum(4, 3));  // 7
console.log(a);     // 4   (列印結果為 window.a)  
console.log(b);     // 3   (列印結果為 window.b)  
// e.g.2
var user = {
  name: 'Echoyya',
  age: 27,
  greet() {
  	console.log(this.name)
  },
  bf: {
    name: 'KK.unlock',
    greet() {
      console.log(this.name)
    }
  }
}

user.greet()      // Echoyya
user.bf.greet()   // KK.unlock

如果 greet 函數不是 user 對象的函數,只是一個獨立的函數。

function greet () {
  console.log(this.name)
}

var user = { name: 'Echoyya' }

如何能讓 greet 方法調用的時候將 this 指向 user 對象?不能再像之前使用 user.greet(),因為 user 並沒有 greet 方法。我們可以通過一些方法去改變this的指向

怎樣改變 this 的指向

(1)使用ES6中箭頭函數

箭頭函數中的 this 始終指向函數定義時的 this,而非執行時。箭頭函數中沒有 this 綁定,必須通過查找作用域鏈來決定其值,如果箭頭函數被非箭頭函數包含,則 this 綁定的是最近一層非箭頭函數的 this,否則,this 為 undefined」。

也正因如此,箭頭函數不能用於構造函數

var name = "windowsName";

var obj = {
  name: "Echoyya",
  func1: function () {
    console.log(this.name)
  },
  func2: () => {
      console.log(this.name)
    }
};

obj.func1() // Echoyya

obj.func2() // windowsName

(2)函數內部使用 _this = this

如果不使用 ES6,那麼這種方式應該是最簡單且不易出錯的方式了,先將調用這個函數的對象保存在變數 _this 中,然後在函數中都使用這個 _this

obj調用functhis 指向obj,防止setTimeout 被 window 調用,導致 setTimeout 中的 this 指向 window,設置 var _this = this,將 this(指向變數 obj) 賦值給一個變數 _this,這樣,在 func 中使用_this 就指向對象 obj

var name = "windowsName";

var obj = {
  name: "Echoyya",
  func: function () {
    var _this = this;
    setTimeout(function () {
      console.log(_this.name)
    }, 1000);
  }
};

obj.func() // Echoyya

(3)使用call,apply,bind方法

call、apply、bind 方法是每個函數都有的一個方法,允許在調用函數時為函數指定上下文。可以改變 this 指向

call 與 apply
  • call 方法,第一個參數會作為函數被調用時的上下文。即this 指向傳給 call 的第一個參數。
  • 傳遞參數給使用 .call 調用的函數時,需要在指定上下文(第一個參數)後一個一個地傳入。
  • 語法:fun.call(thisArg [, arg1[, arg2[, ...]]])

站在函數應用的角度我們知道了call與apply的用途,那這兩個方法又有什麼區別呢,其實區別就一點,參數傳遞方式不同。

  1. call方法中接受的是一個參數列表,第一個參數指向this,其餘的參數在函數執行時都會作為函數形參傳入函數。

    • 語法:fn.call(this, arg1, arg2, ...);
  2. 而apply不同的地方是,除了第一個參數作為this指向外,其它參數都被包裹在一個數組中,在函數執行時同樣會作為形參傳入。

    • 語法:fn.apply(this, [arg1, arg2, ...]);

除此之外,兩個方法的效果完全相同:

var o = {
    a: 1
};

function fn(b, c) {
    console.log(this.a + b + c);
};
fn.call(o, 2, 3);     // 6
fn.apply(o, [2, 3]);  //6
關於 bind

bind要單獨說一下,因為它與call,apply還不太一樣。call與apply在改變this的同時,就立刻執行,而bind綁定this後並不會立馬執行,而是返回一個新的綁定函數,需要在執行一下。

var o = {
    a: 1
};

function fn(b, c) {
    console.log(this.a + b + c);
};

var fn2 = fn.bind(o, 2, 3);

fn2();   //6
var name = 'windowsName'

function greet () {
  console.log(this.name)
}

var user = { name: 'Echoyya' }

greet()          // windowsName
greet.bind()()   // windowsName  (非嚴格模式下,默認指向window)
greet.bind(user)()   // Echoyya
var obj = {
  name: "Echoyya",
  func: function () {
    setTimeout(function () {
      console.log(this.name)
    }.bind(obj)(), 100);
  }
};

obj.func() // Echoyya
call、apply、bind 區別

call、apply、bind 都是可以改變 this 的指向的,但是這三個函數稍有不同。

var test = {
  a: 5,
  b: 6,
  sum: function (a, b) {
    var self = this;

    function getA() {
      return self.a;
    }

    function getB() {
      return self.b;
    }
    console.log(a, b);    // call, apply, bind 傳入參數
    return getA() + getB();
  }
}
var obj = {
  a: 2,
  b: 3
};

console.log(test.sum.call(obj, 4, 5));    // 4,5,5  (self = this = obj)   
console.log(test.sum.apply(obj, [6, 7])); // 6,7,5  (self = this = obj)   
console.log(test.sum.bind(obj, 8, 9)());  // 8,9,5  (self = this = obj)   

apply 和 call 的區別

  • .apply.call 本質相同,他們的區別只是傳入的參數不同。

    • 傳遞參數給使用 .call 調用的函數時,需要在指定上下文(第一個參數)後一個一個地傳入(參數列表)。
    • 傳遞參數給使用 .apply 調用的函數時,可以用數組傳參而且 .apply 會在函數中自動展開(參數數組)。

call、apply、bind到底有什麼區別?bind返回的方法還能修改this指向嗎?

  1. apply與call是函數應用,指定this的同時也將方法執行,bind不同,它只是負責綁定this並返回一個新方法,不會執行。

  2. 嘗試列印返回的新函數fn2,可以看到它並不是一個普通的function,而是一個bound function,簡稱綁定函數

    • TargetFunction 指向 bind 前的函數
    • BoundThis 是綁定的 this 指向
    • BoundArgs 是傳入的其它參數了

  3. 當我們執行fn2時,有點類似於TargetFunction.apply(BoundThis,BoundArgs)。可以得出一個結論,當執行綁定函數時,this指向與形參在bind方法執行時已經確定了,無法改變。

  4. bind多次後執行,函數this還是指向第一次bind的對象。

    var o1 = { a: 1 };
    var o2 = { a: 2 };
    
    function fn(b, c) {
        console.log(this.a + b + c);
    };
    
    var fn1 = fn.bind(o1, 2, 3);
    
    //嘗試再次傳入形參
    fn1(4, 4);           //6
    
    //嘗試改變this
    fn1.call(o2);        //6
    
    //嘗試再次bind
    fn1.bind(o2, 1, 1)()   // 6
    

其實很好理解,當執行fn1時,本質上等於window.fn1(),如果this還能被改變,那this豈不是得指向window,那bind方法就沒太大意義了。