Статьи

Это официально! Толстые Стрелы в JavaScript!

Это официально! Мы получаем новый синтаксис функции! Группа TC39 (панель, отвечающая за поставку ES 6) достигла консенсуса по сокращенному синтаксису для выражений функций JavaScript. Он широко известен как синтаксис жирной стрелки и основан на аналогичной конструкции, найденной в CoffeeScript.

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

BS Alert

Прежде чем начать, я должен сообщить вам, что я собираюсь сделать много утверждений о том, как работают толстые стрелки. Я достаточно уверен, что большинство из них соответствуют последнему предложению, но поскольку исследовательский материал скуден (я полагаюсь на ES Wiki и список обсуждений ES), а примеры не поддаются тестированию (компилятор traceur еще не поддержите жирные стрелки) там будут некоторые ошибки, за которые я заранее прошу прощения. Я приветствую исправления и буду обновлять контент по мере их поступления. Благодаря!

Как это работает?

Синтаксис

Грамматика жирной стрелки имеет следующие характеристики:
1. Стрелка (=>) занимает место ключевого слова функции
2. Параметры указываются перед стрелкой, круглые скобки требуются при наличии нуля, двух или более параметров.
3. Синтаксис блока (т. Е. Заключение тела функции в фигурные скобки) является необязательным, если тело содержит одно выражение, в противном случае это требуется.
4. Ключевое слово return подразумевается, когда тело функции состоит из одного выражения. Во всех других случаях возврат должен использоваться явно.

Вот несколько простых примеров. Я связал каждый вариант использования жирной стрелки с соответствующим длинным синтаксисом, хотя, как мы увидим позже, парные функции не обязательно представляют идентичное поведение. Для удобства я определяю переменные с помощью ключевого слова var, но к тому времени, когда ES6 будет реализован, вы с большей вероятностью будете использовать let, которая позволяет определять переменные с помощью блока видимости:

//empty function
var fat1 = () => {};
var long1 = function() {};

//return the square
var fat2 = x => x * x;
var long2 = function(x) {return x * x};

//add two numbers
var fat3 = (a, b) => a + b;
var long3 = function(a, b) {return a + b};

//return square root if x is a number, otherwise return x 
var fat4 = x => (typeof x == "number") ? Math.sqrt(x) : x;
var long4 = function(x) {
  return (typeof x == "number") ? Math.sqrt(x) : x;
};

Жирные стрелки привносят элегантность в функциональный JavaScript…

//return a new array containing the squares of the original...
[1, 2, 3, 4, 5].map(x => x * x); //[1, 4, 9, 16, 25]

//capitalize...
['caption', 'select', 'cite', 'article'].map(word => word.toUpperCase()); 
//['CAPTION', 'SELECT', 'CITE', 'ARTICLE']

//rewrite all instances of Fahrenheit as Celsius...
function f2c(x) {
  var test = /(\d+(\.\d*)?)F\b/g;
  return x.replace(test, (str, val) => (val-32)*5/9 + "C");
}
f2c("Store between 50F and 77F"); //"Store between 10C and 25C"

(Последний пример переписывает эту традиционную реализацию).

Никаких дополнений для тебя, толстая стрела

Мало того, что толстые стрелки используют легкий синтаксис — они также генерируют легкие функции …

нет конструкторов

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

без аргументов

Объект arguments не доступен в контексте выполнения функции жирной стрелки. Это не огромная потеря; к тому времени, когда ES 6 будет в полном разгаре, мы можем ожидать, что аргументы устареют в пользу остального (…) синтаксиса.

без имен

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

Ценность этого

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

//with long-form inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var longInner = function() {
      console.log(this); //this is global object
    };
    longInner(); 
  }
}

myObj.longOuter();

//with fat arrow inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var fatInner = () => 
      console.log(this); //this is myObj
    fatInner(); 
  }
}

myObj.longOuter();

Это жесткое связывание, которое означает, что если жирная стрелка используется для определения метода в литерале объекта, он будет продолжать привязываться к этому объекту, даже когда вызывается из объекта-заемщика:

var myObj = {
  myMethod: function() {return () => this;},
  toString: () => "myObj" 
}

var yourThievingObject = {
  hoard: myObj.myMethod,
  toString: () => "yourThievingObject"
};

yourThievingObject.hoard(); //"myObj"

Точно так же значение this функции толстой стрелки нельзя изменить с помощью вызова или применения:

//traditional long inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var longInner = function() {
      console.log(this); //this is now myOtherObj
    }
    longInner.call(myOtherObj); 
  }
}

myOtherObj = {};
myObj.longOuter();

//new fat inner function
var myObj = {
  longOuter: function() {
    console.log(this); //this is myObj
    var fatInner = () => 
      console.log(this); //this is still myObj
    fatInner.call(myOtherObj); 
  }
}

myOtherObj = {};
myObj.longOuter();

Так в чем проблема?

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

Итак … помните, как существует пять способов определить значение this в функции? …

Синтаксис вызова функции Значение этого
1. Вызов метода:
myObject.foo ();
MyObject
2. Безосновательный вызов функции:
foo ();
глобальный объект (например, окно)
(не определено в строгом режиме)
3. Используя call:
foo.call (context, myArg);
контекст
4. Используя apply:
foo.apply (context, [myArgs]);
контекст
5. Конструктор с новым:
var newFoo = new Foo ();
новый экземпляр
(например, newFoo)

… ну теперь есть шестой

Синтаксис вызова функции Значение этого
6. Жирная стрелка:
(x => x * x) ();
это лексического родителя

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

Я ценю обоснование лексического связывания. Это интуитивно понятно, и если бы JavaScript начинал сначала, это не было бы плохим способом сделать это. Но в этот момент я влюбился в динамические ценности; они делают функции великолепно гибкими и являются отличным дополнением к функциональным шаблонам, в которых функции образуют основу данных, а другие объекты являются просто взаимозаменяемыми.

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

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

Это правда, что Function.prototype.bind уже предлагает форму жесткой привязки, но делает это явно; с другой стороны, жесткое связывание жирной стрелки — это побочный эффект, и совсем не очевидно, что он сломает код следующим образом:

function mixin(obj, fn) {
  fn.call(obj);
}

//long form function mixin is dynamically bound
var withCircleUtilsLong = function() {
  this.area = function() {return this.radius * this.radius * Math.PI};
  this.diameter = function() {return this.radius + this.radius};
}

//fat arrow function mixin is lexically bound (to global object in this case)
var withCircleUtilsFat = () => {
  this.area = function() {return this.radius * this.radius * Math.PI};
  this.diameter = function() {return this.radius + this.radius};
}

var CircularThing = function(r) {this.radius = r};

//utils get added to CircularThing.prototype
mixin(CircularThing.prototype, withCircleUtilsLong); 
(new CircularThing(1)).area(); //3.14

//utils get added to global object
mixin(CircularThing.prototype, withCircleUtilsFat); 
(new CircularThing(1)).area(); //area is undefined

Как это исправить

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

1) (Это легко). Пусть толстые функции стрелок определяют это так же, как любое обычное выражение функции — то есть в соответствии с пятью правилами в таблице выше. Стоит отметить, что CoffeeScript определил жирную стрелку как альтернативу их синтаксису тонкой стрелки (->). Тонкая стрелка в CoffeeScript ведет себя в основном так же, как обычные выражения функций JavaScript. Напротив, жирная стрелка ES6 пытается сделать по крайней мере две вещи одновременно — быть единственным сокращателем синтаксиса и переопределять контекстное назначение. Делать одно или другое было бы менее запутанным.

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

3) (Этот был предложен @ fb55 в списке обсуждения es ). Использовать лексическую область видимости в качестве запасного варианта, только когда не предлагается эта привязка. Другими словами, это будет принимать значение базовой ссылки в вызове метода или контекст, переданный с вызовом или apply, но будет откладываться до лексической области видимости, когда вызывается как отдельная функция. (Автономные функции могут быть единственной частью JavaScript в этом назначении, которая в любом случае нуждается в исправлении).

Заворачивать

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