ES6 章節:「箭頭函式」實作中常見的錯誤

Vicky
宅宅薇琪 [前端學習筆記]
14 min readFeb 26, 2021

因為「箭頭函式」運作邏輯和傳統 JS 觀念上會有不同,在撰寫上也容易有很多大雷點容易踩坑。我們來看看實作中使用「箭頭函式」會遇到什麼樣的問題,又該如何解決。

「箭頭函式」不能直接回傳物件實字 (要加上小括號)

回想一下前一章有介紹「箭頭函式」的觀念,「箭頭函式」若不加大括號和return,則可以直接回傳後面的值。

const arrFn = () => 1;
console.log(arrFn()) // 1

假設預期要回傳一個物件 {data: 1},為什麼卻回傳了 undefined ?

const arrFn = () => {data: 1};  // 預期要回傳此物件 {data: 1}
console.log(arrFn()) // undefined

整理一下程式碼發現 :
{data: 1} 其中的大括號其實是做為「箭頭函式」裡的程式碼片段範圍使用,並不是物件實字裡所使用的大括號。

const arrFn = () => {  // 此大括號為「箭頭函式」裡的程式碼片段範圍使用
data: 1 // 無法回傳
};
console.log(arrFn()) // undefined

若在實作中,想要回傳物件內容該如何做調整 ?
物件外層再加一對小括號,就能正確回傳物件內容。

const arrFn = ()=> ({data: 1});  
// 在 {data: 1} 兩邊加上小括號,即可回傳物件內容
console.log(arrFn()); // {data: 1}

「判斷式」後面不能搭配「箭頭函式」使用,結構上會有錯誤 (SyntaxError)

修正錯誤方法 : 加上小括號

「傳統函式」寫法

num || function(){ return 1}; 此行得知為一「判斷式」。

  1. function(){ return 1 } 為「函式表達式」。
  2. 因為 num 為 0 是”假值”,所以會把「函式表達式」直接賦予到變數 numFn 上。
let num = 0;  // 先宣告一個數字
const numFn = num || function(){ return 1 };
console.log(numFn()); // 1

若改用「箭頭函式」寫法,結構上會有錯誤,錯誤碼 (SyntaxError)。

let num = 0;
const numFn = num || () => 1;
console.log(numFn()); // 跳錯 (SyntaxError)

修正以上問題,要加上小括號。

let num = 0;
const numFn = num || (() => 1);
console.log(numFn()); // 1

若物件內的方法必須取用 this 時,注意「箭頭函式」的 this 可能無法取到預期中的物件。

取用物件內的方法(其中方法為「箭頭函式」),this 為指向外層作用域的 this。

定義一個物件,裡面有一個 callName 方法(使用「箭頭函式」)。

使用「箭頭函式」要注意 this 的指向。

const person = {
myName: '小明',
callName: () => { // 「箭頭函式」沒有自己的 this,這個 this 指向全域
console.log(this.myName); // undefined
},
}
person.callName(); // this 指向全域,無法正確取到 person 物件內的 myName

解決方法 : 使用「傳統函式」

const person = {
myName: '小明',
callName: function () {
console.log(this.myName); // 小明
},
}
person.callName();

物件裡若要搭配「箭頭函式」,注意指向是不同的,也會影響運行結果。

Vue 的生命週期 created :
created 是當我們的 Vue 執行到這個元件時,在一開始就優先執行這段程式碼。

使用「傳統函式」可以正確在 Vue 的元件裡取得自己的資料內容。

const app = new Vue({      // 將 Vue 實體建立出來
data: {
num: 1
},

created: function(){ // 傳統函式
console.log(this.num); // 1 (可以取到自己的 data)
}
})

若把 created 改成「箭頭函式」就會出錯

const app = new Vue({  // 將 Vue 實體建立出來
data: {
num: 1
},

created: () => {
console.log(this.num); // undefined
}
})

ps : 此段範例需載入 vue 的 CDN 才能實作

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>

「箭頭函式」無法作為「建構函式」使用

下面一段程式碼,使用「傳統函式」做為「建構函式」使用。

const Fn2 = function (a) {   
this.name = a;
}

在 Fn2「建構函式」的原型下,使用「箭頭函式」來新增原型方法。
此段程式碼裡的箭頭函式 this 指向不同,指向全域。無法正確的指到 Fn2「建構函式」物件本身。

Fn2.prototype.protoFn = () => {  
return this.name;
}

使用 new 把 Fn2「建構函式」實體化

const newObj = new Fn2('函式');  // 實體化
console.log(newObj);

可以在 newObj 的物件實體下找到新增的原型方法。

但是可能在執行時會出錯,找不到資料。 無法得到 newObj 物件裡的 name。

console.log(newObj.protoFn());   // 得到空值

原因是「箭頭函式」this 指向和「傳統函式」不一樣。
this 其實是指向「全域」,無法正確的指到 Fn2「建構函式」物件本身。

箭頭函式實戰用法

✏️ Array.prototype.map()

使用 array.map 方法會把陣列裡的所有值一一取出,把陣列裡面的值重新運算後,透過回傳的方式將這些值套用在新的陣列上。

  • array.map 方法適合用在陣列裡所有的值都需要調整時。
  • 使用方式 : array.map(call back 函式)。

使用「傳統函式」寫法

const arr = [15, 12, 63, 67, 1421, 124, 567, 235, 12, 45];
const arrDouble = arr.map(function(num) {
// num 為陣列裡的所有值,運算過後,產生新陣列
return num * 2;
// 把陣列裡的數值全部 * 2 再回傳出來。return 這段就會將值帶到新陣列裡。
})
console.log(arrDouble);
// (10)[30, 24, 126, 134, 2842, 248, 1134, 470, 24, 90]

