iT邦幫忙

2022 iThome 鐵人賽

DAY 9
3

前言

相信箭頭函式是很多前端開發者知道的 ES6 語法,不過有沒有想過為什麼會想用它?

而上篇介紹了 this 的指向,那箭頭函式調用 this 時的指向為何?

什麼時候又不建議使用箭頭函式?

這些問題將在這篇一一說明。


為什麼要用箭頭函式

主要有兩點:

  1. 可以簡化函式的語法
  2. 綁定 this 的方式和一般函式不同,由作用域決定 this 指向更方便

第一點還蠻好舉例的,就是箭頭函式的語法能讓函式更加精簡。

const addOne = function(num) {
  return num++;
}

// 簡化後
const addOne = (num) => num++;

第二點將會在下個段落"箭頭函式調用 this"一併解說。

箭頭函式調用 this

要在箭頭函式內知道 this 的指向,就是要看該箭頭函式被宣告時的作用域在哪裡。

所以像 foo 函式在全域被宣告,以下各種狀況 this 都會指向全域物件,都會印出 true。

const globalObject = this;
let foo = (() => this);
console.log(foo()); // window object
console.log(foo() === globalObject); // true

const obj = { foo: foo };
console.log(obj.foo() === globalObject); // true

console.log(foo.call(obj) === globalObject); // true

foo = foo.bind(obj);
console.log(foo() === globalObject); // true

這裡也拿上篇文章第 2 點的範例稍微改寫一下,此例的 showThis 函式也是屬於全域宣告,所以也印出全域物件。

const person = {
  name: "Jay",
  age: 24,
  showThis: () => {
    console.log(this);
  }
};

person.showThis(); // window object
const showThisFunc = person.showThis;
showThisFunc(); // window object

如果將 showThis 函式調整一下,讓裡面箭頭函式的作用域是 showThis 函式,執行後印出的值就會是 person 物件。

const person = {
  name: "Jay",
  age: 24,
  showThis() {
    const showThisArrFunc = () => console.log(this);
    return showThisArrFunc;
  }
};

const showThisFunc = person.showThis();
showThisFunc(); // 印出 { name: 'Jay', age: 24 }

看完以上的兩個範例,我們得知箭頭函式的 this 是由它出現地方的作用域決定。

而在過去的開發經驗中,我們看過用 const that = this;儲存 this 值 或是用 bind 等函式去綁定 this 等的情況,例如以下範例:

function usesThat(name) {
  const that = this;
  console.log(this); // usesThat 物件 {myName: 'Dave'}
  this.myName = name;

  function returnMe() {
    console.log(this); // window 物件
    return that;
  }

  return { returnMe };
}

const usesthat = new usesThat('Dave');

console.log(usesthat.returnMe().myName); // 'Dave'

這個範例中,巢狀函式 returnMe 內部的 this 指向全域,透過 that 儲存外部的 this 值,所以最後的 console.log 才能順利的印出 Dave

接著將 returnMe 改成箭頭函式,根據箭頭函式對 this 的特性,this 為 usesThat 物件,所以也能印出 Dave

function usesThat(name) {
  console.log(this); // usesThat 物件 {myName: 'Dave'}
  this.myName = name;

  const returnMe = () => {
    console.log(this); // usesThat 物件 {myName: 'Dave'}
    return this;
  }

  return { returnMe };
}

const usesthat = new usesThat('Dave');

console.log(usesthat.returnMe().myName); // 'Dave'

由上面的範例可以知道這也是使用箭頭函式的一個時機,另外上篇文章的 Callback function & this 範例也有使用到箭頭函式,有興趣的讀者也可以回去翻來看。

不過箭頭函式也是有些不建議使用的地方,接著看下個段落。


箭頭函式不適合的使用情況

使用實字物件(Object literal)的方式宣告的物件,其內部方法不建議用箭頭函式

如範例的 showNameArrowFunc 會往全域去找 dogName 屬性,當然找不到。

const myDog = {
  dogName: 'puppy',
  showNameArrowFunc: () => console.log(this.dogName),
  showName() {
    console.log(this.dogName);
  }
}

myDog.showNameArrowFunc(); // undefined
myDog.showName(); // puppy

箭頭函式不適合和 call, apply 和 bind 等函式一同使用

即使使用了 call,introArrowFunc 還是沒有改變 this 指向:

也就是 call、apply、bind 三個方法,無法"覆蓋/修改"箭頭函式中的 this 值

const myDog = {
  dogName: 'puppy'
}

const intro = function(introContent) {
  return `${introContent} ${this.dogName}`;
}

const result = intro.call(myDog, 'My dog name is');
console.log(result); // My dog name is puppy

const introArrowFunc = (introContent) => `${introContent} ${this.dogName}`;
  
const result2 = introArrowFunc.call(myDog, 'My dog name is');
console.log(result2); // My dog name is undefined

箭頭函式不能作為建構函式(constructor)使用

不會和 new 搭配使用,如下範例就出錯了。

const Foo = () => {};
const foo = new Foo(); // TypeError: Foo is not a constructor

而且箭頭函式本身也不會有 prototype,請看下面範例:

求輸出結果?

function giveLydiaPizza() {
  return "Here is pizza!"
}

const giveLydiaChocolate = () => "Here's chocolate... now go hit the gym already."

console.log(giveLydiaPizza.prototype)
console.log(giveLydiaChocolate.prototype)

答案: { constructor: ...} undefined

原因:

  1. giveLydiaPizza()有 prototype 原型屬性,為 constructor 物件
  2. 但箭頭函式如giveLydiaChocolate(),則沒有 prototype 原型屬性,因此輸出 undefined

除了上面提到的幾點外,還要注意箭頭函式沒有一般函式其參數隱藏的 arguments 物件。

總結

箭頭函式:

  1. this 指向和一般函式不同
  2. 沒有 arguments
  3. 不能作為建構函式(constructor)使用,和 new 一起用會抛出錯誤
  4. 沒有 prototype 屬性

這篇的介紹就到這邊,下篇將會介紹 call, apply 和 bind 三個函式。


練習題

求輸出結果? 答案放在留言區

const shape = {
  radius: 10,
  diameter() {
    return this.radius * 2;
  },
  perimeter: () => 2 * Math.PI * this.radius,
};

console.log(shape.diameter());
console.log(shape.perimeter());

參考資料 & 推薦閱讀

MDN Arrow function expressions

Day 06: ES6篇 - Arrow Function(箭頭函式)


上一篇
Day8-JavaScript this 關鍵字
下一篇
Day10-call()、apply() 函式介紹 & 實作
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
harry xie
iT邦研究生 1 級 ‧ 2024-02-15 14:35:07

練習題解答

輸出結果是 20 和 NaN。

因為物件 shape 是宣告在全域,所以在 shape 物件內的箭頭函式 perimeter,其 this 指向也是全域,導致要取出 radius 時是在全域下查找,找不到的結果就是計算後回傳 NaN。

我要留言

立即登入留言