Когда я должен использовать функции Arrow в ECMAScript 6?


407

Вопрос адресован людям, которые думали о стиле кода в контексте готовящегося ECMAScript 6 (Harmony) и уже работали с языком.

С () => {}и function () {}мы получаем два очень похожих способа написания функций в ES6. В других языках лямбда-функции часто отличаются тем, что являются анонимными, но в ECMAScript любая функция может быть анонимной. Каждый из двух типов имеет уникальные домены использования (а именно, когда thisнеобходимо либо явно, либо явно не быть связанным). Между этими доменами существует огромное количество случаев, когда подойдет любая нотация.

Функции стрелок в ES6 имеют как минимум два ограничения:

  • Не работает newи не может быть использован при созданииprototype
  • Исправлена thisпривязка к области при инициализации

Помимо этих двух ограничений, функции со стрелками теоретически могут заменить обычные функции практически в любом месте. Как правильно использовать их на практике? Должны ли использоваться функции стрелок, например:

  • «везде, где они работают», т. е. везде, где функция не должна быть независимой от thisпеременной, и мы не создаем объект.
  • только «везде, где они нужны», то есть прослушиватели событий, тайм-ауты, которые должны быть связаны с определенной областью действия
  • с «короткими» функциями, но не с «длинными» функциями
  • только с функциями, которые не содержат другую функцию стрелки

То, что я ищу, - это руководство по выбору соответствующей функциональной нотации в будущей версии ECMAScript. Руководство должно быть четким, чтобы его можно было преподавать разработчикам в команде, и быть последовательным, чтобы не требовало постоянного рефакторинга назад и вперед от одной записи функции к другой.


33
Вы считаете Fixed this bound to scope at initialisationэто ограничением?
thefourtheye

12
Это преимущество, но оно также может быть ограничением, если вы планируете повторно использовать функцию вне исходного контекста. Например, при динамическом добавлении функции в класс через Object.prototype. Под «ограничением» я подразумеваю, что изменение значения this- это то, что вы можете делать с помощью обычных функций, но не с помощью функций со стрелками.
lyschoening

1
Честно говоря, я думаю, что правила стиля кодирования довольно самоуверенные. Не поймите меня неправильно, я думаю, что они важны, но нет единого руководства, подходящего для всех.
Феликс Клинг

Я не думаю, что Fixed this bound to scope at initialisationэто ограничение. :) Посмотрите на эту статью: exploringjs.com/es6/ch_arrow-functions.html
NgaNguyenDuy

3
@thefourtheye, «ограничение» здесь означает «ограничение, потому что тупой автоматический переводчик кода не может слепо заменить один другим и предположить, что все будет работать так, как ожидалось».
Пейсер

Ответы:


322

Некоторое время назад наша команда перенесла весь свой код (приложение AngularJS среднего размера) в JavaScript, скомпилированный с использованием Traceur Babel . Сейчас я использую следующее правило для функций в ES6 и более поздних версиях:

  • Используйте functionв глобальном масштабе и для Object.prototypeсвойств.
  • Используйте classдля конструкторов объектов.
  • Использовать =>везде

Зачем использовать функции стрелок почти везде?

  1. Безопасность области: Когда функции стрелок используются последовательно, все гарантированно будет использоваться так же, thisObjectкак корень. Если даже один стандартный обратный вызов функции смешивается с кучей функций стрелок, есть вероятность, что область видимости испортится.
  2. Компактность: функции со стрелками легче читать и писать. (Это может показаться самоуверенным, поэтому я приведу несколько примеров далее).
  3. Ясность: когда почти все является функцией стрелки, любой регулярный functionэлемент сразу же торчит для определения объема. Разработчик всегда может найти следующий более высокий functionоператор, чтобы увидеть, что это thisObjectтакое.

Почему всегда использовать обычные функции в глобальной области видимости или области видимости модуля?

  1. Для обозначения функции, которая не должна обращаться к thisObject.
  2. windowОбъект (глобальная область) лучше всего решать в явном виде.
  3. Многие Object.prototypeопределения живут в глобальном масштабе (думаю String.prototype.truncateи т. Д.), И, как правило, они в functionлюбом случае должны иметь тип . Последовательное использование functionв глобальной области помогает избежать ошибок.
  4. Многие функции в глобальной области видимости являются конструкторами объектов для определений классов старого стиля.
  5. Функции могут быть названы 1 . Это имеет два преимущества: (1) Писать менее неудобно, function foo(){}чем, const foo = () => {}в частности, вне вызовов других функций. (2) Имя функции отображается в следах стека. Хотя было бы утомительно называть каждый внутренний обратный вызов, возможно, хорошей идеей будет присвоение имен всем открытым функциям.
  6. Объявления функций поднимаются (это означает, что к ним можно получить доступ до их объявления), что является полезным атрибутом в статической служебной функции.


