Blog icon indicating copy to clipboard operation
Blog copied to clipboard

[MDN] 箭頭函式運算式

Open ChaoLiou opened this issue 5 years ago • 0 comments

Arrow Function Expressions

  • 箭頭函式運算式 比傳統的 函式運算式 寫法更簡潔, 但是他有限制, 無法運用在所有情況.

  • 不同之處 & 限制:

    • 沒有自己的 thissuper, 所以不應該用於 methods.
    • 沒有 argumentsnew.target 的關鍵字.
    • 不適合使用 call, applybind 的 methods, 因為這些方法通常依賴於所建立的 scope.
    • 無法用於 contructors
    • 主體內無法使用 yield

比較傳統函式與箭頭函式

// 傳統函式
function (a) {
  return a + 100;
}

// 拆解成箭頭函式

// 1. 移除單字 "function" 並在參數和函式主體的開頭大括號之間放置箭頭
(a) => {
  return a + 100;
}

// 2. 移除函式主體的大括號, 以及 "return".
(a) => a + 100;

// 3. 移除參數的小括號
a => a + 100;

大括號, 小括號 和 "return" 有時是選填, 有時是必填.

  • 舉例, 如果有 多個參數沒有參數, 需要在參數周圍重新引進小括號.
// 傳統函式(沒有參數)
let a = 4;
let b = 2;
function () {
  return a + b + 100;
}

// 箭頭函式(沒有參數)
let a = 4;
let b = 2;
() => a + b + 100;
  • 如果函式主體需要 額外幾行 的處理過程, 需要重新引進大括號 外加 "return" (箭頭函式不會去猜你要 "回傳" 甚麼, 或何時要 "回傳"):
// 傳統函式
function (a, b) {
  let chuck = 42;
  return a + b + chuck;
}

// 箭頭函式
(a, b) => {
  let chuck = 42;
  return a + b + chuck;
}
  • 最後, 將 具名函式 的箭頭運算式視為變數
// 傳統函式
function bob(a) {
  return a + 100;
}

// 箭頭函式
let bob = (a) => a + 100;

語法

基本語法

  • 只有一個參數, 不需要 "return":
param => expression
  • 多個參數, 不需要 "return":
(param1, paramN) => expression
  • 多行 statements, 需要主體大括號, 以及 "return":
param => {
  let a = 1;
  return a + param;
};
  • 多個參數. 多行 statements 需要主體大括號, 以及 "return":
(param1, paramN) => {
   let a = 1;
   return a + param1 + paramN;
}

進階語法

  • 回傳一個 object literal 運算式需要在周圍加上小括號:
params => ({foo: "a"}) // 回傳物件 {foo: "a"}
  • 支援 rest 參數:
(a, b, ...r) => expression
  • 支援預設參數:
(a=400, b=20, c) => expression
  • 支援參數間的 destructing:
([a, b] = [10, 20]) => a + b;  // 結果是 30
({ a, b } = { a: 10, b: 20 }) => a + b; // 結果是 30

說明

用於 methods

"use strict";

var obj = {
  // 不會建立一個新的 scope
  i: 10,
  b: () => console.log(this.i, this),
  c: function () {
    console.log(this.i, this);
  },
};

obj.b(); // undefined, Window {...} (或是全域物件)
obj.c(); // 10, Object {...}
  • 箭頭函式不會有自己的 this.
"use strict";

var obj = {
  a: 10,
};

Object.defineProperty(obj, "b", {
  get: () => {
    console.log(this.a, typeof this.a, this);
    // undefined 'undefined' Window {...} (或是全域物件)
    return this.a + 10;
    // this 代表全域物件 'Window', 所以 'this.a' 回傳 'undefined'
  },
});

call, apply 和 bind

  • call, applybind 方法 不適合 使用 - 因為 箭頭函式所建立的 "this" 是根據所定義的 scope 而定.
// ----------------------
// 傳統範例
// ----------------------
// 一個單純的物件, 有自己的 "this".
var obj = {
  num: 100,
};

// 在 window 上設定 "num"
window.num = 2020;

// 一個單純的傳統函式運用了 "this"
var add = function (a, b, c) {
  return this.num + a + b + c;
};

// call
var result = add.call(obj, 1, 2, 3); // 建立了 "obj" scope
console.log(result); // 106

// apply
const arr = [1, 2, 3];
var result = add.apply(obj, arr); // 建立了 "obj" scope
console.log(result); // 106

