Khi nào tôi nên sử dụng các hàm Mũi tên trong ECMAScript 6?


406

Câu hỏi được nhắm vào những người đã nghĩ về phong cách mã trong bối cảnh của ECMAScript 6 (Harmony) sắp tới và những người đã làm việc với ngôn ngữ này.

Với () => {}function () {}chúng tôi đang nhận được hai cách rất giống nhau để viết các hàm trong ES6. Trong các ngôn ngữ khác, các hàm lambda thường tự phân biệt bằng cách ẩn danh, nhưng trong ECMAScript, bất kỳ hàm nào cũng có thể ẩn danh. Mỗi trong hai loại có các miền sử dụng duy nhất (cụ thể là khi thiscần phải được ràng buộc rõ ràng hoặc không bị ràng buộc rõ ràng). Giữa các tên miền đó có một số lượng lớn các trường hợp ký hiệu sẽ làm.

Các hàm mũi tên trong ES6 có ít nhất hai giới hạn:

  • Không làm việc với newvà không thể được sử dụng khi tạoprototype
  • Đã sửa lỗi thisgiới hạn phạm vi lúc khởi tạo

Bỏ qua hai hạn chế này, về mặt lý thuyết, các chức năng mũi tên có thể thay thế các chức năng thông thường ở hầu hết mọi nơi. Cách tiếp cận đúng sử dụng chúng trong thực tế là gì? Nên sử dụng các hàm mũi tên, ví dụ:

  • "Ở mọi nơi chúng hoạt động", tức là ở mọi nơi, một hàm không phải là bất khả tri về thisbiến và chúng ta không tạo ra một đối tượng.
  • chỉ "ở mọi nơi họ cần", tức là người nghe sự kiện, thời gian chờ, cần phải bị ràng buộc trong một phạm vi nhất định
  • với các chức năng 'ngắn' nhưng không có chức năng 'dài'
  • chỉ với các chức năng không chứa chức năng mũi tên khác

Những gì tôi đang tìm kiếm là một hướng dẫn để chọn ký hiệu chức năng phù hợp trong phiên bản tương lai của ECMAScript. Hướng dẫn sẽ cần phải rõ ràng, để nó có thể được dạy cho các nhà phát triển trong một nhóm và phải nhất quán để không yêu cầu tái cấu trúc liên tục qua lại từ ký hiệu chức năng này sang ký hiệu khác.


33
Bạn coi Fixed this bound to scope at initialisationnhư một hạn chế?
thefourtheye

12
Đó là một lợi thế, nhưng nó cũng có thể là một hạn chế nếu bạn có kế hoạch sử dụng lại chức năng bên ngoài bối cảnh ban đầu. Chẳng hạn, khi thêm một hàm vào một lớp động thông qua Object.prototype. Ý tôi là 'giới hạn' là việc thay đổi giá trị thislà điều bạn có thể làm với các hàm thông thường nhưng không phải với các hàm mũi tên.
lyschoen

1
Thành thật tôi nghĩ rằng hướng dẫn phong cách mã hóa là khá quan điểm. Đừng hiểu sai ý tôi, tôi nghĩ chúng rất quan trọng, nhưng không có một hướng dẫn nào phù hợp với tất cả mọi người.
Felix Kling

Tôi không nghĩ Fixed this bound to scope at initialisationlà một hạn chế. :) Hãy xem bài viết này: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy

3
@thefourtheye, "giới hạn" ở đây có nghĩa là "giới hạn bởi vì một người dịch mã tự động câm không thể thay thế một cách mù quáng bằng cái khác và cho rằng mọi thứ sẽ chạy như mong đợi".
Pacerier

Câu trả lời:


322

Cách đây một thời gian, nhóm của chúng tôi đã chuyển tất cả mã của nó (một ứng dụng AngularJS cỡ trung bình) sang JavaScript được biên dịch bằng Traceur Babel . Tôi hiện đang sử dụng quy tắc ngón tay cái sau đây cho các chức năng trong ES6 và hơn thế nữa:

  • Sử dụng functiontrong phạm vi toàn cầu và cho Object.prototypecác thuộc tính.
  • Sử dụng classcho các hàm tạo đối tượng.
  • Sử dụng =>ở mọi nơi khác.

Tại sao sử dụng chức năng mũi tên hầu như ở khắp mọi nơi?

  1. Phạm vi an toàn: Khi các chức năng mũi tên được sử dụng nhất quán, mọi thứ được đảm bảo để sử dụng giống thisObjectnhư root. Nếu ngay cả một hàm gọi lại chức năng tiêu chuẩn duy nhất được trộn lẫn với một loạt các hàm mũi tên, có khả năng phạm vi sẽ bị rối tung.
  2. Tính gọn nhẹ: Các chức năng mũi tên dễ đọc và viết hơn. (Điều này có vẻ như có ý kiến ​​vì vậy tôi sẽ đưa ra một vài ví dụ nữa).
  3. Rõ ràng: Khi hầu hết mọi thứ là một chức năng mũi tên, bất kỳ thông thường nào functionngay lập tức đưa ra để xác định phạm vi. Một nhà phát triển luôn có thể tra cứu functioncâu lệnh cao hơn tiếp theo để xem đó là gì thisObject.

Tại sao luôn sử dụng các hàm thông thường trên phạm vi toàn cầu hoặc phạm vi mô-đun?

  1. Để chỉ ra một chức năng không nên truy cập vào thisObject.
  2. Đối windowtượng (phạm vi toàn cầu) được giải quyết tốt nhất một cách rõ ràng.
  3. Nhiều Object.prototypeđịnh nghĩa sống trong phạm vi toàn cầu (nghĩ, String.prototype.truncatev.v.) và những định nghĩa nói chung phải thuộc loại functionnào. Sử dụng nhất quán functiontrên phạm vi toàn cầu giúp tránh lỗi.
  4. Nhiều hàm trong phạm vi toàn cục là các hàm tạo đối tượng cho các định nghĩa lớp kiểu cũ.
  5. Các chức năng có thể được đặt tên 1 . Điều này có hai lợi ích: (1) Viết ít khó xử function foo(){}hơn const foo = () => {}- đặc biệt là bên ngoài các lệnh gọi hàm khác. (2) Tên hàm hiển thị trong dấu vết ngăn xếp. Mặc dù sẽ rất tẻ nhạt khi đặt tên cho mỗi cuộc gọi lại nội bộ, đặt tên cho tất cả các chức năng công cộng có lẽ là một ý tưởng tốt.
  6. Khai báo hàm được nâng lên , (có nghĩa là chúng có thể được truy cập trước khi chúng được khai báo), đây là một thuộc tính hữu ích trong một hàm tiện ích tĩnh.