Конструкторы объектов

Попытка создания экземпляра функции стрелки вызывает исключение:

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

Таким образом, одно ключевое преимущество функций над функциями стрелок состоит в том, что функции удваиваются как конструкторы объектов:

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

Тем не менее, функционально идентичное 2 черновое определение класса ES Harmony почти такое же компактное:

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

Я ожидаю, что использование прежней нотации в конечном итоге будет препятствовать. Нотация конструктора объекта все еще может использоваться некоторыми для простых анонимных объектных фабрик, где объекты генерируются программно, но не для чего-то еще.

Там, где нужен конструктор объекта, следует рассмотреть возможность преобразования функции в a, classкак показано выше. Синтаксис работает также с анонимными функциями / классами.


Читаемость функций стрелок

Вероятно, лучший аргумент для того, чтобы придерживаться обычных функций - будь то проклята безопасность области действия - это то, что функции стрелок менее читаемы, чем обычные функции. Если ваш код изначально не функционален, то функции стрелок могут показаться ненужными, а когда функции стрелок не используются последовательно, они выглядят некрасиво.

ECMAScript сильно изменился с тех пор, как ECMAScript 5.1 дал нам функционал Array.forEach, Array.mapи все эти функциональные возможности программирования, которые заставляют нас использовать функции, в которых циклы for использовались бы раньше. Асинхронный JavaScript взлетел совсем немного. ES6 также отправит Promiseобъект, что означает еще больше анонимных функций. Нет возврата назад к функциональному программированию. В функциональном JavaScript функции со стрелками предпочтительнее обычных функций.

Возьмем, к примеру, этот (особенно запутанный) фрагмент кода 3 :

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;
        })
}

Тот же кусок кода с обычными функциями:

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));
}

Хотя любая из функций стрелок может быть заменена стандартной функцией, выигрыш от этого будет очень небольшим. Какая версия более читабельна? Я бы сказал, первый.

Я думаю, что вопрос, использовать ли функции со стрелками или обычные функции, со временем станет менее актуальным. Большинство функций либо станут методами класса, которые убирают functionключевое слово, либо они станут классами. Функции будут использоваться для исправления классов через Object.prototype. В то же время я предлагаю зарезервировать functionключевое слово для всего, что действительно должно быть методом класса или классом.


Ноты

  1. Функции именованных стрелок были отложены в спецификации ES6 . Они все еще могут быть добавлены в будущей версии.
  2. Согласно проекту спецификации "Объявления / выражения класса создают пару конструктор функция / прототип точно так же, как для объявлений функций", пока класс не использует extendключевое слово. Небольшое отличие состоит в том, что объявления классов являются константами, а объявления функций - нет.
  3. Обратите внимание на блоки в функциях стрелок с одним оператором: мне нравится использовать блок везде, где вызывается функция стрелки только для побочного эффекта (например, присваивания). Таким образом, ясно, что возвращаемое значение может быть отброшено.

4
Другой раз, когда вы захотите использовать functionэто когда вы не хотите thisбыть связанными, верно? Мой наиболее распространенный сценарий для этого - события, когда вы можете thisссылаться на объект (обычно узел DOM), который вызвал событие.
Бретт

13
Я действительно думаю, что в примере 3 регулярные функции более читабельны. Даже непрограммисты могут предугадать, что происходит. С помощью стрелок вам нужно точно знать, как они работают, чтобы понять этот пример. Может быть, больше символов новой строки поможет стрелке, но я не знаю. Только мои 2 цента, но стрелки заставляют меня съеживаться (но я еще не использовал их, так что я могу скоро быть обращенным.)
Спенсер

3
@ Спенсер, это справедливо. Исходя из моего собственного опыта, в =>конечном итоге выглядит лучше со временем. Я сомневаюсь, что непрограммисты будут по-разному относиться к этим двум примерам. Если вы пишете код ES2016, вы, как правило, не собираетесь использовать множество функций стрелок. В этом примере, используя async / await и понимание массива, вы получите только одну функцию стрелки в reduce()вызове.
Лизчоинг

3
Я полностью согласен со Спенсером в том, что в этом примере обычные функции гораздо более читабельны.
Jonschlinkert

