'ฟังก์ชั่นลูกศร' และ 'ฟังก์ชั่น' เทียบเท่าหรือแลกเปลี่ยนได้?


520

ฟังก์ชั่นลูกศรใน ES2015 มีไวยากรณ์ที่กระชับยิ่งขึ้น

  • ฉันสามารถแทนที่การประกาศ / การแสดงออกของฟังก์ชั่นทั้งหมดด้วยฟังก์ชั่นลูกศรได้หรือไม่?
  • ฉันต้องระวังอะไร

ตัวอย่าง:

ฟังก์ชันตัวสร้าง

function User(name) {
  this.name = name;
}

// vs

const User = name => {
  this.name = name;
};

วิธีการต้นแบบ

User.prototype.getName = function() {
  return this.name;
};

// vs

User.prototype.getName = () => this.name;

วิธีการวัตถุ (ตัวอักษร)

const obj = {
  getName: function() {
    // ...
  }
};

// vs

const obj = {
  getName: () => {
    // ...
  }
};

เรียกกลับ

setTimeout(function() {
  // ...
}, 500);

// vs

setTimeout(() => {
  // ...
}, 500);

ฟังก์ชัน Variadic

function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// vs
const sum = (...args) => {
  // ...
};

5
คำถามที่คล้ายกันเกี่ยวกับฟังก์ชั่นลูกศรมีมากขึ้นเรื่อย ๆ เมื่อ ES2015 ได้รับความนิยมมากขึ้น ฉันไม่รู้สึกว่ามีคำถาม / คำตอบที่ดีสำหรับเรื่องนี้ดังนั้นฉันจึงสร้างคำถามนี้ขึ้นมา หากคุณคิดว่ามีสิ่งที่ดีอยู่แล้วโปรดแจ้งให้เราทราบและฉันจะปิดอันนี้เป็นสิ่งที่ซ้ำหรือลบ อย่าลังเลที่จะปรับปรุงตัวอย่างหรือเพิ่มใหม่
เฟลิกซ์คลิง

2
สิ่งที่เกี่ยวกับJavaScript ecma6 เปลี่ยนฟังก์ชั่นปกติเป็นฟังก์ชั่นลูกศร ? แน่นอนว่าคำถามปกติจะไม่สามารถเป็นคำถามที่ดีและเป็นคำถามทั่วไปได้
Bergi

ดูตัวอย่าง Plnkr นี้ตัวแปรthisจะtimesCalledเพิ่มขึ้นทีละมากต่างกันเพียง 1 ในแต่ละครั้งที่เรียกปุ่มนั้น คำตอบใดที่ตอบคำถามส่วนตัวของฉัน: .click( () => { } )และ.click(function() { }) ทั้งคู่ก็สร้างฟังก์ชั่นจำนวนเท่ากันเมื่อใช้ในลูปที่คุณสามารถดูได้จากการนับ Guid ใน Plnkr
abbaf33f

คำตอบ:


750

tl; dr: ไม่! ฟังก์ชั่นลูกศรและการประกาศฟังก์ชั่น / การแสดงออกไม่เทียบเท่าและไม่สามารถแทนที่สุ่มสี่สุ่มห้าได้
หากฟังก์ชั่นที่คุณต้องการที่จะเปลี่ยนไม่ได้ใช้this, argumentsและไม่ได้เรียกว่ามีnewแล้วใช่


จึงมักจะเป็น: มันขึ้นอยู่กับ ฟังก์ชั่นลูกศรมีพฤติกรรมที่แตกต่างจากฟังก์ชั่นการประกาศ / การแสดงออกดังนั้นเรามาดูความแตกต่างก่อน:

1. คำศัพท์thisและarguments

ฟังก์ชั่นลูกศรไม่มีของตัวเองthisหรือargumentsมีผลผูกพัน แต่ตัวระบุเหล่านั้นจะได้รับการแก้ไขในขอบเขตศัพท์เช่นตัวแปรอื่น ๆ ซึ่งหมายความว่าภายในฟังก์ชั่นลูกศรthisและargumentsอ้างถึงค่าของthisและargumentsในสภาพแวดล้อมฟังก์ชั่นลูกศรถูกกำหนดใน (เช่น "นอก" ฟังก์ชั่นลูกศร):