Đối tượng xây dựng

Cố gắng khởi tạo một chức năng mũi tên ném một ngoại lệ:

var x = () => {};
new x(); // TypeError: x is not a constructor

Do đó, một lợi thế chính của các hàm so với các hàm mũi tên là do các hàm này nhân đôi như các hàm tạo đối tượng:

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

Tuy nhiên, định nghĩa lớp dự thảo 2 ES Harmony giống hệt nhau về chức năng là gần như nhỏ gọn:

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

Tôi hy vọng rằng việc sử dụng các ký hiệu cũ cuối cùng sẽ được khuyến khích. Ký hiệu của hàm tạo đối tượng vẫn có thể được sử dụng bởi một số nhà máy đối tượng ẩn danh đơn giản, nơi các đối tượng được tạo lập trình, nhưng không phải cho nhiều thứ khác.

Khi cần một hàm tạo đối tượng, người ta nên xem xét chuyển đổi hàm thành classnhư được hiển thị ở trên. Cú pháp hoạt động với các hàm / lớp ẩn danh là tốt.


Khả năng đọc của các chức năng mũi tên

Đối số có lẽ tốt nhất để bám vào các hàm thông thường - an toàn phạm vi bị nguyền rủa - sẽ là các hàm mũi tên ít đọc hơn các hàm thông thường. Nếu mã của bạn không hoạt động ở vị trí đầu tiên, thì các hàm mũi tên có vẻ không cần thiết và khi các hàm mũi tên không được sử dụng một cách nhất quán, chúng trông xấu.

ECMAScript đã thay đổi khá nhiều kể từ ECMAScript 5.1 đã cho chúng tôi các chức năng Array.forEach, Array.mapvà tất cả các tính năng lập trình chức năng mà có chúng ta chức năng nơi cho-vòng sẽ được sử dụng trước khi sử dụng. JavaScript không đồng bộ đã mất khá nhiều. ES6 cũng sẽ gửi một Promiseđối tượng, có nghĩa là nhiều chức năng ẩn danh hơn. Không có trở lại cho lập trình chức năng. Trong JavaScript chức năng, các hàm mũi tên được ưa thích hơn các hàm thông thường.

Ví dụ đoạn mã 3 (đặc biệt khó hiểu) này :

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(articles => Promise.all(articles.map(article => article.comments.getList())))
        .then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
        .then(comments => {
            this.comments = comments;
        })
}

Cùng một đoạn mã với các hàm thông thường:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(function (articles) {
            return Promise.all(articles.map(function (article) { 
                return article.comments.getList();
            }));
        })
        .then(function (commentLists) {
            return commentLists.reduce(function (a, b) {
                return a.concat(b); 
            });
        })
        .then(function (comments) {
            this.comments = comments;
        }.bind(this));
}

Mặc dù bất kỳ một trong các chức năng mũi tên có thể được thay thế bằng một chức năng tiêu chuẩn, sẽ có rất ít để đạt được điều đó. Phiên bản nào dễ đọc hơn? Tôi sẽ nói điều đầu tiên.

Tôi nghĩ rằng câu hỏi nên sử dụng chức năng mũi tên hoặc chức năng thông thường sẽ trở nên ít liên quan hơn theo thời gian. Hầu hết các hàm sẽ trở thành các phương thức lớp, loại bỏ functiontừ khóa hoặc chúng sẽ trở thành các lớp. Các hàm sẽ vẫn được sử dụng để vá các lớp thông qua Object.prototype. Trong thời gian đó, tôi khuyên bạn nên bảo lưu functiontừ khóa cho bất kỳ thứ gì thực sự nên là một phương thức lớp hoặc một lớp.


Ghi chú

  1. Các chức năng mũi tên được đặt tên đã được hoãn lại trong thông số ES6 . Họ vẫn có thể được thêm một phiên bản trong tương lai.
  2. Theo đặc tả dự thảo "Khai báo / biểu thức lớp tạo một cặp hàm xây dựng / nguyên mẫu chính xác như đối với khai báo hàm" miễn là một lớp không sử dụng extendtừ khóa. Một sự khác biệt nhỏ là khai báo lớp là hằng số, trong khi khai báo hàm thì không.
  3. Lưu ý về các khối trong các hàm mũi tên câu lệnh đơn: Tôi thích sử dụng một khối bất cứ nơi nào một hàm mũi tên được gọi cho hiệu ứng phụ một mình (ví dụ: gán). Bằng cách đó, rõ ràng là giá trị trả về có thể bị loại bỏ.

4
Lần khác bạn muốn sử dụng functionlà khi bạn không muốn thisbị ràng buộc, phải không? Kịch bản phổ biến nhất của tôi cho điều này là các sự kiện, trong đó bạn có thể muốn thistham chiếu đến đối tượng (thường là nút DOM) đã kích hoạt sự kiện.
Brett

13
Tôi thực sự nghĩ trong ví dụ 3, các hàm thông thường dễ đọc hơn. Ngay cả những người không lập trình cũng có thể thần thánh những gì đang xảy ra. Với các mũi tên, bạn cần biết chính xác cách chúng hoạt động để hiểu ví dụ đó. Có thể nhiều dòng mới sẽ giúp ví dụ về mũi tên, nhưng tôi không biết. Chỉ 2 xu của tôi nhưng mũi tên làm tôi co rúm lại (nhưng tôi chưa sử dụng chúng, vì vậy tôi có thể được chuyển đổi sớm.)
Spencer

3
@Spencer đó là một điểm công bằng. Từ kinh nghiệm của riêng tôi, =>cuối cùng nhìn tốt hơn với thời gian. Tôi nghi ngờ rằng những người không lập trình sẽ cảm thấy rất khác nhau về hai ví dụ. Nếu bạn đang viết mã ES2016, thông thường bạn sẽ không sử dụng nhiều hàm mũi tên này. Trong ví dụ này, sử dụng async / await và hiểu mảng, bạn sẽ kết thúc chỉ với một hàm mũi tên trong reduce()cuộc gọi.
lyschoen

3
Tôi hoàn toàn đồng ý với Spencer rằng các chức năng thông thường dễ đọc hơn nhiều trong ví dụ đó.
jonschlinkert

