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

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

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

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

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

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

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

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

7 ответов

Некоторое время назад наша команда перенесла весь свой код (приложение 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;
    }
}

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

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

Согласно предложению, стрелки направлены на "решение и устранение нескольких общих болевых точек традиционных 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) относятся к 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; }? Может быть, это потому, что он с большей вероятностью создаст одну короткую строку кода. В соответствии с 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 к переменной. Не использовать () => {},

Стрелка функции были созданы для упрощения функции 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!');

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

Функция стрелки связывает 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/

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

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

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

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

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

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

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

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

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

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

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

Что касается руководства

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

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

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

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

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

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

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

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 функция а global context хотя есть локальный контекст.

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

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

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

Другие вопросы по тегам