2
Хороший ответ, спасибо! лично я также использую стрелки в глобальном масштабе как можно больше. Это оставляет меня почти без «функции». Для меня «функция» в коде означает особый случай, который нужно выделять и тщательно продумывать.
Кофифус

81

Согласно предложению , стрелки направлены на «устранение и устранение нескольких общих болевых точек традиционных Function Expression». Они намеревались улучшить положение дел, связывая его thisлексически и предлагая краткий синтаксис.

Однако,

  • Нельзя последовательно связывать thisлексически
  • Синтаксис функции стрелки деликатный и неоднозначный

Поэтому функции стрелок создают возможности для путаницы и ошибок и должны быть исключены из словаря программиста JavaScript, заменены functionисключительно на.

Что касается лексического this

this проблематично:

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

Функции со стрелками предназначены для решения проблемы, когда нам нужно получить доступ к свойству thisвнутри обратного вызова. Уже есть несколько способов сделать это: можно присвоить thisпеременную, использовать bindили использовать третий аргумент, доступный в Arrayагрегатных методах. Тем не менее, стрелки, кажется, самый простой обходной путь, поэтому метод можно изменить следующим образом:

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

Однако, подумайте, не использует ли код такую ​​библиотеку, как jQuery, чьи методы связываются thisспециально. Теперь thisнужно разобраться с двумя значениями:

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

Мы должны использовать functionдля того, eachчтобы связывать thisдинамически. Мы не можем использовать функцию стрелки здесь.

Работа с несколькими thisзначениями также может сбивать с толку, потому что трудно понять, о чем thisговорил автор:

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

Автор действительно намеревался позвонить Book.prototype.reformat? Или он забыл связать thisи намеревался позвонить Reader.prototype.reformat? Если мы изменим обработчик на функцию со стрелкой, мы также будем интересоваться, хочет ли автор динамику this, но выбрал стрелку, потому что она помещается в одну строку:

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

Можно задаться вопросом: «Исключительно ли то, что стрелки иногда могут быть неправильной функцией для использования? Может быть, если нам редко нужны динамические thisзначения, тогда все равно будет нормально использовать стрелки большую часть времени».

Но спросите себя: «Стоит ли« отлаживать »код и обнаруживать, что результат ошибки вызван« крайним случаем »?» Я предпочел бы избегать проблем не только большую часть времени, но и 100% времени.

Есть лучший способ: всегда использовать function(поэтому thisвсегда можно динамически связывать) и всегда ссылаться thisчерез переменную. Переменные являются лексическими и предполагают много имен. Присвоение thisпеременной прояснит ваши намерения:

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

Кроме того, всегда присваивание thisпеременной (даже если есть одна thisили нет других функций) гарантирует, что его намерения остаются ясными даже после изменения кода.

Кроме того, динамика thisвряд ли является исключительной. jQuery используется на более чем 50 миллионах веб-сайтов (на момент написания статьи в феврале 2016 года). Вот другие API, привязывающиеся thisдинамически:

  • Mocha (~ 120k загрузок вчера) предоставляет методы для своих тестов через this.
  • Grunt (~ 63k загрузок вчера) предоставляет методы для построения задач через this.
  • Backbone (~ 22 тыс. Загрузок вчера) определяет методы доступа this.
  • API событий (например, DOM) ссылаются на EventTargetwith this.
  • Прототипные API, которые исправлены или расширены, ссылаются на экземпляры с this.

(Статистика через http://trends.builtwith.com/javascript/jQuery и https://www.npmjs.com .)

Вероятно, вам уже потребуются динамические thisпривязки.

thisИногда ожидается лексическое , а иногда нет; так же, как thisиногда ожидается динамика , но иногда нет. К счастью, есть лучший способ, который всегда производит и сообщает ожидаемую привязку.

Относительно краткого синтаксиса

Стрелкам функции удалось обеспечить «более короткую синтаксическую форму» для функций. Но сделают ли эти короткие функции вас более успешными?

Является ли x => x * x«легче читать» чем function (x) { return x * x; }? Может быть, потому что это более вероятно, чтобы создать одну короткую строку кода. В соответствии с Dyson's Влияние скорости чтения и длины строки на эффективность чтения с экрана ,

Средняя длина строки (55 символов в строке) обеспечивает эффективное чтение с нормальной и высокой скоростью. Это дало высочайший уровень понимания. , ,

Аналогичные обоснования сделаны для условного (троичного) оператора и для однострочных ifоператоров.

Однако действительно ли вы пишете простые математические функции, рекламируемые в предложении ? Мои домены не математические, поэтому мои подпрограммы редко бывают такими элегантными. Скорее, я обычно вижу, что функции стрелок нарушают ограничение столбца и переносятся на другую строку из-за редактора или руководства по стилю, которое сводит на нет «читабельность» по определению Дайсона.

Кто-то может сказать: «Как насчет использования короткой версии для коротких функций, когда это возможно?» Но теперь стилистическое правило противоречит языковым ограничениям: «Старайтесь использовать самую короткую запись из возможных функций, имея в виду, что иногда только самая длинная запись будет связываться, thisкак ожидается». Такое смешение делает стрелки особенно склонными к неправильному использованию.

Есть много проблем с синтаксисом функции стрелки:

const a = x =>
    doSomething(x);

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

Обе эти функции синтаксически допустимы. Но doSomethingElse(x);не в теле b, это просто плохо выраженное заявление на высшем уровне.

При расширении до блочной формы больше не существует неявного return, которое можно забыть восстановить. Но выражение может только было предназначено для получения побочного эффекта, так что кто знает , если явно returnнеобходимо будет идти вперед?

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);
};