2
Câu trả lời tốt, thx! cá nhân tôi cũng sử dụng mũi tên trong phạm vi toàn cầu càng nhiều càng tốt. Điều này khiến tôi gần như không có "chức năng". Đối với tôi, một 'hàm' trong mã có nghĩa là một trường hợp đặc biệt cần bám sát và được xem xét cẩn thận.
kofifus

80

Theo đề xuất , các mũi tên nhằm "giải quyết và giải quyết một số điểm đau chung của truyền thống Function Expression". Họ dự định cải thiện vấn đề bằng cách ràng buộc từ thisvựng và đưa ra cú pháp ngắn gọn.

Tuy nhiên,

  • Người ta không thể liên kết từ thisvựng
  • Cú pháp hàm mũi tên là tinh tế và mơ hồ

Do đó, các hàm mũi tên tạo cơ hội cho sự nhầm lẫn và lỗi và nên được loại trừ khỏi từ vựng của lập trình viên JavaScript, được thay thế bằng functionđộc quyền.

Về từ vựng this

this có vấn đề:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

Các hàm mũi tên có ý định khắc phục sự cố trong đó chúng ta cần truy cập vào một thuộc tính thisbên trong một cuộc gọi lại. Đã có một số cách để làm điều đó: Người ta có thể gán thischo một biến, sử dụng bindhoặc sử dụng đối số thứ 3 có sẵn trên các Arrayphương thức tổng hợp. Tuy nhiên, mũi tên dường như là cách giải quyết đơn giản nhất, vì vậy phương pháp có thể được cấu trúc lại như thế này:

this.pages.forEach(page => page.draw(this.settings));

Tuy nhiên, hãy xem xét nếu mã sử dụng một thư viện như jQuery, có phương thức liên kết thisđặc biệt. Bây giờ, có hai thisgiá trị để giải quyết:

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

Chúng ta phải sử dụng functionđể cho eachđể ràng buộc thisđộng. Chúng ta không thể sử dụng chức năng mũi tên ở đây.

Đối phó với nhiều thisgiá trị cũng có thể gây nhầm lẫn, bởi vì thật khó để biết thistác giả nào đang nói về:

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

Có phải tác giả thực sự có ý định gọi Book.prototype.reformat? Hay anh quên không ràng buộc this, và có ý định gọi Reader.prototype.reformat? Nếu chúng ta thay đổi trình xử lý thành hàm mũi tên, chúng ta sẽ tự hỏi tương tự liệu tác giả có muốn động không this, nhưng đã chọn một mũi tên vì nó phù hợp trên một dòng:

function Reader() {
    this.book.on('change', () => this.reformat());
}

Người ta có thể đặt ra: "Điều đặc biệt là mũi tên đôi khi có thể là chức năng sai khi sử dụng? Có lẽ nếu chúng ta hiếm khi chỉ cần các thisgiá trị động , thì vẫn có thể sử dụng mũi tên trong hầu hết thời gian."

Nhưng hãy tự hỏi mình điều này: "Sẽ là" đáng giá "để gỡ lỗi mã và thấy rằng kết quả của một lỗi đã được đưa ra bởi một 'trường hợp cạnh?'" Tôi muốn tránh rắc rối không chỉ trong hầu hết thời gian, nhưng 100% thời gian.

Có một cách tốt hơn: Luôn luôn sử dụng function(vì vậy thisluôn có thể bị ràng buộc động) và luôn tham chiếu thisqua một biến. Các biến là từ vựng và giả sử nhiều tên. Việc gán thischo một biến sẽ làm cho ý định của bạn rõ ràng:

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

Hơn nữa, luôn luôn gán thischo một biến (ngay cả khi có một thischức năng duy nhất hoặc không có chức năng nào khác) đảm bảo ý định của một người vẫn rõ ràng ngay cả sau khi mã được thay đổi.

Ngoài ra, năng động thislà khó khăn đặc biệt. jQuery được sử dụng trên hơn 50 triệu trang web (kể từ khi viết vào tháng 2 năm 2016). Dưới đây là các API khác ràng buộc thisđộng:

  • Mocha (~ 120k lượt tải ngày hôm qua) hiển thị các phương thức cho các thử nghiệm của nó thông qua this.
  • Grunt (~ 63k lượt tải ngày hôm qua) hiển thị các phương thức để xây dựng các tác vụ thông qua this.
  • Xương sống (~ 22k lượt tải ngày hôm qua) xác định phương thức truy cập this.
  • API tổ chức sự kiện (như của DOM) đề cập đến một EventTargetvới this.
  • API nguyên mẫu được vá hoặc mở rộng tham chiếu đến các trường hợp với this.

