1. 程式人生 > >this、call、apply、bind

this、call、apply、bind

This指標

每一個方法或函式都會有一個this物件,this物件是方法(或函式)在執行時的那個環境,也可以說是這個函式在那個作用域下執行的。說的更通俗一點:this就相當於咱們平時說話時候說的“我”,“我家”的概念。就是說當一個方法在執行的時候,它是屬於誰的。它在執行的時候它的家是誰家。

在 ES5 中,其實 this 的指向,始終堅持一個原理:this 永遠指向最後呼叫它的那個物件

例1:物件的呼叫

    var person = {
      name: 'Demi',
      describe: function () {
        return '姓名:' + this.name;
      }
    };
    console.log(person.describe()) // 姓名:Demi

上面程式碼中,this.name是在describe方法中呼叫,而describe方法所在的當前物件是person,因此this最後呼叫的那個物件就是person, this.name就是person.name

例2:物件賦值給另外一個物件呼叫

    var A = {
      name: 'Demi',
      describe: function () {
        return '姓名:' + this.name;
      }
    };
    var B = {
      name: 'dingding'
    };
    B.describe = A.describe;
    console.log(B.describe()) // 姓名:dingding

上面程式碼中,A.describe屬性被賦給B,於是B.describe就表示describe方法所在的當前物件是B,所以this.name就指向B.name

例3:函式呼叫

    var name = "windowsName";
    function a() {
      var name = "Demi";
      console.log(this.name);          // windowsName
      console.log("inner:" + this);    // inner:[object Window]
    }
    a();
    console.log("outer:" + this);      // outer:[object Window]

上面程式碼中,this.name是在a()函式中呼叫, a()前面沒有呼叫的物件,那麼this會指向頂層物件Window,相當於window.a()

例4:this 永遠指向最後呼叫它的那個物件,不會向上一個物件尋找未定義變數

    var name = "windowsName";
    var a = {
      // name: "Demi",
      fn: function () {
        console.log(this.name);      // undefined
      }
    }
    window.a.fn();

上面程式碼中,最後呼叫fn()的物件是a, 所以this指向的是a物件,也就是說 fn 的內部的 this 是物件 a,而物件 a 中並沒有對 name 進行定義,所以 log 的this.name的值是 undefined

例5:匿名函式this指向window

    var obj = {
      name: 'Demi',
      times: [1, 2, 3],
      print: function () {
        this.times.forEach(function (n) {
          console.log(this.name);
        });
      }
    };
    obj.print(); // 無輸出

上面程式碼中,obj.print內部this.times的this是指向obj的,這個沒有問題。但是,forEach方法的回撥函式內部的this.time卻是指向全域性物件,導致沒有辦法取到值。

如何改變this指向

this的動態切換,固然為 JavaScript 創造了巨大的靈活性,但也使得程式設計變得困難和模糊。有時,需要把this固定下來,避免出現意想不到的情況。改變this指向有以下幾種方法:

  • 使用ES6箭頭函式
  • 在函式內部使用_this=this 
  • JavaScript 提供了call、apply、bind這三個方法來切換this的指向
  • new例項化一個物件

箭頭函式

this物件的指向是可變的,但是在箭頭函式中,它是固定的。箭頭函式體內的this物件,就是定義時所在的物件,而不是呼叫時所在的物件。

例5:通過箭頭函式改變原來指向window的this

    let name = "windowsName";
    let a = () => {
      let name = "Demi";
      console.log(this.name);          // Demi
      console.log("inner:" + this);    // inner:[object a]
    }
    a();
    console.log("outer:" + this);      // outer:[object Window]

上面程式碼中,原來是window呼叫a()函式,this指向的是window,將函式改成箭頭函式,this指向定義時所在的物件,也就是說this.name指向a

在函式內部使用_this = this

那麼這種方式應該是最簡單的不會出錯的方式了,我們是先將呼叫這個函式的物件儲存在變數 _this中,然後在函式中都使用這個 _this,這樣 _this就不會改變了

    var obj = {
      name: 'Demi',
      times: [1, 2, 3],
      print: function () {
        var _this = this
        this.times.forEach(function (n) {
          console.log(_this.name);
        });
      }
    };
    obj.print(); // Demi Demi Demi

Function.prototype.call()

函式例項的call方法,可以指定函式內部this的指向(即函式執行時所在的作用域),然後在所指定的作用域中,呼叫該函式。

使用格式:func.apply(thisValue, arg1, arg2, ...)  call的第一個引數就是this所要指向的那個物件,後面的引數則是函式呼叫時所需的引數。this.Value為空、null、undefined,則預設傳入全域性物件。

    var obj = {
      name: 'Demi'
    }
    function a() {
      console.log(this.name);
    };
    
    a.call(obj); // Demi


    function add(a, b) {
      return a + b;
    }
    add.call(this, 1, 2) // 3

Function.prototype.apply()   

apply方法的作用與call方法類似,也是改變this指向,然後再呼叫該函式。唯一的區別就是,它接收一個數組作為函式執行時的引數,使用格式如下。

使用格式:func.apply(thisValue, [arg1, arg2, ...])  第一個引數也是this所要指向的那個物件,如果thisValue設為nullundefined,則預設傳入全域性物件。第二個引數則是一個數組,該陣列的所有成員依次作為引數,傳入原函式。原函式的引數,在call方法中必須一個個新增,但是在apply方法中,必須以陣列形式新增。

    function a(x, y) {
      console.log(x + y);
    };

    a.call(null, 1, 1); // 2
    a.call(null, [1, 1]); // 2

Function.prototype.bind()

bind()方法建立一個新的函式, 當被呼叫時,將其this關鍵字設定為提供的值,在呼叫新函式時,在任何提供之前提供一個給定的引數序列。bind 是建立一個新的函式,我們必須要手動去呼叫它才會執行。

使用格式:func.apply(thisValue, arg1, arg2, ...) () 第一個引數就是this所要指向的那個物件,後面的引數則是函式呼叫時所需的引數。this.Value為空、null、undefined,則預設傳入全域性物件。

    var a = {
      name: "Demi",
      fn: function (a, b) {
        console.log(a + b)
      }
    }
    var b = a.fn; 
    b.bind(a, 1, 2)() // 3

使用建構函式呼叫函式

如果函式呼叫前使用了 new 關鍵字, 則是呼叫了建構函式。
這看起來就像建立了新的函式,但實際上 JavaScript 函式是重新建立的物件

    // 建構函式:
    function myFunction(arg1, arg2) {
      this.firstName = arg1;
      this.lastName = arg2;
    }

    // This creates a new object
    var a = new myFunction("Ding", "Demi");
    console.log(a.lastName);  // 返回 "Demi"

new例項化過程

  1. 建立一個空物件 obj;
  2. 將新建立的空物件的隱式原型指向其建構函式的顯示原型。
  3. 使用 call 改變 this 的指向
  4. 如果無返回值或者返回一個非物件值,則將 obj 返回作為新物件;如果返回值是一個新物件的話那麼直接直接返回該物件。

所以我們可以看到,在 new 的過程中,我們是使用 call 改變了 this 的指向。

var a = new myFunction("Ding","Demi");

new myFunction{
    var obj = {};
    obj.__proto__ = myFunction.prototype;
    var result = myFunction.call(obj,"Ding","Demi");
    return typeof result === 'obj'? result : obj;
}