// Example using a function expression
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: function() {
      console.log('Inside `bar`:', this.foo);
    },
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

// Example using a arrow function
function createObject() {
  console.log('Inside `createObject`:', this.foo);
  return {
    foo: 42,
    bar: () => console.log('Inside `bar`:', this.foo),
  };
}

createObject.call({foo: 21}).bar(); // override `this` inside createObject

ในกรณีที่การแสดงออกของฟังก์ชั่นที่หมายถึงวัตถุที่ถูกสร้างขึ้นภายในthis createObjectฟังก์ชั่นในกรณีที่ลูกศรthisหมายถึงthisของcreateObjectตัวเอง

สิ่งนี้ทำให้ฟังก์ชั่นลูกศรมีประโยชน์หากคุณต้องการเข้าถึงthisสภาพแวดล้อมปัจจุบัน:

// currently common pattern
var that = this;
getData(function(data) {
  that.data = data;
});

// better alternative with arrow functions
getData(data => {
  this.data = data;
});

หมายเหตุว่าเรื่องนี้ก็หมายความว่าเป็นไม่ได้ที่เป็นไปได้ในการตั้งค่าฟังก์ชั่นของลูกศรthisด้วยหรือ.bind.call

หากคุณไม่คุ้นเคยthisให้ลองอ่าน

2. ฟังก์ชั่นลูกศรไม่สามารถเรียกได้ด้วย new

ES2015 แยกความแตกต่างระหว่างฟังก์ชั่นที่สามารถโทรออกและฟังก์ชั่นที่สามารถสร้างได้ ถ้าทำงานเป็น constructable ก็สามารถที่จะเรียกว่ามี คือnew new User()หากฟังก์ชั่นสามารถเรียกใช้ได้ก็สามารถเรียกได้โดยไม่ต้องnew(เช่นการเรียกใช้ฟังก์ชั่นปกติ)

ฟังก์ชั่นที่สร้างขึ้นผ่านการประกาศฟังก์ชั่น / การแสดงออกมีทั้งที่สามารถสร้างได้และ callable
ฟังก์ชั่นลูกศร (และวิธีการ) สามารถเรียกใช้ได้เท่านั้น classตัวสร้างสามารถสร้างได้เท่านั้น

หากคุณพยายามเรียกใช้ฟังก์ชันที่ไม่สามารถเรียกได้หรือสร้างฟังก์ชันที่ไม่สามารถแก้ไขได้คุณจะได้รับข้อผิดพลาดรันไทม์


เมื่อทราบสิ่งนี้เราสามารถระบุสิ่งต่อไปนี้

ถอดเปลี่ยนได้:

  • ฟังก์ชั่นที่ไม่ได้ใช้หรือthisarguments
  • ฟังก์ชั่นที่ใช้กับ .bind(this)

ไม่สามารถเปลี่ยนได้:

  • ฟังก์ชันตัวสร้าง
  • ฟังก์ชั่น / วิธีการเพิ่มต้นแบบ (เพราะพวกเขามักจะใช้this)
  • ฟังก์ชัน Variadic (ถ้าใช้arguments(ดูด้านล่าง))

มาดูตัวอย่างนี้โดยละเอียด:

ฟังก์ชันตัวสร้าง

newนี้จะไม่ทำงานเพราะฟังก์ชั่นลูกศรไม่สามารถเรียกว่ามี ใช้ฟังก์ชั่นการประกาศ / แสดงออกหรือใช้classต่อไป

วิธีการต้นแบบ

ไม่น่าเป็นไปได้เพราะวิธีการต้นแบบมักจะใช้thisในการเข้าถึงอินสแตนซ์ หากไม่ได้ใช้งานthisคุณสามารถแทนที่ได้ อย่างไรก็ตามถ้าคุณสนใจไวยากรณ์ที่รัดกุมให้ใช้classกับไวยากรณ์ของเมธอดรัดกุม:

