this 到底怎麼一回事

在學習 JS,往往都會對 this 的值很模糊,也讓人困擾!以為認為 this 是指向某個對象,但往往不是你想那樣。正確來說,誰調用函數,那麼this 就指向調用者。但是要特別注意如果使用嚴謹模式 this 會轉變成 undefined

function fn() {
  'use strict'
  console.log(this) // undefined
} 

this 的值取決於函數呼叫方式。

function Cat(name) {
 this.name = name;
 this.sayName = function () {
   console.log(`Meow! My name is ${this.name}`);
 };
}
const bailey = new Cat('Bailey');

什麼時候 this 被賦值

  • 很多人誤解,以為 this 是指向對象。
  • 正確來說:當前函數的 this 是在函數被調用執行的時候才確定。
const dog = {
  bark: function () {
    console.log('Woof!');
  },
  barkTwice: function () {
    this.bark();
    this.bark();
  }
};

this 的值實際上沒有分配給任何東西,直到 object 呼叫方法。
this 的值是根據 object 調用方法時,this 被確定了。

What Does this Get Set To?

當函數被調用時,變數this被設成特定的值。根據函數的調用方式,this 指向對象會截然不同。

有四種方式去 call function,在每個設定 this 方式不同

  1. 使用關鍵字 new 來調用構造函數將會設定 this 指向新的 object
  2. object 調用方法 (method) this 指向 obj 本身。
    1. 例子:dog.barkTwice() 會訪問 dog 本身
  3. 呼叫原生函數,this 會指向 window (browser 環境)
  4. 呼叫原生函數允許我們去設定 this 指向

總結

函數,對象和 this 是有互相關聯的。當我們使用 new 來調用構造函數,變數 this 會被設為新的 object。當我們在 object 調用方法 (method) ,this 會被設為 object 本身。當我們調用原生函數 (在 browser 環境) this 會被設為 window

設定this的指向

如果我們想要設成自己 this 的值,JavaScript 有提供幾種來設定/改變 this 的值的方法(call(), apply(), bind())來實現。

  • call(), apply() 會根據參數形式。
  • bind() 是返回新的函數。

call()

call() 是一個直接調用在函數上的方法
function.call(函數內部指定 this 的值, 函數的參數)

以下面例子函數裡 this 是指向 window,3和4是函數的參數。

function multiply(n1, n2) {
  return n1 * n2;
}

multiply(3, 4); // 12

multiply.call(window, 3, 4); // 12

以下面例子,在 object 的方法(method) 使用 call()

  • 使用 call() 來向 object 借方法來執行
  • 當我執行 mockingbird.describe 方法時,裡面 this 是指向 object(mockingbird) 本身。
  • 執行 mockingbird.describe.call(pride); 第一個參數是來指定 this 的值,所以 this 是指向 pride 對象
const mockingbird = {
  title: 'To Kill a Mockingbird',
  describe: function () {
    console.log(`${this.title} is a classic novel`);
  }
};
mockingbird.describe();  // 'To Kill a Mockingbird is a classic novel'

const pride = {
  title: 'Pride and Prejudice'
};
mockingbird.describe.call(pride); // 'Pride and Prejudice is a classic novel'

apply()

apply() 是一個直接調用在函數上的方法
call()apply() 很相似,apply() 差別在於函數的參數是陣列形式
function.apply(函數內部指定this的值, [函數的參數])

以下面例子函數裡 this 是指向 window,3和4是函數的參數。

function multiply(n1, n2) {
  return n1 * n2;
}
multiply.apply(window, [3, 4]);  // 12

以下面例子使用 apply 來調用 object 方法(method)

  • apply()call() 使用方式是一樣。
  • 結果也和 call() 是一樣
const mockingbird = {
  title: 'To Kill a Mockingbird',
  describe: function () {
    console.log(`${this.title} is a classic novel`);
  }
};
mockingbird.describe();  // 'To Kill a Mockingbird is a classic novel'

const pride = {
  title: 'Pride and Prejudice'
};
mockingbird.describe.apply(pride); // 'Pride and Prejudice is a classic novel'

選擇哪個方法呢?

call() 可能有限,如果你不知道 function 需要多少參數,可以使用apply() 來代替是最佳選擇!

callback and this

function invokeTwice(cb) {
   cb();
   cb();
}


const dog = {
  age: 5,
  growOneYear: function () {
    this.age += 1;
  }
};


const cat = {
  age: 5,
  growOneYear: function () {
    this.age += 1;
  }
};


dog.growOneYear();
dog.age;  // 6


invokeTwice(dog.growOneYear);
dog.age;  // 6

為什麼 age 還是保留 6。因為 invokeTwice 調用 dog.growOneYear ,但是是當作 function 來調用而不是 method,所以 this 是指向 global object 而不是 dog

bind()

bind 可以用來指定 this,會 return 新的函數

  • bind() 是一個直接調用在函數上的方法
  • 返回該函數副本,具有特定 this
  • bind 中傳入一個參數是
  • bind 函數被調用時作為目標函數的 this 參數
// 根據上面程式例子
const grow = dog.growOneYear.bind(dog)
invokeTwice(grow)
dog.age // 8


// dog.growOneYear 是目標函數,
// 我們不希望this是指向 global object,
// 但我希望 this 是指向 dog


const grow = dog.growOneYear.bind(cat)
invokeTwice(grow) // cat.age = 7


// dog.growOneYear 裡面的 this 綁定到 cat (指定object),
// 會複製一份函數存在 grow 變數。
// 呼叫 grow() cat 的 age 會是8
// 理解成 cat.growOneYear()

剪頭函數?

關於剪頭函數有沒有 this ?

An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors.

剪頭函數本身是沒 this !

總結

總結來說,this 的值是誰調用函數 this 指向誰 (this 指向是函數調用者)。但我們可以強制改變 this 的指向,可以使用 (call, apply, bind)

參考