Когда я должен использовать функции 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
для конструкторов объектов. - использование
=>
где-либо еще.
Зачем использовать функции стрелок почти везде?
- Безопасность области: при последовательном использовании функций стрелок все гарантированно будет использоваться одинаково
thisObject
как корень. Если даже один стандартный вызов функции смешивается с кучей функций со стрелками, есть вероятность, что область видимости испортится. - Компактность: функции со стрелками легче читать и писать. (Это может показаться самоуверенным, поэтому я приведу несколько примеров далее).
- Ясность: когда почти все является функцией стрелки, любой регулярный
function
сразу торчит для определения объема. Разработчик всегда может посмотреть на следующий вышеfunction
заявление, чтобы увидеть, чтоthisObject
является.
Почему всегда использовать обычные функции в глобальной области видимости или области видимости модуля?
- Чтобы указать функцию, которая не должна получить доступ к
thisObject
, -
window
Объект (глобальная область) лучше всего адресован явно. - Много
Object.prototype
определения живут в глобальном масштабе (думаю,String.prototype.truncate
и т.д.) и те, как правило, должны быть типаfunction
тем не мение. Последовательно используяfunction
в глобальном масштабе помогает избежать ошибок. - Многие функции в глобальной области видимости являются конструкторами объектов для определений классов старого стиля.
- Функции могут быть названы 1. Это имеет два преимущества: (1) это менее неудобно писать
function foo(){}
чемconst foo = () => {}
- в частности, за пределами других вызовов функций. (2) Имя функции отображается в следах стека. Хотя было бы утомительно называть каждый внутренний обратный вызов, возможно, хорошей идеей будет присвоение имен всем открытым функциям. - Объявления функций поднимаются (это означает, что к ним можно получить доступ до их объявления), что является полезным атрибутом в статической служебной функции.
Конструкторы объектов
Попытка создания экземпляра функции стрелки вызывает исключение:
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
ключевое слово для всего, что действительно должно быть методом класса или классом.
Заметки
- Функции именованных стрелок были отложены в спецификации ES6. Они все еще могут быть добавлены в будущей версии.
- Согласно проекту спецификации "Объявления / выражения класса создают пару конструктор функция / прототип точно так же, как и для объявлений функций", если класс не использует
extend
ключевое слово. Небольшое отличие состоит в том, что объявления классов являются константами, а объявления функций - нет. - Обратите внимание на блоки в функциях стрелок с одним оператором: мне нравится использовать блок везде, где вызывается функция стрелки только для побочного эффекта (например, присваивания). Таким образом, ясно, что возвращаемое значение может быть отброшено.
Согласно предложению, стрелки направлены на "решение и устранение нескольких общих болевых точек традиционных 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, за исключением следующих случаев:
Функции стрелок НЕ должны использоваться:
- Когда мы хотим поднять функцию
- так как функции стрелок являются анонимными.
- Когда мы хотим использовать
this
/arguments
в функции- так как стрелка функции не имеют
this
/arguments
сами по себе они зависят от своего внешнего контекста.
- так как стрелка функции не имеют
- Когда мы хотим использовать именованную функцию
- так как функции стрелок являются анонимными.
- Когда мы хотим использовать функцию как
constructor
- так как функции стрелок не имеют своих
this
,
- так как функции стрелок не имеют своих
- Когда мы хотим добавить функцию как свойство в литерал объекта и использовать объект в нем
- как мы не можем получить доступ
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
,
Таким образом, во всех случаях, когда привязка является явной, стрелки решают проблему по умолчанию.