(Số liệu thống kê qua http://trends.builtwith.com/javascript/jQueryhttps://www.npmjs.com .)

Bạn có khả năng yêu cầu thisràng buộc năng động đã.

Một từ vựng thisđôi khi được mong đợi, nhưng đôi khi không; giống như một động lực thisđôi khi được mong đợi, nhưng đôi khi không. Rất may, có một cách tốt hơn, luôn tạo ra và truyền đạt sự ràng buộc mong đợi.

Về cú pháp ngắn gọn

Các hàm mũi tên đã thành công trong việc cung cấp "dạng cú pháp ngắn hơn" cho các hàm. Nhưng những chức năng ngắn này sẽ làm cho bạn thành công hơn?

x => x * x"dễ đọc" hơn function (x) { return x * x; }? Có lẽ là như vậy, bởi vì nó có nhiều khả năng tạo ra một dòng mã ngắn. Tham gia vào Dyson ' Ảnh hưởng của tốc độ đọc và độ dài dòng đến hiệu quả của việc đọc từ màn hình ,

Độ dài dòng trung bình (55 ký tự trên mỗi dòng) xuất hiện để hỗ trợ đọc hiệu quả ở tốc độ bình thường và nhanh. Điều này tạo ra mức độ hiểu biết cao nhất. . .

Các biện minh tương tự được thực hiện cho toán tử điều kiện (ternary) và cho các ifcâu lệnh đơn.

Tuy nhiên, bạn có thực sự viết các hàm toán học đơn giản được quảng cáo trong đề xuất không? Miền của tôi không phải là toán học, vì vậy chương trình con của tôi hiếm khi thanh lịch như vậy. Thay vào đó, tôi thường thấy các hàm mũi tên phá vỡ giới hạn cột và bọc sang một dòng khác do trình chỉnh sửa hoặc hướng dẫn kiểu, làm vô hiệu hóa "khả năng đọc" theo định nghĩa của Dyson.

Người ta có thể đặt ra, "Làm thế nào về việc chỉ sử dụng phiên bản ngắn cho các chức năng ngắn, khi có thể?" Nhưng bây giờ một quy tắc phong cách mâu thuẫn với một ràng buộc ngôn ngữ: "Cố gắng sử dụng ký hiệu chức năng ngắn nhất có thể, hãy nhớ rằng đôi khi chỉ có ký hiệu dài nhất sẽ ràng buộc thisnhư mong đợi." Sự kết hợp như vậy làm cho mũi tên đặc biệt dễ bị sử dụng sai.

Có nhiều vấn đề với cú pháp hàm mũi tên:

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

Cả hai chức năng này đều có giá trị cú pháp. Nhưng doSomethingElse(x);không phải trong cơ thể b, nó chỉ là một tuyên bố cấp thấp, thụt lề.

Khi mở rộng sang dạng khối, không còn ẩn return, người ta có thể quên khôi phục. Nhưng biểu thức chỉ có thể được dự định để tạo ra một hiệu ứng phụ, vậy ai biết nếu một điều rõ ràng returnsẽ là cần thiết trong tương lai?

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

Những gì có thể được dự định là một tham số phần còn lại có thể được phân tích cú pháp như toán tử trải rộng:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

Bài tập có thể bị nhầm lẫn với các đối số mặc định:

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens

Các khối trông giống như các đối tượng:

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

Điều đó có nghĩa là gì?

() => {}

Có phải tác giả đã có ý định tạo ra một no-op, hoặc một hàm trả về một đối tượng trống? (Với suy nghĩ này, chúng ta có nên đặt {sau =>không? Chúng ta có nên hạn chế chỉ sử dụng cú pháp biểu thức không? Điều đó sẽ làm giảm thêm tần suất của mũi tên.)

=>trông giống như <=>=:

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

Để gọi một biểu thức chức năng mũi tên ngay lập tức, người ta phải đặt ()bên ngoài, nhưng đặt ()bên trong là hợp lệ và có thể có chủ ý.

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

Mặc dù, nếu một người viết (() => doSomething()());với ý định viết một biểu thức hàm được gọi ngay lập tức, đơn giản là sẽ không có gì xảy ra.

Thật khó để tranh luận rằng các chức năng mũi tên là "dễ hiểu hơn" với tất cả các trường hợp trên trong tâm trí. Người ta có thể tìm hiểu tất cả các quy tắc đặc biệt cần thiết để sử dụng cú pháp này. nó thật sự đáng giá thế sao?

Cú pháp của functionlà không khái quát. Để sử dụng functionriêng có nghĩa là ngôn ngữ tự ngăn người ta viết mã khó hiểu. Để viết các thủ tục cần được hiểu theo cú pháp trong mọi trường hợp, tôi chọn function.

Về hướng dẫn

Bạn yêu cầu một hướng dẫn cần phải "rõ ràng" và "nhất quán". Việc sử dụng các hàm mũi tên cuối cùng sẽ dẫn đến mã hợp lệ, không hợp lệ về mặt cú pháp, với cả hai dạng hàm được đan xen, có ý nghĩa và tùy ý. Vì vậy, tôi cung cấp như sau:

Hướng dẫn về ký hiệu chức năng trong ES6:

  • Luôn tạo thủ tục với function.
  • Luôn luôn gán thischo một biến. Không sử dụng () => {}.

5
Thú vị viết lên quan điểm của một lập trình viên chức năng về JavaScript. Tôi không chắc chắn tôi đồng ý với các đối số biến riêng tư. IMO vài người thực sự cần chúng; những người làm có lẽ cũng sẽ cần các tính năng hợp đồng khác và đi đến một phần mở rộng ngôn ngữ như TypeScript. Tôi chắc chắn có thể thấy sự hấp dẫn của một selfthay vì điều này. Cạm bẫy chức năng mũi tên đã nêu của bạn cũng đều hợp lệ, và các tiêu chuẩn tương tự như trên các tuyên bố khác có thể đi mà không cần niềng răng chắc chắn cũng áp dụng ở đây; mặt khác, tôi nghĩ với lập luận của bạn, người ta cũng có thể ủng hộ các chức năng mũi tên ở mọi nơi.
lyschoen

7
"Có nhiều cách làm việc tạo ra các vectơ không cần thiết cho tranh luận và bất đồng trong công việc và cộng đồng ngôn ngữ. Sẽ tốt hơn nếu ngữ pháp ngôn ngữ không cho phép chúng tôi đưa ra lựa chọn kém." Đồng ý rất nhiều. Đẹp lắm Tôi nghĩ rằng các chức năng mũi tên là một bước lùi. Ở một chủ đề khác, tôi ước các đồng nghiệp của mình sẽ ngừng cố gắng biến JavaScript thành C # với một loạt các định nghĩa .prototype. No thật kinh tởm. Tôi nên ẩn danh liên kết bài đăng của bạn :)
Spencer

11
Viết rất tốt! Mặc dù tôi không đồng ý với hầu hết các quan điểm của bạn, nhưng điều quan trọng là phải xem xét quan điểm ngược lại.
tôi

4
Không phải các chức năng mũi tên nhưng hành vi kỳ lạ thislà vấn đề của Javascript. Thay vì bị ràng buộc ngầm, thisnên được thông qua như một đối số rõ ràng.
bob

5
" Luôn luôn sử dụng chức năng (vì vậy điều này luôn có thể được ràng buộc động) và luôn tham chiếu điều này thông qua một biến. ". Tôi không thể không đồng ý nhiều hơn nữa!

48

Các hàm mũi tên được tạo để đơn giản hóa chức năng scopevà giải thistừ khóa bằng cách làm cho nó đơn giản hơn. Họ sử dụng =>cú pháp, trông giống như một mũi tên.

Lưu ý: Nó không thay thế các chức năng hiện có. Nếu bạn thay thế mọi cú pháp hàm bằng các hàm mũi tên, nó sẽ không hoạt động trong mọi trường hợp.

Chúng ta hãy xem cú pháp ES5 hiện có, Nếu this từ khóa nằm trong phương thức của một đối tượng (một hàm thuộc về một đối tượng), thì nó sẽ đề cập đến điều gì?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

Đoạn trích trên sẽ đề cập đến một objectvà in ra tên"RajiniKanth" . Hãy khám phá đoạn trích dưới đây và xem điều gì sẽ chỉ ra ở đây.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Bây giờ thì sao nếu this từ khóa là bên trong method’s function?

Ở đây điều này sẽ đề cập đến window objecthơninner function là nó rơi ra scope. Bởi vì this, luôn luôn tham chiếu chủ sở hữu của chức năng, trong trường hợp này - vì hiện tại nó nằm ngoài phạm vi - cửa sổ / đối tượng toàn cầu.

Khi nó ở bên trong objectphương thức - functionchủ sở hữu là đối tượng. Do đó, từ khóa này được ràng buộc với đối tượng. Tuy nhiên, khi nó ở bên trong một hàm, độc lập hoặc trong một phương thức khác, nó sẽ luôn luôn tham chiếu đến window/globalđối tượng.

var fn = function(){
  alert(this);
}

fn(); // [object Window]

Có nhiều cách để tự giải quyết vấn đề ES5này, chúng ta hãy xem xét vấn đề đó trước khi đi sâu vào chức năng mũi tên ES6 về cách giải quyết nó.

Thông thường, bạn sẽ tạo một biến bên ngoài hàm bên trong của phương thức. Bây giờ ‘forEach’phương thức đạt được quyền truy cập thisvà do đó các object’sthuộc tính và giá trị của chúng.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

sử dụng bindđể đính kèm thistừ khóa đề cập đến phương thức vào method’s inner function.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   }.bind(this));
  }
};