// bind
var result = add.bind(obj); // 建立了 "obj" scope
console.log(result(1, 2, 3)); // 106
// ----------------------
// 箭頭範例
// ----------------------

// 一個單純的物件, 有自己的 "this".
var obj = {
  num: 100,
};

// 在 window 上設定 "num"
window.num = 2020;

// 箭頭函式
var add = (a, b, c) => this.num + a + b + c;

// call
console.log(add.call(obj, 1, 2, 3)); // 結果是 2026

// apply
const arr = [1, 2, 3];
console.log(add.apply(obj, arr)); // 結果是 2026

// bind
const bound = add.bind(obj);
console.log(bound(1, 2, 3)); // 結果是 2026

傳統範例:

var obj = {
  count: 10,
  doSomethingLater: function () {
    setTimeout(function () {
      // 在 window scope 執行函式
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // "NaN", 因為 "count" 不在 window scope.

箭頭範例:

var obj = {
  count: 10,
  doSomethingLater: function () {
    setTimeout(() => {
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater();

沒有 arguments

  • 不會有自己的 arguments 物件. 因此, arguments 是一個參考, 指向 enclosing scope arguments
var arguments = [1, 2, 3];
var arr = () => arguments[0];

arr(); // 1

function foo(n) {
  var f = () => arguments[0] + n; // arguments[0] is n
  return f();
}

foo(3); // 3 + 3 = 6
function foo(n) {
  var f = (...args) => args[0] + n;
  return f(10);
}

foo(1); // 11

new 運算子

  • 無法用於建構子, 使用 new 會拋出錯誤.
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo 不是一個建構子

prototype 屬性

  • 不會有 prototype 屬性.
var Foo = () => {};
console.log(Foo.prototype); // undefined

yield 關鍵字

  • 在主體中無法使用 yield 關鍵字. 因此無法用於 generators.

函式主體

  • 可以是 "簡潔主體" 或 "區塊主體".
    • 簡潔主體, 只會有一個運算式, 並運算成回傳值.
    • 區塊主體, 必須表明 return.
var func = (x) => x * x;

var func = (x, y) => {
  return x + y;
};

回傳 object literal

var func = () => { foo: 1 };
// 呼叫 func() 回傳 undefined!

var func = () => { foo: function() {} };
// SyntaxError: 函式敘述句需要一個名稱
  • 因為在大括號裡的程式碼會被解析為一連串的 statements, 必須用小括號包住:
var func = () => ({ foo: 1 });

換行

  • 無法在參數和箭頭間換行.
var func = (a, b, c)
  => 1;
// SyntaxError: 預期是運算式, 但得到 '=>'
  • 不過可以經過修正, 透過在箭頭後面換行, 或使用小/大括號, 確保程式碼保持整齊. 也可以在參數間換行.
var func = (a, b, c) =>
  1;

var func = (a, b, c) => (
  1
);

var func = (a, b, c) => {
  return 1
};

var func = (
  a,
  b,
  c
) => 1;

// 沒有拋出 SyntaxError

解析順序

  • 雖然箭頭(=>)不是一個運算子, 但他有特殊的解析規則, 與常規函式的 operator precedence 不同.
let callback;

callback = callback || function() {}; // ok

callback = callback || () => {};
// SyntaxError: 不合法的箭頭函式參數

callback = callback || (() => {});    // ok

範例

基本用途

let empty = () => {};
// undefined

(() => "foobar")();
// "foobar"
// (這是一個立即呼叫的函式運算式)

var simple = (a) => (a > 15 ? 15 : a);
simple(16); // 15
simple(10); // 10

let max = (a, b) => (a > b ? a : b);

// 陣列篩選和映射 ...

var arr = [5, 6, 13, 0, 1, 18, 23];

var sum = arr.reduce((a, b) => a + b);
// 66

var even = arr.filter((v) => v % 2 == 0);
// [6, 0, 18]

var double = arr.map((v) => v * 2);
// [10, 12, 26, 0, 2, 36, 46]

// promise chains
promise
  .then((a) => {
    // ...
  })
  .then((b) => {
    // ...
  });

// 無參數
setTimeout(() => {
  console.log("我不久後發生");
  setTimeout(() => {
    // 更深的程式
    console.log("我之後發生");
  }, 1);
}, 1);

瀏覽器相容性

  • 只有 IE 不支援, 其他瀏覽器都完全支援

ChaoLiou avatar Jan 27 '21 05:01 ChaoLiou