iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0
自我挑戰組

那些 Notion 中的前端筆記系列 第 26

Day26 | JS 箭頭函式

  • 分享至 

  • xImage
  •  

❒ 箭頭函式架構

  1. 可把 {} 大括號省略。
    1. 箭頭函式後的 {} 是作為箭頭函式裡面的程式碼片段的範圍使用,並不是物件實字中使用的{} 大括號。所以如果箭頭函式要回傳一個物件內容,須於 {} 外層加上 () 包覆 → ({})
  2. 如果程式碼內容為表達式 ( 會回傳值 ),沒有其他內容就可以做縮寫「 省略 {} 大括號 & return 」。
  3. 如果只有一個參數,可省略 () 括號,但如果沒有參數要保留 () 括號。
  4. 如果大括號內有多行程式,建議不要省略 {} 大括號 & return
// 箭頭函式回傳裡面的物件
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. 傳統函式

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

https://ithelp.ithome.com.tw/upload/images/20221011/20119743EgEhUmstMe.png

範例 2. 箭頭函式

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

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

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

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

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

https://ithelp.ithome.com.tw/upload/images/20221011/20119743woYrjNdE78.png

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

➋ This 綁定的差異

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

範例 1.

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.

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 也會指向不同位置

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

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

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 ( 原型 )

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

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

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

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

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

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

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

    // 正確寫法 --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

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

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

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

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

      const Fn2 = function (a) {
        this.name = a;
      }
      // 調整程式碼看看裡面的this
      Fn2.prototype.protoFn = () => {
        return this;
      }
      // 實體化
      const newObj = new Fn2('函式');
      console.log(newObj.protoFn()); // window
      

      截圖

    • 正確寫法,把 Fn2.prototype.protoFn 使用傳統函式來建構,可見 this 指向為第一行的 Fn2

      const Fn2 = function (a) {
        this.name = a;
      }
      Fn2.prototype.protoFn = function() {
        return this;
      }
      // 實體化
      const newObj = new Fn2('函式');
      console.log(newObj.protoFn());
      

      截圖

箭頭函式實戰用法

實戰 1. 搭配陣列方法

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

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

實戰 2. 平均值 加總

// 傳統函式寫法
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 使用將它轉為純陣列。

箭頭函式寫法

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

實戰 3. 物件內 this

// 傳統函式
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

調整成箭頭函式

// 箭頭函式寫法
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; 可刪除。

參考資訊


上一篇
Day25 | JS 函式 This 的運作
下一篇
Day27 | JS DOM
系列文
那些 Notion 中的前端筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言