ES6 章節:「箭頭函式」實作中常見的錯誤
因為「箭頭函式」運作邏輯和傳統 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};
此行得知為一「判斷式」。
- function(){ return 1 } 為「函式表達式」。
- 因為 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); // 15return 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
- 把上面範例 (第 9 行) 改成「箭頭函式」,此時這段「箭頭函式」內的 this 就會使用外層作用域的 this。
- 再把 (第 5 行) 註解。
- 再把 (第 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());