Day26 | JS 箭頭函式

❒ 箭頭函式架構

  1. 可把 {} 大括號省略。
    1. 箭頭函式後的 {} 是作為箭頭函式裡面的程式碼片段的範圍使用,並不是物件實字中使用的{} 大括號。所以如果箭頭函式要回傳一個物件內容,須於 {} 外層加上 () 包覆 → ({})
  2. 如果程式碼內容為表達式 ( 會回傳值 ),沒有其他內容就可以做縮寫「 省略 {} 大括號 & return 」。
  3. 如果只有一個參數,可省略 () 括號,但如果沒有參數要保留 () 括號。
  4. 如果大括號內有多行程式,建議不要省略 {} 大括號 & return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 箭頭函式回傳裡面的物件
const ArrFn = () => ({
data:1
});
console.log(ArrFn());

// 箭頭函式未縮寫
const numB = (x) => {
return x * x;
};
console.log(numB(3));

// 箭頭函式縮寫 "return和大括號"可拿掉
const numB = (x) => x * x;
console.log(numB(3));

const numC = (x) => `數字相乘等於 ${x * x}`;
console.log(numC(3));

// 箭頭函式縮寫-沒有參數
const numD = () => `數字只能是 ${9}`
console.log(numD());

❒ 箭頭函式與傳統函式差異

  1. 箭頭函式沒有 argument 這個參數。
  2. 箭頭函式沒有自己的 this,所以它的 this 會指向它的外層。
  3. 箭頭函式無法透過 call, apply, bind 重新給予 this
  4. 箭頭函式不能當建構函式使用。

➊ 箭頭函式沒有 argument 這個參數

傳統函式在執行時會自動帶上 argument 這個參數,但箭頭函式沒有 argument 這個參數。

範例 1. 傳統函式

1
2
3
4
const nums = function() {
console.log(arguments);
}
nums(10, 50, 60, 5, 6);

截圖

範例 2. 箭頭函式

1
2
3
4
const nums = () => {
console.log(arguments);
}
nums(10, 50, 60, 5, 6);

會顯示錯誤訊息 Uncaught ReferenceError: arguments is not defined at nums

其餘參數:解決箭頭函式沒有 argument 的方式

有時候也會遇到想要取出沒列出的參數,可使用「 其餘參數 」的方式 …變數名稱

1
2
3
4
const nums = (...ary) => {
console.log(ary);
}
nums(10, 50, 60, 5, 6);

截圖

  • 使用其餘參數後,展開後可見原型 Property 一樣是陣列,可以使用所有陣列的方法,和 argument 參數有些微不同。

➋ This 綁定的差異

箭頭函式沒有自己的 this,所以它的 this 會指向它外層。

範例 1.

1
2
3
4
5
6
7
8
9
10
11
12
var myName = '全域';
var person = {
myName: '小明',
callName: function() {
console.log('1', this.myName);
setTimeout(()=> {
console.log('2', this.myName);
console.log('3', this);
},10);
},
}
person.callName();

解析:
截圖

因為箭頭函式沒有自己的 this,所以 setTimeout 裡的 this 就會指向它外層的 person 下的 myName

範例 2.

1
2
3
4
5
6
7
8
9
10
11
12
var myName = '全域';
var person = {
myName: '小明',
callName: () => {
console.log('1', this.myName);
setTimeout(()=> {
console.log('2', this.myName);
console.log('3', this.myName);
},10);
},
}
person.callName();

解析:

  • callName 調整成箭頭函式,因為箭頭函式沒有自己的 this,所以會往外找外層的 myName
  • 答案:1、2 為 全域,3 為 window。
    截圖

範例 3. this 不同,導致 DOM 的 this 也會指向不同位置

1
2
3
4
5
// 使用傳統函式
const el = document.querySelector('p');
el.addEventListener('click', function() {
console.log(this);
});
  • this 會指向 p 段落。
    截圖
