it-swarm.com.ru

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

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

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

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

  • Не работать с new
  • Исправлено this, привязанное к области при инициализации

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

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

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

365
lyschoening

Некоторое время назад наша команда перенесла весь свой код (приложение AngularJS среднего размера) в JavaScript, скомпилированный с использованием Traceur Бабель . Сейчас я использую следующее правило для функций в 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. Объявления функций: hoisted , (то есть доступ к ним можно получить до их объявления), что является полезным атрибутом статической служебной функции.


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

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

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

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

Там, где нужен конструктор объекта, следует рассмотреть возможность преобразования функции в 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. Обратите внимание на блоки в функциях стрелок с одним оператором: мне нравится использовать блок везде, где вызывается функция стрелки только для побочного эффекта (например, назначение). Таким образом, ясно, что возвращаемое значение может быть отброшено.
295
lyschoening

В соответствии с предложение стрелки были направлены на "устранение и устранение нескольких общих болевых точек традиционного 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 (~ 120 тыс. Загрузок вчера) предоставляет методы для своих тестов через this.
  • Grunt (~ 63 тыс. Загрузок вчера) предоставляет методы для задач сборки через this.
  • Backbone (~ 22 тыс. Загрузок вчера) определяет методы доступа к this.
  • API событий (например, DOM) ссылаются на EventTarget с this.
  • Прототипные API, которые исправлены или расширены, ссылаются на экземпляры с this.

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

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

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

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

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

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

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

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

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

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

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

const a = x =>
    doSomething(x);

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

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

При расширении до блочной формы больше не существует неявного return, который можно забыть восстановить. Но выражение only может быть предназначено для создания побочного эффекта, поэтому кто знает, понадобится ли в будущем явное 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. Не используйте () => {}.
79
Jackson

Функции со стрелками были созданы для упрощения функции 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 от this до parent scope. Если аргумент arrow function is in top scope, this будет ссылаться на window/global scope, тогда как функция стрелки внутри обычной функции будет иметь свой аргумент this таким же, как и его внешняя функция.

С функциями arrowthis привязывается к включающему 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 определяется с помощью функции стрелки, но при вызове он выдает предупреждение undefined, поскольку 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://rainsoft.io/when-not-to-use-arrow-functions-in-javascript/

32
Thalaivar

Функции стрелок - наиболее широко используемая функция 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

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

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

Версия 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] для foo равен 2.

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.

13
Manishz90

В дополнение к отличным ответам, я хотел бы представить совершенно другую причину, по которой функции стрелок в определенном смысле существенно лучше, чем "обычные" функции 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, поэтому требуемый тип для " это "не может быть добавлено в качестве аннотации к 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!
6
Carsten Führmann

Я предпочитаю использовать функции стрелок всегда, когда доступ к локальному this не нужен, потому что функция стрелок не связывайте свои собственные this, arguments, super или new.target .

3
zowers

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

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
Rajendra kumar Vankadari

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

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

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

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

// 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() определен с использованием обычного functionname__, он работал путем правильного преобразования thisв objAдля вызова метода, но завершился неудачей, когда был определен как функция arrow=>. Это связано с тем, что thisв обычной функции, когда вызывается как метод объекта (objAname__), является самим объектом. Однако в случае функции со стрелкой thisполучает лексическую привязку к thisвключающей области действия, в которой она была определена (в нашем случае global/Window), и остается такой же во время вызова в качестве метода для objAname__.

Преимущества функций со стрелками по сравнению с обычными функциями в методе (ах) объекта, НО, только когда ожидается, что 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}] )] в качестве обратного вызова setTimeoutname__, thisбыла определена правильно, если функция была возвращена в objBиспользуется как обратный вызов, но не работает, когда обратный вызов был определен как обычная функция. Это потому, что функция стрелки =>, переданная в setTimeout(()=>..), закрыта над thisлексически от своего родителя, т.е. вызов objB.print(), который его определил. Другими словами, функция стрелки => была передана в setTimeout(()==>..., связанную с objBв качестве его thisname__, потому что при вызове objB.print()thisбыл сам objBname__.

Мы могли бы легко использовать Function.prototype.bind(), чтобы обратный вызов определялся как обычная функция, связывая его с правильным thisname__.

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}] );} as thisне может быть изменено и останется связанным с thisобласти действия, в которой она была определена (глобальная/Окно). Во всех этих примерах мы вызывали одну и ту же функцию с разными объектами (obj1 и obj2) один за другим, оба из которых были созданы после объявления функции print().

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

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

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


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

1
Simar Singh

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

Относительно лексического this

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

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

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

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

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

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

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

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

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

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

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

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