Actor.showMovies();

Bây giờ với ES6chức năng mũi tên, chúng ta có thể xử lý lexical scopingvấn đề theo cách đơn giản hơn.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Arrow functionsgiống như các câu lệnh hàm hơn, ngoại trừ việc chúng bindlà cái này parent scope. Nếuarrow function is in top scopethis đối số , sẽ tham chiếu window/global scope, trong khi một hàm mũi tên bên trong một hàm thông thường sẽ có đối số này giống như hàm ngoài của nó.

Với các arrowchức năng thisđược ràng buộc với bao vây scopetại thời điểm tạo và không thể thay đổi. Toán tử mới, liên kết, gọi và áp dụng không có tác dụng trong việc này.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

Trong ví dụ trên, chúng tôi đã mất quyền kiểm soát này. Chúng ta có thể giải quyết ví dụ trên bằng cách sử dụng tham chiếu biến thishoặc sử dụng bind. Với ES6, việc quản lý thisnhư bị ràng buộc trở nên dễ dàng hơn lexical scoping.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

Khi không đến chức năng Mũi tên

Bên trong một đối tượng theo nghĩa đen.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getNameđược định nghĩa bằng hàm mũi tên, nhưng khi gọi, nó cảnh báo không xác định vì this.nameundefined như bối cảnh vẫn còn window.

Nó xảy ra bởi vì hàm mũi tên liên kết bối cảnh từ vựng với window object... tức là phạm vi bên ngoài. Thi công this.nametương đương vớiwindow.name , không xác định.

Nguyên mẫu đối tượng

Quy tắc tương tự áp dụng khi xác định các phương thức trên a prototype object. Thay vì sử dụng hàm mũi tên để xác định phương thức sayCatName, điều này mang lại không chính xác context window:

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

Gọi các nhà xây dựng

thistrong một lời mời xây dựng là đối tượng mới được tạo. Khi thực hiện Fn () mới, bối cảnh của constructor Fnmột đối tượng mới : this instanceof Fn === true.

this được thiết lập từ bối cảnh kèm theo, tức là phạm vi bên ngoài làm cho nó không được gán cho đối tượng mới được tạo.

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

Gọi lại với bối cảnh năng động

Hàm mũi tên liên kết contexttĩnh trên khai báo và không thể làm cho nó động. Gắn trình lắng nghe sự kiện vào các phần tử DOM là một nhiệm vụ phổ biến trong lập trình phía máy khách. Một sự kiện kích hoạt chức năng xử lý với điều này là phần tử đích.

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

thislà cửa sổ trong một hàm mũi tên được xác định trong bối cảnh toàn cầu. Khi sự kiện nhấp xảy ra, trình duyệt sẽ cố gắng gọi chức năng xử lý với ngữ cảnh nút, nhưng chức năng mũi tên không thay đổi bối cảnh được xác định trước. this.innerHTMLlà tương đương window.innerHTMLvà không có ý nghĩa.

Bạn phải áp dụng một biểu thức hàm, cho phép thay đổi điều này tùy thuộc vào thành phần đích:

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

Khi người dùng nhấp vào nút, nút này trong chức năng xử lý là nút. Do đó this.innerHTML = 'Clicked button'sửa đổi chính xác văn bản nút để phản ánh trạng thái nhấp.

Tài liệu tham khảo: https://dmitripavlutin.com/when-not-to-use-arrow-fifts-in-javascript/


Chà, tôi phải thừa nhận rằng "điều tốt nhất nằm ở giữa" . Được nâng cấp cho câu lệnh, các hàm mũi tên sẽ không bao gồm bất kỳ trường hợp sử dụng hàm nào có thể. Chúng thực sự được thiết kế để chỉ giải quyết một phần của các vấn đề phổ biến. Chỉ cần chuyển sang họ hoàn toàn sẽ là một quá mức cần thiết.
BlitZ

@DmitriPavlutin: Kiểm tra bài đăng cập nhật của tôi, một bộ sưu tập rất nhiều điều ... có thể tôi nên đăng ra một tài liệu tham khảo.
Thalaivar

2
Mã của bạn sau dòng 'sử dụng liên kết để đính kèm từ khóa này đề cập đến phương thức vào hàm bên trong của phương thức.' có lỗi trong đó. Bạn đã thử nghiệm các ví dụ còn lại của bạn?
Isaac Pak

Một cái using bind to attach the this keyword that refers to the method to the method’s inner function.có lỗi cú pháp.
Coda Chang

Nên làvar Actor = { name: 'RajiniKanth', movies: ['Kabali', 'Sivaji', 'Baba'], showMovies: function() { this.movies.forEach(function(movie){ alert(this.name + ' has acted in ' + movie); }.bind(this)) } }; Actor.showMovies();
Coda Chang

14

Các chức năng mũi tên - tính năng ES6 được sử dụng rộng rãi nhất cho đến nay ...