1
2
3
4
5
// 使用箭頭函式
const el = document.querySelector('p');
el.addEventListener('click', () => {
console.log(this);
});
  • 箭頭函式 this 會指向 window ,因為箭頭函式沒有自己的 this 它會指向外層。
    截圖

➌ 無法透過 call, apply, bind 重新給予 this

1
2
3
4
5
6
7
8
9
10
11
12
const family = {
myName: '小明家',
}

// const fn = function (para1, para2) {
// console.log(this, para1, para2);
// }

const fn = (para1, para2) => {
console.log(this, para1, para2);
}
fn.call(family, '小明', '杰倫');
  • 傳統函式透過 call 可把另外一段函式傳入,作為它的還是函式執行的 this
  • 箭頭函式
    • familycall 的方式傳入,this 會指向全域,因為箭頭函式的 this 無法透過 call, apply, bind 重新給予。

❹ 箭頭函式不能當建構函式使用

範例 1. 查看傳統函式與箭頭函式的 prototype ( 原型 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Fn = function (a) {
this.name = a;
}

const ArrowFn = (a) => {
this.name = a;
}
console.log(Fn.prototype);
console.log(ArrowFn.prototype);

const a = new Fn('a');
console.log(a);

const b = new ArrowFn('b');
console.log(b);
  • 這兩段拿來做建構函式使用,使用建構函式時會使用 prototype 來新增一些方法,但因「 箭頭函式不具有 prototype 所以是不能拿來當建構函式使用 」。所以當 ArrowFn 要新建建構函式就會出現錯誤訊息 Uncaught TypeError: ArrowFn is not a constructor
    截圖

關於建構式可參考:鐵人賽:JavaScript 建構式

❒ 箭頭函式常見問題

常見錯誤寫法 1

1
2
3
4
const ArrFn = () => {
data: 1,
};
console.log(ArrFn());
  • 此方式不能直接回傳物件實字,會顯示錯誤訊息:Uncaught SyntaxError: Unexpected token '}’

  • 箭頭函式後的 {} 是作為箭頭函式裡面的程式碼片段的範圍使用,並不是物件實字中使用的{} 大括號。所以如果箭頭函式要回傳一個物件內容,須於 {} 外層加上 () 包覆 → ({})

    1
    2
    3
    4
    5
    // 正確寫法
    const ArrFn = () => ({
    data: 1,
    });
    console.log(ArrFn()); // 就可正確回傳裡面的物件

常見錯誤寫法 2. 判斷式後方不能接箭頭函式

1
2
3
let num = 0;
const numFn = num || () => 1;
console.log(numFn());
  • 這種判斷式後方不能使用箭頭函式,會出現錯誤訊息 Uncaught SyntaxError: Malformed arrow function parameter list

  • 正確寫法需使用傳統函式或是如上一個範例用 () 包覆。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 正確寫法 --1
    // numFn 右方的 num 是假值(上方定義 num為0所以會直接執行後方函式),回傳函式表達式中的 1
    let num = 0;
    const numFn = num || function() { return 1; }
    console.log(numFn());

    // 正確寫法 --2
    let num = 0;
    const numFn = num || (() => 1);
    console.log(numFn());

常見錯誤寫法 3

1
2
3
4
5
6
7
const person = {
myName: '小明',
callName: () => {
console.log(this.myName);
}
}
person.callName();
  • 會印出 undefined。因為箭頭函式沒有自己的 thiscallName 使用箭頭函式裡面的 this 會指向全域,全域並沒有 muName 。所以如果要取得小明callName 就要使用傳統函式。

    1
    2
    3
    4
    5
    6
    7
    const person = {
    myName: '小明',
    callName: function (){
    console.log(this.myName);
    }
    }
    person.callName();

常見錯誤寫法 4. 箭頭函式不能作建構函式使用

1
2
3
4
5
6
7
8
9
10
11
const Fn2 = function (a) {
this.name = a;
}
Fn2.prototype.protoFn = () => {
return this.name;
}
// 實體化
const newObj = new Fn2('函式');

console.log(newObj);
console.log(newObj.protoFn());
  • 第一行使用傳統函式作建構函式使用。第二行在 Fn2 函式的 prototype 下新增一個原型方法 protoFn ,這個原型方法使用建構函式來建立。

  • console.log(newObj); 可正確查到 Fn2 原型 (prototype) 下的 protoFn 方法。但實際在執行時 console.log(newObj.protoFn()); 所得到的值會是空的。
    截圖

  • 為什麼無法得到 newObj 下面的 name,而是取到空值?

    • 主要是箭頭函式指向和傳統函式不同,所以 Fn2.prototype.protoFn 裡的 this 指向是全域 window 而非第一行 Fn2

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const Fn2 = function (a) {
      this.name = a;
      }
      // 調整程式碼看看裡面的this
      Fn2.prototype.protoFn = () => {
      return this;
      }
      // 實體化
      const newObj = new Fn2('函式');
      console.log(newObj.protoFn()); // window
      ![截圖](https://ithelp.ithome.com.tw/upload/images/20221011/20119743agmUs6yGMv.png)
    • 正確寫法,把 Fn2.prototype.protoFn 使用傳統函式來建構,可見 this 指向為第一行的 Fn2

      1
      2
      3
      4
      5
      6
      7
      8
      9
      const Fn2 = function (a) {
      this.name = a;
      }
      Fn2.prototype.protoFn = function() {
      return this;
      }
      // 實體化
      const newObj = new Fn2('函式');
      console.log(newObj.protoFn());
      ![截圖](https://ithelp.ithome.com.tw/upload/images/20221011/20119743hoJB77fPfs.png)

箭頭函式實戰用法

實戰 1. 搭配陣列方法

1
2
3
4
// 陣列雙倍
const arr = [15, 12, 63, 67, 1421, 124, 56];
const arrDouble = arr.map((num) => num * 2);
console.log(arrDouble);

map 很適合用在陣列裡所有的值都需要調整的時候,因為 map 會透過函式所回傳的值組成一個新的陣列。

實戰 2. 平均值 加總

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 傳統函式寫法
const average = function () {
const nums = Array.from(arguments);

// 使用 reduce 算1~5總和
const total = nums.reduce(function (acc, curr) {
return acc + curr
}, 0);
console.log(total);

// 算平均值
return total / nums.length;

}
console.log(average(1, 2, 3, 4, 5, 10));
  • average() 此函式會將傳入的參數 ( average(1, 2, 3, 4, 5) ) 全部取出,我們使用 arguments 這個參數。由於 arguments 為類陣列沒有所有陣列的方法,所以 Array.from 使用將它轉為純陣列。

箭頭函式寫法

1
2
const average = (...num) => num.reduce((acc, curr) => acc + curr, 0) / num.length;
console.log(average(1, 2, 3, 4, 5, 10));
  • 因為箭頭函式沒有 argument 的方式,所以可使用其餘參數來解決無法取出沒有列出的參數問題。

實戰 3. 物件內 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 傳統函式
const person = {
data: {},
// getData 方法可取得遠端資料
getData: function() {
// 將this指向新變數vm
const vm = this;
// 透過ajax取的遠端資料
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function (data) {
console.log(data);
// vm.data指的是person.data
vm.data = data.results[0];
console.log('person.data', person.data);
}
});
}
}
person.getData();
  • 記得先載入 jQuery CDN 才可運作 https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js

調整成箭頭函式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 箭頭函式寫法
const person = {
data: {},
getData: function() {
// 透過ajax取的遠端資料
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: (data) => {
console.log(data);
// vm.data指的是person.data
this.data = data.results[0];
console.log('person.data', person.data);
}
});
}
}
person.getData();
  • 箭頭函式沒有自己的 this,所以把 success 後方調整成箭頭函式後,裡面的 this 會指向外層作用域 getData,而基於 getData 是由 person 呼叫所以 this 會指向 personsuccess 內原本的 vm 可修改成 thisconst vm = this; 可刪除。

參考資訊