'Hàm mũi tên' và 'Hàm' có tương đương / có thể trao đổi không?


520

Các hàm mũi tên trong ES2015 cung cấp một cú pháp ngắn gọn hơn.

  • Bây giờ tôi có thể thay thế tất cả các khai báo / biểu thức hàm bằng các hàm mũi tên không?
  • Tôi phải chú ý điều gì?

Ví dụ:

Hàm xây dựng

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

// vs

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

Phương pháp nguyên mẫu

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

// vs

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

Phương thức đối tượng (nghĩa đen)

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

// vs

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

Gọi lại

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

// vs

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

Chức năng biến thể

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

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

5
Các câu hỏi tương tự về các chức năng mũi tên đã xuất hiện ngày càng nhiều với ES2015 trở nên phổ biến hơn. Tôi không cảm thấy như có một câu hỏi / câu trả lời chính tắc tốt cho vấn đề này nên tôi đã tạo ra câu hỏi này. Nếu bạn nghĩ rằng đã có một cái tốt, xin vui lòng cho tôi biết và tôi sẽ đóng cái này dưới dạng trùng lặp hoặc xóa nó. Hãy cải thiện các ví dụ hoặc thêm những cái mới.
Felix Kling

2
Điều gì về JavaScript ecma6 thay đổi chức năng bình thường thành chức năng mũi tên ? Tất nhiên, một câu hỏi bình thường không bao giờ có thể tốt và chung chung như một câu hỏi được viết cụ thể để trở thành kinh điển.
Bergi

Nhìn vào ví dụ Plnkr này Biến số thisnày rất khác nhau timesCalledchỉ bằng 1 mỗi lần nút được gọi. Câu trả lời nào cho câu hỏi cá nhân của tôi: .click( () => { } ).click(function() { }) cả hai đều tạo ra cùng một số hàm khi được sử dụng trong một vòng lặp như bạn có thể thấy từ số Hướng dẫn trong Plnkr.
abbaf33f

Câu trả lời:


750

tl; dr: Không! Hàm mũi tên và khai báo hàm / biểu thức không tương đương và không thể thay thế một cách mù quáng.
Nếu chức năng bạn muốn thay thế không không sử dụng this, argumentsvà không được gọi với new, sau đó có.


Như thường lệ: nó phụ thuộc . Các hàm mũi tên có hành vi khác với khai báo / biểu thức hàm, vì vậy trước tiên chúng ta hãy xem xét sự khác biệt:

1. Từ điển thisarguments

Các hàm mũi tên không có ràng buộc riêng thishoặc argumentsràng buộc. Thay vào đó, những định danh được giải quyết trong phạm vi từ vựng như bất kỳ biến nào khác. Điều đó có nghĩa là bên trong hàm mũi tên thisargumentstham chiếu đến các giá trị của thisargumentstrong môi trường, hàm mũi tên được xác định trong (tức là "bên ngoài" hàm mũi tên):

// 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

Trong trường hợp biểu thức hàm, thistham chiếu đến đối tượng được tạo bên trong createObject. Trong trường hợp mũi tên chức năng, thisđề cập đến thiscủa createObjectchính nó.

Điều này làm cho các chức năng mũi tên trở nên hữu ích nếu bạn cần truy cập vào thismôi trường hiện tại:

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

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

Lưu ý rằng điều này cũng có nghĩa là không thể đặt chức năng mũi tên thisbằng .bindhoặc .call.

Nếu bạn không quen thuộc lắm this, hãy cân nhắc đọc

2. Chức năng mũi tên không thể được gọi với new

ES2015 phân biệt giữa các chức năng có khả năng gọi và chức năng có thể xây dựng . Nếu một chức năng là có thể xây dựng, nó có thể được gọi với new, tức là new User(). Nếu một chức năng có thể gọi được, nó có thể được gọi mà không có new(tức là gọi chức năng bình thường).

Các hàm được tạo thông qua các khai báo / biểu thức hàm đều có thể xây dựng và có thể gọi được.
Các hàm mũi tên (và phương thức) chỉ có thể gọi được. classnhà xây dựng chỉ có thể xây dựng.

Nếu bạn đang cố gắng gọi một chức năng không thể gọi được hoặc để xây dựng một chức năng không thể xây dựng, bạn sẽ gặp lỗi thời gian chạy.


Biết điều này, chúng ta có thể nói như sau.

Có thể thay thế:

  • Các chức năng không sử dụng thishoặc arguments.
  • Các hàm được sử dụng với .bind(this)

Không thể thay thế:

  • Hàm xây dựng
  • Hàm / phương thức được thêm vào nguyên mẫu (vì chúng thường sử dụng this)
  • Các hàm biến thể (nếu chúng sử dụng arguments(xem bên dưới))

Hãy xem xét kỹ hơn điều này bằng các ví dụ của bạn:

Hàm xây dựng

Điều này sẽ không hoạt động vì các chức năng mũi tên không thể được gọi với new. Tiếp tục sử dụng một khai báo / biểu thức chức năng hoặc sử dụng class.

Phương pháp nguyên mẫu

Nhiều khả năng là không, bởi vì các phương thức nguyên mẫu thường sử dụng thisđể truy cập thể hiện. Nếu họ không sử dụng this, thì bạn có thể thay thế nó. Tuy nhiên, nếu bạn chủ yếu quan tâm đến cú pháp súc tích, hãy sử dụng classvới cú pháp phương thức súc tích của nó:

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

  getName() {
    return this.name;
  }
}

Phương thức đối tượng

Tương tự cho các phương thức trong một đối tượng bằng chữ. Nếu phương thức muốn tham chiếu chính đối tượng thông qua this, hãy tiếp tục sử dụng các biểu thức hàm hoặc sử dụng cú pháp phương thức mới:

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