Cách sử dụng: Tất cả các chức năng ES5 nên được thay thế bằng các chức năng mũi tên ES6 ngoại trừ trong các trường hợp sau:

Các chức năng mũi tên KHÔNG nên được sử dụng:

  1. Khi chúng ta muốn nâng chức năng
    • như các chức năng mũi tên là ẩn danh.
  2. Khi chúng ta muốn sử dụng this/ argumentstrong một chức năng
    • vì các hàm mũi tên không có this/ argumentscủa riêng chúng, chúng phụ thuộc vào bối cảnh bên ngoài của chúng.
  3. Khi chúng ta muốn sử dụng chức năng được đặt tên
    • như các chức năng mũi tên là ẩn danh.
  4. Khi chúng ta muốn sử dụng chức năng như một constructor
    • như các chức năng mũi tên không có riêng của họ this.
  5. Khi chúng ta muốn thêm chức năng như một thuộc tính trong đối tượng bằng chữ và sử dụng đối tượng trong đó
    • vì chúng ta không thể truy cập this(mà nên là chính đối tượng).

Hãy cho chúng tôi hiểu một số biến thể của các chức năng mũi tên để hiểu rõ hơn:

Biến thể 1 : Khi chúng ta muốn truyền nhiều hơn một đối số cho hàm và trả về một số giá trị từ nó.

Phiên bản ES5 :

var multiply = function (a,b) {
    return a*b;
};
console.log(multiply(5,6)); //30

Phiên bản ES6 :

var multiplyArrow = (a,b) => a*b;
console.log(multiplyArrow(5,6)); //30

Lưu ý: functiontừ khóa là không bắt buộc. =>bắt buộc. {}là tùy chọn, khi chúng tôi không cung cấp {} returnsẽ được JavaScript thêm vào và khi chúng tôi cung cấp, {}chúng tôi cần thêm returnnếu chúng tôi cần.

Biến 2 : Khi chúng ta muốn truyền CHỈ một đối số cho hàm và trả về một số giá trị từ nó.

Phiên bản ES5 :

var double = function(a) {
    return a*2;
};
console.log(double(2)); //4

Phiên bản ES6 :

var doubleArrow  = a => a*2;
console.log(doubleArrow(2)); //4

Lưu ý: Khi chỉ truyền một đối số, chúng ta có thể bỏ qua dấu ngoặc đơn ().

Biến 3 : Khi chúng ta KHÔNG muốn truyền bất kỳ đối số nào cho hàm và KHÔNG muốn trả về bất kỳ giá trị nào.

Phiên bản ES5 :

var sayHello = function() {
    console.log("Hello");
};
sayHello(); //Hello

Phiên bản ES6 :

var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); //sayHelloArrow

Biến thể 4 : Khi chúng ta muốn trả về một cách rõ ràng từ các hàm mũi tên.

Phiên bản ES6 :

var increment = x => {
  return x + 1;
};
console.log(increment(1)); //2

Biến thể 5 : Khi chúng ta muốn trả về một đối tượng từ các hàm mũi tên.

Phiên bản ES6 :

var returnObject = () => ({a:5});
console.log(returnObject());

Lưu ý: Chúng ta cần bọc đối tượng trong ngoặc đơn ()nếu không JavaScript không thể phân biệt giữa một khối và một đối tượng.

Biến 6 : Các hàm mũi tên KHÔNG có arguments(một mảng giống như đối tượng) của chúng mà chúng phụ thuộc vào bối cảnh bên ngoài arguments.

Phiên bản ES6 :

function foo() {
  var abc = i => arguments[0];
  console.log(abc(1));
};    
foo(2); // 2

Lưu ý: foolà một chức năng ES5, với một argumentsmảng như đối tượng và một cuộc tranh cãi được truyền cho nó là 2để arguments[0]cho foolà 2.

abclà một ES6 mũi tên chức năng vì nó không có nó là của riêng argumentsvì thế nó in arguments[0]của foobối cảnh bên ngoài của nó để thay thế.

Biến thể 7 : Các hàm mũi tên KHÔNG có thiscủa riêng chúng, chúng phụ thuộc vào bối cảnh bên ngoài chothis

Phiên bản ES5 :

var obj5 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" here is undefined.
        });
     }
};

obj5.greetUser("Katty"); //undefined: Katty

Lưu ý: Cuộc gọi lại được chuyển đến setTimeout là một hàm ES5 và nó có chức năng riêng thiskhông được xác định trong use-strictmôi trường do đó chúng tôi nhận được đầu ra:

undefined: Katty

Phiên bản ES6 :

var obj6 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user)); 
      // this here refers to outer context
   }
};

obj6.greetUser("Katty"); //Hi, Welcome: Katty

Lưu ý: Các callback truyền cho setTimeoutlà một ES6 mũi tên chức năng và nó không có riêng của nó thisnên phải mất nó từ đó là bối cảnh bên ngoài mà là greetUsertrong đó có thisnghĩa là obj6vì thế chúng tôi nhận được kết quả:

Hi, Welcome: Katty

Linh tinh: Chúng tôi không thể sử dụng newvới các chức năng mũi tên. Chức năng mũi tên không có prototypetài sản. Chúng tôi KHÔNG có ràng buộc thiskhi chức năng mũi tên được gọi thông qua applyhoặc call.


6

Ngoài các câu trả lời tuyệt vời cho đến nay, tôi muốn trình bày một lý do rất khác tại sao các hàm mũi tên theo một nghĩa nào đó về cơ bản tốt hơn các hàm JavaScript "thông thường". Để thảo luận, hãy tạm thời cho rằng chúng tôi sử dụng trình kiểm tra loại như "FlowScript" của Facebook. Hãy xem xét mô-đun đồ chơi sau đây, mã ECMAScript 6 hợp lệ cộng với các chú thích loại Lưu lượng: (Tôi sẽ bao gồm mã chưa được mã hóa, kết quả thực tế từ Babel, ở cuối câu trả lời này, vì vậy nó thực sự có thể được chạy.)