class User {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

วิธีการวัตถุ

ในทำนองเดียวกันสำหรับวิธีการในวัตถุตามตัวอักษร หากวิธีการที่ต้องการอ้างอิงวัตถุตัวเองผ่านthisให้ใช้การแสดงออกของฟังก์ชั่นหรือใช้ไวยากรณ์วิธีการใหม่:

const obj = {
  getName() {
    // ...
  },
};

เรียกกลับ

มันขึ้นอยู่กับ. คุณควรแทนที่ถ้าคุณใช้นามแฝงด้านนอกthisหรือกำลังใช้งาน.bind(this):

// old
setTimeout(function() {
  // ...
}.bind(this), 500);

// new
setTimeout(() => {
  // ...
}, 500);

แต่:หากโค้ดที่เรียก callback ตั้งค่าไว้อย่างชัดเจนthisเช่นเดียวกับตัวจัดการเหตุการณ์โดยเฉพาะกับ jQuery และ callback ใช้this(หรือarguments) คุณจะไม่สามารถใช้ฟังก์ชันลูกศรได้!

ฟังก์ชัน Variadic

เนื่องจากฟังก์ชั่นลูกศรไม่มีของตัวเองargumentsคุณจึงไม่สามารถแทนที่ด้วยฟังก์ชั่นลูกศรได้ อย่างไรก็ตาม ES2015 แนะนำทางเลือกในการใช้argumentsคือพารามิเตอร์ที่เหลือ

// old
function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// new
const sum = (...args) => {
  // ...
};

คำถามที่เกี่ยวข้อง:

แหล่งข้อมูลเพิ่มเติม:


6
อาจจะเป็นมูลค่าการกล่าวขวัญว่าศัพท์thisยังมีผลต่อและว่าพวกเขาไม่มีsuper .prototype
loganfsmyth

1
นอกจากนี้ยังเป็นการดีที่จะกล่าวถึงว่าพวกเขาไม่สามารถใช้แทนกันในทาง syntactically ได้ - ฟังก์ชั่นลูกศร ( AssignmentExpression) ไม่สามารถตกหล่นไปในทุก ๆ ที่การแสดงออกของฟังก์ชั่น ( PrimaryExpression) สามารถทำได้ ข้อผิดพลาดในการใช้งาน JS ที่สำคัญ)
JMM

@JMM: "มันดึงดูดผู้คนค่อนข้างบ่อย"คุณสามารถให้ตัวอย่างที่เป็นรูปธรรมได้หรือไม่ ดูเหมือนว่าสถานที่ที่คุณสามารถใส่ FE ได้ แต่ไม่ใช่ AF จะส่งผลให้เกิดข้อผิดพลาดขณะรันไทม์ ...
Felix Kling

แน่นอนว่าฉันหมายถึงสิ่งต่าง ๆ ที่พยายามเรียกใช้ฟังก์ชันลูกศรเช่นนิพจน์ฟังก์ชัน ( () => {}()) หรือทำสิ่งที่ต้องการx || () => {}ทันที นั่นคือสิ่งที่ฉันหมายถึง: ข้อผิดพลาด runtime (แยกวิเคราะห์) (และถึงแม้ว่าเป็นกรณีนี้บ่อยครั้งที่ผู้คนคิดว่าข้อผิดพลาดนั้นเกิดจากข้อผิดพลาด) คุณแค่พยายามปกปิดข้อผิดพลาดทางตรรกะที่จะไม่มีใครสังเกตเห็นเพราะพวกเขาไม่จำเป็นต้องมีข้อผิดพลาดเมื่อวิเคราะห์หรือดำเนินการ newกำลังมีข้อผิดพลาดรันไทม์ใช่ไหม
JMM

นี่คือลิงค์บางส่วนของมันขึ้นมาในป่า: substack / node-browserify # 1499 , babel / babel-eslint # 245 (นี่คือลูกศร async แต่ฉันคิดว่ามันเป็นปัญหาพื้นฐานเดียวกัน) และปัญหามากมายใน บาเบลที่หายากในขณะนี้ แต่ที่นี่เป็นหนึ่งT2847
JMM

11