Gọi lại

Nó phụ thuộc. Bạn chắc chắn nên thay thế nó nếu bạn đang răng cưa bên ngoài thishoặc đang sử dụng .bind(this):

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

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

Nhưng: Nếu mã gọi lại gọi lại rõ ràng thisthành một giá trị cụ thể, như trường hợp thường gặp với các trình xử lý sự kiện, đặc biệt là với jQuery và gọi lại sử dụng this(hoặc arguments), bạn không thể sử dụng hàm mũi tên!

Chức năng biến thể

Vì các hàm mũi tên không có riêng arguments, bạn không thể thay thế chúng bằng hàm mũi tên. Tuy nhiên, ES2015 giới thiệu một cách thay thế cho việc sử dụng arguments: tham số phần còn lại .

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

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

Câu hỏi liên quan:

Tài nguyên khác:


6
Có thể đáng nói là từ vựng thiscũng ảnh hưởng supervà họ không có .prototype.
loganfsmyth

1
Cũng tốt khi đề cập rằng chúng không thể hoán đổi cho nhau về mặt cú pháp - một hàm mũi tên ( AssignmentExpression) không thể được bỏ ở mọi nơi một biểu thức hàm ( PrimaryExpression) có thể và nó khiến mọi người gặp nhau khá thường xuyên (đặc biệt là khi đã phân tích cú pháp lỗi trong các triển khai JS chính).
JMM

@JMM: "nó khiến mọi người đi lên khá thường xuyên" bạn có thể cung cấp một ví dụ cụ thể không? Lướt qua thông số kỹ thuật, có vẻ như những nơi bạn có thể đặt FE nhưng không phải là AF sẽ dẫn đến lỗi thời gian chạy ...
Felix Kling

Chắc chắn, ý tôi là những thứ như cố gắng gọi ngay một hàm mũi tên như biểu thức hàm ( () => {}()) hoặc làm một cái gì đó như thế x || () => {}. Ý tôi là: lỗi thời gian chạy (phân tích cú pháp). (Và mặc dù đó là trường hợp, khá thường xuyên mọi người nghĩ rằng lỗi là do lỗi.) Bạn chỉ đang cố gắng bao gồm các lỗi logic sẽ không được chú ý bởi vì họ không nhất thiết phải lỗi khi phân tích cú pháp hoặc thực thi? newMột trong những lỗi là thời gian chạy phải không?
JMM

Dưới đây là một số liên kết của nó xuất hiện trong tự nhiên: subsack / node-browserify # 1499 , babel / babel-eslint # 245 (đây là một mũi tên không đồng bộ, nhưng tôi nghĩ đó là vấn đề cơ bản tương tự) và một loạt vấn đề về Babel hiện khó tìm, nhưng đây là một chiếc T2847 .
JMM

11

Chức năng mũi tên => tính năng ES6 tốt nhất cho đến nay. Chúng là một bổ sung cực kỳ mạnh mẽ cho ES6, mà tôi sử dụng liên tục.

Đợi đã, bạn không thể sử dụng chức năng mũi tên ở mọi nơi trong mã của mình, nó sẽ không hoạt động trong mọi trường hợp như thischức năng mũi tên không sử dụng được. Không còn nghi ngờ gì nữa, chức năng mũi tên là một bổ sung tuyệt vời, nó mang lại sự đơn giản cho mã.

Nhưng bạn không thể sử dụng hàm mũi tên khi cần một bối cảnh động: xác định các phương thức, tạo các đối tượng với các hàm tạo, lấy mục tiêu từ đây khi xử lý các sự kiện.

Không nên sử dụng các hàm mũi tên vì:

  1. Họ không có this

    Nó sử dụng phạm vi phạm vi từ vựng của Cameron để tìm ra giá trị của phạm lỗi this. Nói một cách đơn giản, phạm vi từ vựng của nó, nó sử dụng thistừ ngữ trong cơ thể của hàm.

  2. Họ không có arguments

    Các hàm mũi tên không có argumentsđối tượng. Nhưng chức năng tương tự có thể đạt được bằng cách sử dụng các tham số nghỉ ngơi.

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

  3. Chúng không thể được sử dụng với new

    Các hàm mũi tên không thể được hiểu bởi vì chúng không có thuộc tính nguyên mẫu.

Khi nào nên sử dụng chức năng mũi tên và khi nào không:

  1. Không sử dụng để thêm chức năng như một thuộc tính theo nghĩa đen bởi vì chúng tôi không thể truy cập này.
  2. Các biểu thức hàm là tốt nhất cho các phương thức đối tượng. Mũi tên chức năng là tốt nhất cho callbacks hoặc các phương pháp như map, reducehoặc forEach.
  3. Sử dụng khai báo hàm cho các hàm bạn gọi theo tên (vì chúng được nâng lên).
  4. Sử dụng các chức năng mũi tên cho các cuộc gọi lại (bởi vì chúng có xu hướng nhạy hơn).

2
2. Họ không có đối số, tôi xin lỗi là không đúng, người ta có thể tranh luận mà không sử dụng toán tử ..., có thể bạn muốn nói rằng thy không có mảng làm đối số
Carmine Tambascia

@CarmineTambascia Đọc về argumentsđối tượng đặc biệt không có trong các hàm mũi tên ở đây: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
tựa

0

Để sử dụng các hàm mũi tên với function.prototype.call, tôi đã tạo một hàm trợ giúp trên nguyên mẫu đối tượng:

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

sử dụng

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

Biên tập

Bạn không CẦN một người trợ giúp. Bạn có thể làm:

var obj = {f:3, a:2}
(This => This.f + This.a).call(undefined, obj); // 5
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.