export class C {
  n : number;
  f1: number => number; 
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

Bây giờ hãy xem điều gì xảy ra khi chúng ta sử dụng lớp C từ một mô-đun khác, như thế này:

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

Như bạn có thể thấy, trình kiểm tra loại thất bại ở đây: f2 được cho là trả về một số, nhưng nó trả về một chuỗi!

Tồi tệ hơn, dường như không có trình kiểm tra loại có thể hiểu được nào có thể xử lý các hàm JavaScript thông thường (không phải mũi tên), bởi vì "cái này" của f2 không xuất hiện trong danh sách đối số của f2, vì vậy không thể thêm loại được yêu cầu cho "cái này" như một chú thích cho f2.

Có phải vấn đề này cũng ảnh hưởng đến những người không sử dụng trình kiểm tra loại? Tôi nghĩ vậy, bởi vì ngay cả khi chúng ta không có loại tĩnh, chúng ta vẫn nghĩ như thể chúng ở đó. .

Đây là phiên bản chưa được chạy, được sản xuất bởi Babel:

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!



3

Tôi vẫn đứng trước mọi thứ tôi đã viết trong câu trả lời đầu tiên của mình trong chủ đề này. Tuy nhiên, ý kiến ​​của tôi về phong cách mã đã phát triển kể từ đó, vì vậy tôi có một câu trả lời mới cho câu hỏi này dựa trên câu hỏi cuối cùng của tôi.

Về từ vựng this

Trong câu trả lời cuối cùng của tôi, tôi đã cố tình tránh một niềm tin tiềm ẩn mà tôi nắm giữ về ngôn ngữ này, vì nó không liên quan trực tiếp đến cuộc tranh luận mà tôi đang đưa ra. Tuy nhiên, không có điều này được tuyên bố rõ ràng, tôi có thể hiểu tại sao nhiều người chỉ đơn giản là khuyên bạn không nên sử dụng mũi tên, khi họ thấy mũi tên rất hữu ích.

Tôi tin rằng đây là: chúng ta không nên sử dụng thisở nơi đầu tiên. Do đó, nếu một người cố tình tránh sử dụng thismã của mình, thì thistính năng mũi tên từ vựng của YouTube rất ít hoặc không có giá trị. Ngoài ra, dưới tiền đề thislà một điều xấu, sự đối xử của mũi tên thisít hơn một điều tốt của người Hồi giáo; thay vào đó, nó giống như một hình thức kiểm soát thiệt hại cho một tính năng ngôn ngữ xấu khác.

Tôi cho rằng điều này không xảy ra với một số người, nhưng ngay cả với những người làm điều đó, họ phải luôn luôn thấy mình làm việc trong các cơ sở mã hóa thisxuất hiện hàng trăm lần trên mỗi tệp và tất cả (hoặc rất nhiều) kiểm soát thiệt hại là tất cả một người hợp lý có thể hy vọng. Vì vậy, mũi tên có thể tốt, theo một cách nào đó, khi chúng làm cho một tình huống xấu tốt hơn.

Ngay cả khi việc viết mã bằng thismũi tên dễ dàng hơn so với không có chúng, các quy tắc sử dụng mũi tên vẫn rất phức tạp (xem: luồng hiện tại). Do đó, các hướng dẫn không phải là rõ ràng và không nhất quán, như bạn đã yêu cầu. Ngay cả khi các lập trình viên biết về sự mơ hồ của mũi tên, tôi nghĩ họ vẫn nhún vai và chấp nhận chúng, bởi vì giá trị của từ vựng thislàm lu mờ chúng.

Tất cả điều này là một lời nói đầu cho nhận thức sau đây: nếu một người không sử dụng this, thì sự mơ hồ về thismũi tên đó thường gây ra trở nên không liên quan. Mũi tên trở nên trung lập hơn trong bối cảnh này.

Về cú pháp ngắn gọn

Khi tôi viết câu trả lời đầu tiên của mình, tôi đã có ý kiến ​​rằng ngay cả việc tuân thủ các thực tiễn tốt nhất cũng là một cái giá đáng để trả nếu điều đó có nghĩa là tôi có thể tạo ra mã hoàn hảo hơn. Nhưng cuối cùng tôi đã nhận ra rằng sự căng thẳng có thể đóng vai trò là một hình thức trừu tượng cũng có thể cải thiện chất lượng mã - đủ để biện minh cho việc đi lạc từ các thực tiễn tốt nhất đôi khi.

Nói cách khác: chết tiệt, tôi cũng muốn các chức năng một lớp lót!

Về hướng dẫn

Với khả năng của các thischức năng mũi tên -neutral và sự căng thẳng đáng để theo đuổi, tôi đưa ra hướng dẫn nhẹ nhàng hơn sau đây:

Hướng dẫn về ký hiệu chức năng trong ES6:

  • Đừng sử dụng this.
  • 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).
  • 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).

Đồng ý 100% với phần "Hướng dẫn về ký hiệu chức năng trong ES6" ở phía dưới - đặc biệt là với các chức năng gọi lại nội tuyến và trực tuyến. câu trả lời tốt đẹp
Jeff McCloud

1

Một cách đơn giản,

var a =20; function a(){this.a=10; console.log(a);} 
//20, since the context here is window.

Một trường hợp khác:

var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();

Trả lời: Bảng điều khiển sẽ in 20.

Lý do là bất cứ khi nào một hàm được thực thi ngăn xếp của chính nó được tạo ra, trong ví dụ này, exhàm được thực thi với newtoán tử để tạo bối cảnh và khi innerđược thực thi, JS sẽ tạo một ngăn xếp mới và thực thi innerhàm global contextđó mặc dù có một bối cảnh địa phương.

Vì vậy, nếu chúng ta muốn innerhàm có bối cảnh cục bộ exthì chúng ta cần liên kết bối cảnh với hàm bên trong.

Mũi tên giải quyết vấn đề này, thay vì lấy cái Global contexthọ lấy local contextnếu tồn tại bất kỳ. Trong given example,đó sẽ mất new ex()như this.

Vì vậy, trong mọi trường hợp ràng buộc là Mũi tên rõ ràng giải quyết vấn đề bằng cách mặc định.


1

Các hàm mũi tên hoặc Lambdas, đã được giới thiệu trong ES 6. Ngoài sự tao nhã trong cú pháp tối thiểu, sự khác biệt đáng chú ý nhất là phạm vi this bên trong một hàm mũi tên

Trong các biểu thức hàm thông thường , thistừ khóa được liên kết với các giá trị khác nhau dựa trên ngữ cảnh mà nó được gọi.

Trong chức năng mũi tên , thisđược giải nghĩa từ vựng ràng buộc, có nghĩa là nó đóng qua thiskhỏi phạm vi trong đó mũi tên hàm được định nghĩa (mẹ-phạm vi), và không thay đổi bất cứ đâu và làm thế nào nó được gọi / gọi.