使用「箭頭函式」寫法

const arr = [15, 12, 63, 67, 1421, 124, 567, 235, 12, 45];  
const arrDouble = arr.map(num => num * 2);
// 此時只有一個參數 num,可以省略小括號
console.log(arrDouble);
// (10)[30, 24, 126, 134, 2842, 248, 1134, 470, 24, 90]

✏️ Array.prototype.reduce()

Array.reduce 方法 : 透過迴圈的方式將陣列裡所有的值一一帶入。可以與 “前一個回傳的值” 再次作運算。

參數如下 :

  • acc: 前一個參數,如果是第一個陣列的話,值是以另外傳入或初始化的值。(以此範例,此 acc 為第一次執行的前一個值 0 )
  • val 就是 “當前的值”。(以此範例,此 val 即參數的 “第一個值” 為 1 )

範例 : 比較哪個數字大 (使用「傳統函式」寫法)

const average = function (...nums) {
console.log(nums); // [1, 2, 3, 4, 5]
return nums.reduce(function (acc, val) {
return Math.max(acc , val);
}, 0);
}
console.log(average(1, 2, 3, 4, 115)); // 115

範例 : 傳入的所有參數會產生一個平均值 (使用「傳統函式」寫法)

先使用 arguments 把所有的參數取出。因為 arguments 是「類陣列」,沒有陣列的使用方法,使用 Array.from 的方式將他轉為「純陣列」。並且存入至新變數 nums 上。

const average = function () {
const nums = Array.from(arguments);
// 使用 Array.from 的方式將他轉為「純陣列」
const total = nums.reduce(function (acc, val) {
return acc + val
}, 0);
console.log(total); // 15
return total / nums.length;
}
console.log(average(1, 2, 3, 4, 5)); // 3

使用「箭頭函式」寫法

const average = (...num) => num.reduce((acc, val) => acc + val, 0) / num.length;
console.log(average(1, 2, 3, 4, 5)); // 3

✏️ 撈取遠端的 AJAX 資料

使用 jQuery 套件 (CDN 如下)

<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

使用「傳統函式」寫法

getData 使用「傳統函式」,可以確保存取到 person 物件下的內容。

const person = {
data: { // 有一個 data 的物件資料
},
// 有一個 getData 的方法,會取得遠端資料並更新 data 的內容
getData: function() {
// 直接將 this 指向一個新的變數 vm (為了可以正確指向 person 內的 data)
const vm = this;
$.ajax({ // 透過 ajax 取得遠端資料
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function(data){
// 取得遠端資料後,會將資料存取回 person 物件裡的 data
console.log(data);
vm.data = data.results[0]; // vm.data 指的是 person.data
console.log('person.data', person.data);
}
});
}
}
person.getData();

使用「箭頭函式」寫法 : 「箭頭函式」沒有自己的 this

  1. 把上面範例 (第 9 行) 改成「箭頭函式」,此時這段「箭頭函式」內的 this 就會使用外層作用域的 this。
  2. 再把 (第 5 行) 註解。
  3. 再把 (第 11 行) 中的 vm 改成 this。

修改後程式碼如下,結果與上例同。

const person = {
data: {
},
getData: function() {
// const vm = this;
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: (data) => {
console.log(data);
this.data = data.results[0]; // vm.data 指的是 person.data
console.log('person.data', person.data);
}
});
}
}
person.getData();

「箭頭函式」練習

第一題

var person = {
// 提款記錄
withdrawalRecord: [12, 45, 560, 120],
// 計算扣除的金額
calculate: () => {
var total = this.total; //箭頭函式 this 指向不同,這裡指向全域 window
var sum = this.withdrawalRecord.reduce(function(acc, val) {
return acc + val
}, 0);
total = total - sum;
this.total = total; //箭頭函式 this 指向不同,這裡指向全域 window
},
// 剩餘的金額
total: 1000
}
person.calculate();
console.log(person.total);
// 問題:
// 1. 哪邊出錯了,請嘗試說明
// 2. 修正錯誤,並嘗試縮寫以上程式碼

使用「箭頭函式」精簡程式碼
將上面程式碼 (第 5 行) 先改成「傳統函式」
將上面程式碼 (第 7 行) 改成「箭頭函式」

var person = {
withdrawalRecord: [12, 45, 560, 120], // 提款記錄
// 計算扣除的金額
calculate: function(){
var total = this.total;
var sum = this.withdrawalRecord.reduce((acc, val) => acc + val, 0);
total = total - sum;
this.total = total;
},

total: 1000 // 剩餘的金額
}
person.calculate();
console.log(person.total); // 263

第二題

Array.prototype.sumData = () => {
return this.reduce(function (acc, val) {
// 箭頭函式 this 指向不同,指向全域
return acc + val
}, 0);
}
var arr = [1, 23, 5, 8, 52, 53, 63];
console.log(arr.sumData());
// 問題:
// 1. 哪邊出錯了,請嘗試說明
// 2. 修正錯誤,並嘗試縮寫以上程式碼

改成「箭頭函式」方式一

Array.prototype.sumData = function(){
return this.reduce((acc, val) => acc + val, 0);
}
var arr = [1, 23, 5, 8, 52, 53, 63];
console.log(arr.sumData()); // 205

改成「箭頭函式」方式二

Array.prototype.sumData = () => arr.reduce((acc, val) => acc + val, 0);

var arr = [1, 23, 5, 8, 52, 53, 63];
console.log(arr.sumData());

--

--