То, что может быть задано как параметр rest, может быть проанализировано как оператор распространения:

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

Назначение может быть перепутано с параметрами по умолчанию:

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

Блоки выглядят как объекты:

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

Что это значит?

() => {}

Намерен ли автор создать неоперативную функцию или функцию, которая возвращает пустой объект? (Имея это в виду, должны ли мы когда-либо ставить {после =>? Должны ли мы ограничиться только синтаксисом выражения? Это еще больше уменьшит частоту стрелок.)

=>выглядит <=и >=:

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

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

Чтобы немедленно вызвать выражение функции стрелки, нужно поместить ()снаружи, но размещение ()внутри является допустимым и может быть преднамеренным.

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

Хотя, если кто-то пишет (() => doSomething()());с намерением написать выражение для немедленного вызова функции, просто ничего не произойдет.

Трудно утверждать, что функции стрелок «более понятны» с учетом всех вышеперечисленных случаев. Один мог бы узнать все специальные правила , необходимые для использования этого синтаксиса. Это действительно того стоит?

Синтаксис functionбезоговорочно обобщен. Использовать functionисключительно означает, что сам язык не позволяет писать запутанный код. Чтобы написать процедуры, которые должны быть синтаксически понятны во всех случаях, я выбираю function.

Относительно руководства

Вы запрашиваете руководство, которое должно быть «четким» и «последовательным». Использование функций стрелок в конечном итоге приведет к синтаксически правильному, логически неверному коду, причем обе функциональные формы переплетены, осмысленно и произвольно. Поэтому я предлагаю следующее:

Руководство по обозначению функций в ES6:

  • Всегда создавайте процедуры с function.
  • Всегда присваивайте thisпеременной. Не используйте () => {}.

5
Интересно написать на функциональный взгляд программиста на JavaScript. Я не уверен, что согласен с аргументом частных переменных. ИМО мало кому они действительно нужны; тем, кому это нужно, вероятно, понадобятся и другие возможности контракта, и в любом случае они пойдут на расширение языка, например TypeScript Я, конечно, вижу привлекательность selfвместо этого. Ваши заявленные ошибки функции стрелки также все действительны, и те же стандарты, что и для других операторов, которые могут идти без скобок, определенно применимы и здесь; в противном случае, я думаю, что с вашим аргументом можно с таким же успехом отстаивать функции стрелок везде
расправа

7
«Наличие нескольких способов ведения дел создает ненужные векторы для споров и разногласий на рабочем месте и в языковом сообществе. Было бы лучше, если бы языковая грамматика не позволяла нам делать неправильный выбор». Согласитесь так много. Хорошая рецензия! Я думаю, что функции стрелок на самом деле шаг назад. По другой теме, я бы хотел, чтобы мои коллеги перестали пытаться превратить JavaScript в C # с помощью серии определений .prototype. Это отвратительно. Я должен анонимно связать ваш пост :)
Спенсер

11
Очень хорошо написано! Хотя я не согласен с большинством ваших точек зрения, важно учитывать противоположную точку зрения.
Minexew

4
thisПроблема не в стрелках, а в странном поведении - это проблема Javascript. Вместо того, чтобы быть неявно связанным, thisдолжен быть передан как явный аргумент.
Боб

5
Msgstr " Всегда использовать функцию (так что это всегда может быть динамически связано), и всегда ссылаться на это через переменную. " Я не мог не согласиться больше!

48

Функции стрелок были созданы, чтобы упростить функцию scopeи решить thisключевое слово, сделав его более простым. Они используют =>синтаксис, который выглядит как стрелка.

Примечание: он не заменяет существующие функции. Если вы замените каждый синтаксис функции на функции стрелок, он не будет работать во всех случаях.

Давайте посмотрим на существующий синтаксис ES5. Если бы thisключевое слово было внутри метода объекта (функции, которая принадлежит объекту), на что бы оно ссылалось?

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

Приведенный выше фрагмент ссылается на objectи выводит имя "RajiniKanth". Давайте рассмотрим приведенный ниже фрагмент кода и посмотрим, на что он здесь указывает.

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

Actor.showMovies();

А что, если thisключевое слово было внутри method’s function?

Здесь это будет относиться к window objectтому, inner functionкак оно выпало scope. Потому что thisвсегда ссылается на владельца функции, в которой он находится, для этого случая - поскольку теперь он находится вне области видимости - окна / глобального объекта.

Когда он находится внутри objectметода - functionвладельцем является объект. Таким образом, ключевое слово this связано с объектом. Тем не менее, когда он находится внутри функции, отдельно или внутри другого метода, он всегда будет ссылаться на window/globalобъект.

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

fn(); // [object Window]

Есть способы решить эту проблему ES5самим, давайте рассмотрим это, прежде чем углубляться в функции стрелок ES6, чтобы узнать, как ее решить.

Как правило, вы создаете переменную вне внутренней функции метода. Теперь ‘forEach’получает метод доступа к thisи , таким образом, object’sсвойства и их значения.

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();

используется bindдля прикрепления thisключевого слова, которое ссылается на метод к 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();

Теперь с помощью ES6функции стрелки мы можем решить lexical scopingпроблему более простым способом.

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

Actor.showMovies();

Arrow functionsбольше похожи на операторы функций, за исключением того, что они bindэто к parent scope. Если arrow function is in top scope, thisаргумент будет ссылаться window/global scope, в то время как стрелка функция внутри обычной функции будет иметь этот аргумент так же , как его внешняя функция.

С arrowфункциями thisсвязан с вложением scopeво время создания и не может быть изменен. Новый оператор bind, call и apply не влияет на это.

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

В приведенном выше примере мы потеряли контроль над этим. Мы можем решить приведенный выше пример, используя ссылку на переменную thisили используя bind. С ES6 становится легче управлять, thisчем связано 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

Когда не стрелять функции

Внутри объект буквальный.

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

Actor.getName();

Actor.getNameопределяются с помощью функции стрелки, но при вызове он предупреждает неопределенное , потому что this.nameэто , undefinedкак контекст остается window.

Это происходит потому, что функция стрелки связывает контекст лексически с window object... т.е. внешней областью видимости. Выполнение this.nameэквивалентно window.name, что не определено.

Прототип объекта

То же правило применяется при определении методов для prototype object. Вместо использования функции стрелки для определения метода sayCatName, который приводит к неверному 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

Вызов конструкторов

thisв вызове конструкции находится вновь созданный объект. При выполнении нового Fn (), контекст constructor Fnпредставляет собой новый объект: this instanceof Fn === true.

this это настройка из окружающего контекста, то есть внешней области, которая делает его не назначенным вновь созданному объекту.

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

Обратный вызов с динамическим контекстом

Функция Arrow связывает contextстатически при объявлении и не позволяет сделать его динамическим. Присоединение слушателей событий к элементам DOM является обычной задачей в программировании на стороне клиента. Событие запускает функцию-обработчик с этим в качестве целевого элемента.

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

thisэто окно в функции стрелки, которая определена в глобальном контексте. Когда происходит событие щелчка, браузер пытается вызвать функцию обработчика с помощью контекста кнопки, но функция стрелки не изменяет свой предварительно определенный контекст. this.innerHTMLэквивалентно window.innerHTMLи не имеет смысла.

Вы должны применить выражение функции, которое позволяет изменить это в зависимости от целевого элемента:

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

Когда пользователь нажимает кнопку, в функции обработчика есть кнопка. Таким образом this.innerHTML = 'Clicked button'корректно изменяет текст кнопки, чтобы отразить нажатый статус.

Ссылки: https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/


Ну, я должен признать, что «лучшее лежит в середине» . Подтверждено утверждение, что функции стрелок не будут охватывать любые возможные варианты использования функций. Они действительно предназначены для решения только части общих проблем. Просто переключиться на них полностью будет излишним.
BlitZ

@DmitriPavlutin: Проверьте мой обновленный пост, это коллекция многих вещей ... может быть, я должен опубликовать ссылку.
Thalaivar

2
Ваш код после строки «с помощью bind присоединяет ключевое слово this, которое ссылается на метод, к внутренней функции метода». есть ошибки в нем. Вы проверили остальные ваши примеры?
Исаак Пак

У одного using bind to attach the this keyword that refers to the method to the method’s inner function.есть синтаксические ошибки.
Coda Chang

Должно быть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

Функции стрелок - наиболее широко используемая функция ES6 до сих пор ...

Использование: Все функции ES5 должны быть заменены на функции стрелок ES6, за исключением следующих случаев:

Функции стрелок НЕ должны использоваться:

  1. Когда мы хотим поднять функцию
    • так как функции стрелок являются анонимными.
  2. Когда мы хотим использовать this/ argumentsв функции
    • а стрелка функция не имеет this/ argumentsих собственные, они зависят от их внешнего контекста.
  3. Когда мы хотим использовать именованную функцию
    • так как функции стрелок являются анонимными.
  4. Когда мы хотим использовать функцию как constructor
    • так как функции стрелок не имеют своих this.
  5. Когда мы хотим добавить функцию как свойство в литерал объекта и использовать объект в нем
    • как мы не можем получить доступ this(который должен быть сам объект).

Давайте разберемся с некоторыми вариантами функций стрелок, чтобы лучше понять:

Вариант 1 : когда мы хотим передать более одного аргумента в функцию и вернуть ей некоторое значение.

Версия ES5 :

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

Версия ES6 :

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

Примечание: functionключевое слово не требуется. =>необходимо. {}являются необязательными, когда мы не предоставляем {} return, неявно добавляется JavaScript, а когда мы предоставляем, {}мы должны добавить, returnесли нам это нужно.

Вариант 2 : Когда мы хотим передать ТОЛЬКО один аргумент в функцию и вернуть ей некоторое значение.

Версия ES5 :

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

Версия ES6 :

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

Примечание: при передаче только одного аргумента мы можем опустить круглые скобки ().

Вариант 3 : Когда мы НЕ хотим передавать какой-либо аргумент в функцию и НЕ хотим возвращать какое-либо значение.

Версия ES5 :

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

Версия ES6 :

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

Вариант 4 : когда мы хотим явно вернуться из функций стрелок.

Версия ES6 :

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

Вариант 5 : Когда мы хотим вернуть объект из функций стрелок.

Версия ES6 :

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

Примечание. Нам нужно заключить объект в круглые скобки, ()иначе JavaScript не сможет различить блок и объект.

Вариант 6 : функции-стрелки НЕ имеют arguments(массива, подобного объекту) своих собственных функций, для которых они зависят от внешнего контекста arguments.

Версия ES6 :

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

Примечание: fooэто функция ES5, с argumentsмассивом, подобным объекту, и передаваемому ей аргументу, 2так что arguments[0]для foo2.

abcявляется ES6 стрелки функции , так как она не имеет его собственного , argumentsследовательно , она выводит arguments[0]из fooэтого внешнего контекста вместо этого.

Вариант 7 : функции стрелок не имеют thisсвоих собственных, они зависят от внешнего контекста дляthis

Версия 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

Примечание: обратный вызов, переданный setTimeout, является функцией ES5 и имеет свою собственную функцию, thisкоторая не определена в use-strictсреде, поэтому мы получаем вывод:

undefined: Katty

Версия 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

Примечание: переданный обратный вызов setTimeoutявляется функцией стрелки ES6, и он НЕ имеет своей собственной функции, thisпоэтому он берет ее из внешнего контекста, greetUserкоторый имеет thisто, что, obj6следовательно, мы получаем вывод:

Hi, Welcome: Katty

Разное: Мы не можем использовать newс функциями стрелок. Функции стрелок не имеют prototypeсвойства. У нас нет привязки, thisкогда функция стрелки вызывается через applyили call.


6

В дополнение к отличным ответам, я хотел бы представить совершенно другую причину, по которой функции стрелок в определенном смысле существенно лучше, чем «обычные» функции JavaScript. Для обсуждения давайте временно предположим, что мы используем средство проверки типов, например TypeScript или Facebook «Flow». Рассмотрим следующий игрушечный модуль, который является допустимым кодом ECMAScript 6 плюс аннотации типа Flow: (в конце этого ответа я включу нетипизированный код, который будет реально получен из Babel, чтобы его можно было запустить).

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;};
  }
}