Hạn chế Mũi tên-Chức năng như các phương thức trên Đối tượng

// this = global Window
let objA = {
 id: 10,
 name: "Simar",
 print () { // same as print: function() 
  console.log(`[${this.id} -> ${this.name}]`);
 }
}
objA.print(); // logs: [10 -> Simar]
objA = {
 id: 10,
 name: "Simar",
 print: () => {
  // closes over this lexically (global Window)
  console.log(`[${this.id} -> ${this.name}]`);
 }
};
objA.print(); // logs: [undefined -> undefined]

Trong trường hợp objA.print()khi print()phương thức được xác định bằng cách sử dụng thường xuyên function , nó hoạt động bằng cách giải quyết thisđúng cách để objAgọi phương thức nhưng không thành công khi được định nghĩa là =>hàm mũi tên . Đó là bởi vì thistrong một hàm thông thường khi được gọi như là một phương thức trên một đối tượng ( objA), là chính đối tượng đó. Tuy nhiên, trong trường hợp hàm mũi tên, thisbị ràng buộc về mặt thistừ vựng với phạm vi kèm theo nơi nó được xác định (toàn cầu / Cửa sổ trong trường hợp của chúng tôi) và giữ nguyên trong khi gọi như một phương thức objA.

Ưu điểm của hàm mũi tên so với các hàm thông thường trong (các) phương thức của đối tượng NHƯNG chỉ khi thisđược dự kiến ​​sẽ cố định & ràng buộc tại định nghĩa thời gian.

/* this = global | Window (enclosing scope) */

let objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( function() {
    // invoked async, not bound to objB
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( () => {
    // closes over bind to this from objB.print()
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [20 -> Paul]

Trong trường hợp objB.print()nơi print()phương pháp được định nghĩa là chức năng đó gọi console.log([$ {this.id} -> {this.name}] )không đồng bộ như một lời kêu gọi trở lại setTimeout , thisgiải quyết một cách chính xác để objBkhi một mũi tên hàm được sử dụng như gọi lại nhưng thất bại khi gọi lại được định nghĩa là chức năng thông thường. Đó là bởi vì =>hàm mũi tên được chuyển sang setTimeout(()=>..)đóng thistừ vựng từ cha mẹ của nó. gọi trong objB.print()đó xác định nó. Nói cách khác, =>hàm mũi tên được truyền vào để setTimeout(()==>...ràng buộc objBnhư là thisvì nó được gọi objB.print() thisobjBchính nó.

Chúng ta có thể dễ dàng sử dụng Function.prototype.bind(), để làm cho cuộc gọi lại được xác định là một chức năng thông thường, bằng cách ràng buộc nó với chính xác this.

const objB = {
 id: 20,
 name: "Singh",
 print () { // same as print: function() 
  setTimeout( (function() {
    console.log(`[${this.id} -> ${this.name}]`);
  }).bind(this), 1)
 }
}
objB.print() // logs: [20 -> Singh]

Tuy nhiên, các hàm mũi tên trở nên tiện dụng và ít xảy ra lỗi hơn trong trường hợp các cuộc gọi lại không đồng bộ nơi chúng ta biết thistại thời điểm định nghĩa các hàm mà nó nhận và nên được ràng buộc.

Giới hạn của các hàm mũi tên trong đó điều này cần thay đổi trên các lệnh

Bất cứ lúc nào, chúng tôi cần chức năng có thisthể thay đổi tại thời điểm gọi, chúng tôi không thể sử dụng các chức năng mũi tên.

/* this = global | Window (enclosing scope) */

function print() { 
   console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
 id: 10,
 name: "Simar",
 print // same as print: print
};
obj.print(); // logs: [10 -> Simar]
const obj2 = {
 id: 20,
 name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]

Không có cái nào ở trên sẽ hoạt động với chức năng mũi tên const print = () => { console.log([$ {this.id} -> {this.name}] );}thiskhông thể thay đổi và sẽ bị ràng buộc với thisphạm vi kèm theo nơi nó được xác định (toàn cầu / Cửa sổ). Trong tất cả các ví dụ này, chúng tôi đã gọi cùng một hàm với các đối tượng khác nhau ( obj1obj2) lần lượt từng đối tượng khác, cả hai đều được tạo sau khi print()hàm được khai báo.

Đây là những ví dụ giả định, nhưng hãy nghĩ về một số ví dụ thực tế hơn. Nếu chúng ta phải viết reduce()phương thức của chúng ta tương tự như phương pháp hoạt động arrays , chúng ta lại không thể định nghĩa nó là lambda, vì nó cần phải suy ra thistừ bối cảnh gọi, tức là. mảng mà nó được gọi

Vì lý do này, các constructorhàm không bao giờ có thể được định nghĩa là các hàm mũi tên, vì thisđối với hàm xây dựng không thể được đặt tại thời điểm khai báo. Mỗi khi một hàm tạo được gọi với newtừ khóa, một đối tượng mới được tạo ra sau đó bị ràng buộc với lời gọi cụ thể đó.

Ngoài ra, khi các khung hoặc hệ thống chấp nhận (các) chức năng gọi lại sẽ được gọi sau với ngữ cảnh động this , chúng ta không thể sử dụng các hàm mũi tên vì một lần nữa thiscó thể cần phải thay đổi với mỗi lần gọi. Tình huống này thường xảy ra với các trình xử lý sự kiện DOM

'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});

Đây cũng là lý do tại sao trong các khung như Angular 2+Vue.js mong đợi các phương thức liên kết thành phần mẫu là hàm / phương thức thông thường thisvì việc gọi của chúng được quản lý bởi các khung cho các hàm liên kết. (Angular sử dụng Zone.js để quản lý bối cảnh không đồng bộ cho các hàm liên kết của khung nhìn mẫu).

Mặt khác, trong React , khi chúng ta muốn truyền phương thức của một thành phần làm trình xử lý sự kiện, ví dụ, <input onChange={this.handleOnchange} />chúng ta nên định nghĩa handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}là một hàm mũi tên như đối với mọi lời gọi, chúng ta muốn đây là cùng một thể hiện của thành phần tạo ra JSX để hiển thị Phần tử DOM.


Bài viết này cũng có sẵn trên ấn phẩm Trung bình của tôi . Nếu bạn thích tính nghệ thuật, hoặc có bất kỳ nhận xét và đề xuất nào, vui lòng vỗ tay hoặc để lại nhận xét trên Medium .

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.