ฟังก์ชั่นลูกศร => คุณสมบัติ ES6 ที่ดีที่สุดจนถึงตอนนี้ พวกเขาเป็นส่วนเสริมที่ทรงพลังอย่างมากต่อ ES6 ที่ฉันใช้อย่างต่อเนื่อง

รอคุณไม่สามารถใช้ฟังก์ชั่นลูกศรได้ทุกที่ในรหัสของคุณมันจะไม่ทำงานในทุกกรณีเช่นthisที่ฟังก์ชันลูกศรไม่สามารถใช้งานได้ ไม่ต้องสงสัยเลยว่าฟังก์ชั่นลูกศรเป็นส่วนเสริมที่ยอดเยี่ยมทำให้ได้รหัสที่เรียบง่าย

แต่คุณไม่สามารถใช้ฟังก์ชันลูกศรเมื่อจำเป็นต้องใช้บริบทแบบไดนามิก: การกำหนดวิธีการสร้างวัตถุที่มีตัวสร้างได้รับเป้าหมายจากสิ่งนี้เมื่อจัดการเหตุการณ์

ไม่ควรใช้ฟังก์ชันลูกศรเพราะ:

  1. พวกเขาไม่มี this

    มันใช้ "การกำหนดขอบเขตศัพท์" เพื่อหาว่าค่าของ " this" ควรเป็นเท่าไหร่ คำศัพท์ง่าย ๆ การกำหนดขอบเขตใช้ " this" จากด้านในของร่างกาย

  2. พวกเขาไม่มี arguments

    ฟังก์ชั่นลูกศรไม่มีargumentsวัตถุ แต่ฟังก์ชั่นเดียวกันนี้สามารถทำได้โดยใช้พารามิเตอร์ที่เหลือ

    let sum = (...args) => args.reduce((x, y) => x + y, 0) sum(3, 3, 1) // output - 7 `

  3. ไม่สามารถใช้กับ new

    ฟังก์ชั่นลูกศรไม่สามารถเป็นตัว จำกัด เนื่องจากมันไม่มีคุณสมบัติต้นแบบ

ควรใช้ฟังก์ชั่นลูกศรเมื่อใดและเมื่อใด:

  1. อย่าใช้เพื่อเพิ่มฟังก์ชั่นเป็นคุณสมบัติในวัตถุตามตัวอักษรเพราะเราไม่สามารถเข้าถึงได้
  2. การแสดงออกของฟังก์ชั่นที่ดีที่สุดสำหรับวิธีการวัตถุ ลูกศรฟังก์ชั่นที่ดีที่สุดสำหรับการเรียกกลับหรือวิธีการเช่นmap, หรือreduceforEach
  3. ใช้การประกาศฟังก์ชั่นสำหรับฟังก์ชั่นที่คุณจะเรียกชื่อ (เพราะพวกเขากำลังชักรอก)
  4. ใช้ฟังก์ชั่นลูกศรสำหรับการโทรกลับ (เพราะพวกเขามักจะผิดเพี้ยน)

2
2 พวกเขาไม่ได้มีข้อโต้แย้งฉันขอโทษไม่เป็นความจริงอย่างใดอย่างหนึ่งสามารถมีข้อโต้แย้งโดยไม่ต้องใช้ตัวดำเนินการ ... บางทีคุณอาจต้องการบอกว่าเจ้าไม่ได้มีอาร์เรย์เป็นอาร์กิวเมนต์
Carmine Tambascia

@CarmineTambascia อ่านเกี่ยวกับargumentsวัตถุพิเศษที่ไม่มีในฟังก์ชั่นลูกศรที่นี่: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
......

0

ในการใช้ฟังก์ชั่นลูกศรด้วยfunction.prototype.callฉันสร้างฟังก์ชันตัวช่วยบนต้นแบบวัตถุ:

  // Using
  // @func = function() {use this here} or This => {use This here}
  using(func) {
    return func.call(this, this);
  }

การใช้

  var obj = {f:3, a:2}
  .using(This => This.f + This.a) // 5

แก้ไข

คุณไม่จำเป็นต้องมีผู้ช่วย คุณสามารถทำได้:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.