Теперь посмотрим, что происходит, когда мы используем класс C из другого модуля, например так:

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!

Как вы можете видеть, проверка типов здесь не удалась : f2 должен был вернуть число, но вернул строку!

Хуже того, кажется, что никакая мыслимая программа проверки типов не может обрабатывать обычные (не стрелки) функции JavaScript, потому что «this» из f2 не встречается в списке аргументов f2, поэтому требуемый тип для «this» не может быть добавлен в качестве аннотации к f2.

Эта проблема также влияет на людей, которые не используют проверки типов? Я так думаю, потому что даже когда у нас нет статических типов, мы думаем, что они есть. («Первый параметр должен быть числом, второй - строкой» и т. Д.) Скрытый аргумент «это», который может или не может использоваться в теле функции, делает нашу умственную бухгалтерию более сложной.

Вот работоспособная нетипизированная версия, которая будет выпущена Бабелем:

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!


4

Я все еще поддерживаю все, что я написал в своем первом ответе в этой теме. Однако мое мнение о стиле кода с тех пор изменилось, поэтому у меня есть новый ответ на этот вопрос, который основывается на моем последнем.

Что касается лексического this

В своем последнем ответе я сознательно отказался от основополагающего убеждения, которое я придерживаюсь об этом языке, поскольку оно не было напрямую связано с аргументом, который я приводил. Тем не менее, без явного указания, я могу понять, почему многие люди просто отказываются от моей рекомендации не использовать стрелки, когда они находят стрелки такими полезными.

Мое убеждение заключается в следующем: мы не должны использовать thisв первую очередь. Поэтому, если человек намеренно избегает использования thisв своем коде, то «лексическая this» особенность стрелок мало что значит. Кроме того, если исходить из того, что thisэто плохо, то, что стрелка относится к ним, this- это не «хорошая вещь»; вместо этого это скорее форма контроля ущерба для другой плохой языковой функции.

Я полагаю, что это либо не случается с некоторыми людьми, но даже с теми, кому это случается, они должны неизменно обнаруживать себя работающими в кодовых базах, где thisпо сто раз появляется в файле, и все (или много) контроль за ущербом - все разумный человек может надеяться. Таким образом, стрелки могут быть хорошими, в некотором смысле, когда они улучшают плохую ситуацию.

Даже если написать код с thisпомощью стрелок легче, чем без них, правила использования стрелок остаются очень сложными (см .: текущий поток). Таким образом, рекомендации не являются ни «четкими», ни «последовательными», как вы просили. Даже если программисты знают о неясностях стрелок, я думаю, что они пожимают плечами и принимают их в любом случае, потому что значение лексического thisзатмевает их.

Все это предисловие к следующему пониманию: если не использовать this, то двусмысленность thisэтих стрелок обычно становится неактуальной. Стрелки становятся более нейтральными в этом контексте.

Относительно краткого синтаксиса

Когда я писал свой первый ответ, я придерживался мнения, что даже рабская приверженность передовым методам была достойной ценой, если бы я мог создавать более совершенный код. Но в конце концов я осознал, что краткость может служить формой абстракции, которая также может улучшить качество кода - этого достаточно, чтобы иногда оправдывать отклонение от передового опыта.

Другими словами: черт, я тоже хочу однострочные функции!

Относительно руководства

С учетом возможности использования thisнейтральных стрелок и краткости, я предлагаю следующее более мягкое руководство:

Руководство по обозначению функций в ES6:

  • Не используйте this.
  • Используйте объявления функций для функций, которые вы вызываете по имени (потому что они подняты).
  • Используйте функции стрелок для обратных вызовов (потому что они имеют тенденцию быть более краткими).

Согласитесь на 100% с вашим «Руководством по обозначению функций в ES6» в нижней части - особенно с функциями подъема и встроенного обратного вызова. хороший ответ!
Джефф МакКлауд


1

Проще говоря,

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

Другой пример:

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();

Ответ: Консоль будет печатать 20.

Причина в том, что всякий раз, когда функция выполняется, создается ее собственный стек, в этом примере exфункция выполняется с newоператором, поэтому создается контекст, и когда innerон выполняется, JS создает новый стек и выполняет innerфункцию a, global contextхотя и существует местный контекст.

Итак, если мы хотим, чтобы innerфункция имела локальный контекст, exто нам нужно связать контекст с внутренней функцией.

Стрелки решают эту проблему, вместо того, чтобы Global contextвзять, local contextесли они есть, если они есть. В given example,это займет , new ex()как this.

Таким образом, во всех случаях, когда привязка является явной, стрелки решают проблему по умолчанию.


1

Функции стрелки или лямбда-выражения были введены в ES 6. Помимо элегантности в минимальном синтаксисе, наиболее заметным функциональным отличием является область видимости this внутри функции стрелки

В регулярных выражениях функцийthis ключевое слово связано с различными значениями в зависимости от контекста, в котором оно вызывается.

В функции со стрелками , thisявляется лексический связанно, что означает , что закрывает более thisот объема , в котором была определена функция стрелки (родитель-объем), и не изменяется независимо от того , где и как он вызывается / называется.

Ограничения-функции-стрелки как методы объекта

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

В случае, objA.print()когда print()метод определен с использованием обычного метода function , он работал путем thisправильного разрешения objAвызова метода, но завершался неудачно, когда определялся как =>функция стрелки . Это потому, что thisв обычной функции, когда вызывается как метод для объекта ( objA), является сам объект. Однако, в случае функции со стрелкой, он thisлексически связывается с thisобластью включения, в которой она была определена (в нашем случае global / Window), и остается неизменной во время вызова в качестве метода on objA.

Преимущества функций-стрелок над обычными функциями в методе (-ах) объекта, НО только в тех случаях, когда thisожидается, что они будут фиксированы и ограничены во время определения.

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

В случае, objB.print()когда print()метод определен как функция, которая асинхронно вызывает console.log([$ {this.id} -> {this.name}] )как обратный вызов setTimeout , thisкорректно разрешается, objBкогда функция со стрелкой использовалась в качестве обратного вызова, но не работала когда обратный вызов был определен как обычная функция. Это потому, что =>функция стрелки передана setTimeout(()=>..)закрытой thisлексически от своего родителя т.е. вызов objB.print()которого определил это. Другими словами, =>функция стрелки передается в setTimeout(()==>...привязку objBкак ее, thisпотому что вызов in objB.print() thisбыл objBсам по себе.

Мы могли бы легко использовать Function.prototype.bind(), чтобы обратный вызов определялся как обычная функция, связывая ее с правильным 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]

Тем не менее, функции со стрелками полезны и менее подвержены ошибкам в случае асинхронных обратных вызовов, когда мы знаем, thisво время определения функции, с которой она получает и должна быть связана.

Ограничение функций со стрелками, где это должно меняться при каждом вызове

В любое время нам нужна функция, thisкоторую можно изменить во время вызова, мы не можем использовать функции стрелок.

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

Ничто из вышеперечисленного не будет работать с функцией стрелки const print = () => { console.log([$ {this.id} -> {this.name}], так );}как thisне может быть изменено, и останется связанным с thisобластью, в которой оно было определено (global / Window). Во всех этих примерах мы вызывали одну и ту же функцию с разными объектами ( obj1и obj2) один за другим, оба из которых были созданы после print()объявления функции.

Это были надуманные примеры, но давайте подумаем о более реальных примерах. Если бы нам пришлось написать наш reduce()метод, похожий на тот, который работает arrays , мы снова не можем определить его как лямбду, потому что он должен выводиться thisиз контекста вызова, т.е. массив, на котором он был вызван

По этой причине constructorфункции никогда не могут быть определены как функции стрелок, поскольку thisдля конструктора функции нельзя установить во время ее объявления. Каждый раз, когда функция конструктора вызывается с newключевым словом, создается новый объект, который затем связывается с этим конкретным вызовом.

Также, когда фреймворки или системы принимают функцию (и) обратного вызова для последующего вызова с динамическим контекстом this , мы не можем использовать функции стрелок, поскольку снова thisможет потребоваться изменение при каждом вызове. Эта ситуация обычно возникает с обработчиками событий 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');
});

Это также причина, по которой в таких средах, как Angular 2+ и Vue.js, ожидается, что методы привязки к компоненту шаблона будут обычными функциями / методами, поскольку thisих вызов управляется структурами для функций привязки. (Angular использует Zone.js для управления асинхронным контекстом для вызова функций привязки шаблона представления).

С другой стороны, в React , когда мы хотим передать метод компонента в качестве обработчика события, например, <input onChange={this.handleOnchange} />мы должны определить handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);}как функцию стрелки, как и для каждого вызова, мы хотим, чтобы это был тот же экземпляр компонента, который произвел JSX для визуализации. Элемент DOM.


Эта статья также доступна в моей средней публикации. Если вам нравится артиллерия, или у вас есть какие-либо комментарии и предложения, пожалуйста, хлопайте или оставляйте комментарии на Medium .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.