Обзор ECMAScript 6, следующей версии JavaScript

в 17:49, , рубрики: ECMAScript, javascript, метки: ,

Для начала, ликбез и несколько фактов:

  • ECMAScript — это официальный стандарт языка JavaScript (Слово JavaScript не могло быть использовано, потому что слово Java являлось торговой маркой компании Sun) Т.е. JavaScript — это имплементация стандарта ECMAScript.
  • TC39 — комитет, развивающий стандарт ECMAScript и принимающий решения по включению фич в него.
  • ECMAScript стандартов много. Самый популярный из них — ECMA-262.
  • ECMAScript 5 — последняя версия стандарта ECMA-262 (утвержден в 2009 году).
  • Предыдущие версии стандарта ECMA-262 были:
    • ECMAScript 3 — поддерживается большинством браузеров (утвержден в 1999 году).
    • ECMAScript 4 — не принят в виду слишком радикальных изменений в стандарте. Позднее в июле 2008 году в урезанном варианте (но все же намного богаче, чем ECMAScript 3) вылился в новый проект ECMAScript Harmony.

  • ECMAScript 6 (кодовое имя ECMAScript.next) должен утвердиться до конца 2013 года.

Итак, что же нас ждет в новой версии JavaScript?

Блочная область видимости (block scope)

В текущей версии JavaScript присутствует функциональная область видимости. Это означает, что все переменные, объявленные c помощью ключевого слова var, будут видны в любом месте функции (даже если они объявлены внутри блока):

function f(a) {
   if (a < 0) {
      var i = 3;
   }

  console.log(i); // 3
}

f(-1)

В новой версии появится ключевое слово let, которое позволит объявлять переменные с блочной областью видимости:

function f(a) {
   if (a < 0) {
      let i = 3;
   }

   console.log(i); // ReferenceError: i is not defined
}

f(-1)

Значения параметров по умолчанию

В функциях добавилась возможность объявлять у параметров значения по умолчанию:

function setLevel(newLevel = 0) {
   ...
}

setLevel(); // newLevel = 0
setLevel(5); // newLevel = 5
setLevel(undefined); // newLevel = 0

Именованные параметры функций

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

function foo({ from, to = 10 }) {
   ...
}

foo({ from: 1, to: 5 });
foo({ to: 5, from: 1 });
foo({ from: 1 });

Именованные параметры можно комбинировать с обычным (позиционными параметрами):

function foo(positional, { named1, named2 }) {
   ...
}

foo(123, { named1: 'abc', named2: 'def' })
foo(123, { named2: 'def', named1: 'abc' })

Destructuring assignment

ECMAScript 6 позволит деструктуризировать при присваивании:

let { first: f, last: l } = { first: 'Jane', last: 'Doe' };
console.log(f); // 'Jane'
console.log(l); // 'Doe'

Кстати, в примере из предыдущего пункта (Именованные параметры) вы видели пример деструктуризации параметров функции.

Деструктуризация по умолчанию является refutable (не имею понятия, как это переводить). Т.е. если в объекте-источнике присваивания соответствующего поля нету, то выбрасывается ошибка:

let { first: f, last: l } = { first: 'Jane' };  // ошибка

Если же вы не хотите, чтобы ошибка генерировалась, то переменную можно объявить как irrefutable с помощью суффикса ?:

let { first: f, last?: l } = { first: 'Jane' };  // ok
console.log(l);  // undefined

Либо можно дать переменной значение по умолчанию:

let { first: f, last: l = 'Unknown' } = { first: 'Jane' };  // ok
console.log(l);  // 'Unknown'

Значение по умолчанию также срабатывает, если соответствующее поле в объекте-источнике является undefined:

let { a: x = 1 } = { a: undefined }
console.log(x);  // 1

Наконец, если вы хотите, чтобы все переменные были irrefutable, то можно поставить суффикс ? в конце всего шаблона присваивания:

let { foo: f }? = anything;  // всегда ok

В последнем примере переменная f будет инициализирована значением undefined, если anything будет равно undefined, null или не иметь поля foo.

С помощью деструктуризации можно одной строчкой кода поменять значение двух переменных (без всяких tmp):

{ foo: foo, bar: bar } = { foo: bar, bar: foo};

Или ище короче:

[ foo, bar ] = [ bar, foo ];

Классы

В ECMAScript 6 появятся классы:

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

   describe() {
      return "Person called " + this.name;
   }
}

// Subtype
class Employee extends Person {
   constructor(name, title) {
      super.constructor(name);
      this.title = title;
   }

   describe() {
      return super.describe() + " (" + this.title + ")";
   }
}

Теперь можно использовать эти классы:

let jane = new Employee("Jane", "CTO");
jane instanceof Person; // true
jane instanceof Employee; // true
jane.describe(); // 'Person called Jane (CTO)'

Всего того же можно было добиться с помощью прототипов:

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

Person.prototype.describe = function () {
   return "Person called " + this.name;
};

// Subtype
function Employee(name, title) {
   Person.call(this, name);
   this.title = title;
}

Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.describe = function () {
   return Person.prototype.describe.call(this) + " (" + this.title + ")";
};

Как видите, классы в ECMAScript 6 — это просто синтаксический сахар над конструкторами и функциями.

Классы могут иметь статические методы:

class Point {
   constructor(x, y) {
      this.x = x;
      this.y = y;
   }
   
   static zero() {
      return new Point(0, 0);
   }
}

Приватных полей и методов не будет (по крайней мере, в ECMAScript 6). Однако некоторое сокрытие данных все же появится. Через модули.

Модули

В JavaScript наконец-то появятся модули:

module Math {
   export function sum(x, y) {
      return x + y;
   }

   export var pi = 3.141593;

   // Не видна снаружи
   function internal() {
      ...
   }
}

Импортирование модуля:

import Math.{sum, pi}; 
alert("2π = " + sum(pi, pi));

Можно использовать *, чтобы импортировать всё:

import Math.*; 
alert("2π = " + sum(pi, pi));

Модули можно вкладывать друг в друга:

module Widgets {
   module Button { ... }
   module Alert { ... }
   module TextArea { ... }
   ...
}

import Widgets.Alert.{messageBox, confirmDialog};
...

Модули можно подгружать из веба или через файловую систему:

module JSON = require('http://json.org/modules/json2.js'); // web
import JSON.*;

module File = require('io/File'); // file system

import require("bar.js").y; // file system

Все глобальные переменные в модули являются глобальными только в этом модуле.

Возможны циклические зависимости между модулями.

Цикл for-of

Как вы знаете, цикл for-in в JavaScript итерирует по всем полям объекта (включая наследованных). Т.е. итерироваться по значениям массива можно, но опасно:

let arr = [ "blue", "green" ];
arr.notAnIndex = 123;
Array.prototype.protoProp = 456;

for(var x in arr) {
   console.log(x); // Напечатает blue, green, notAnIndex, protoProp
}

В ECMAScript 6 появится цикл for-of, который решит данную проблему:

for(var x of arr) {
   console.log(x); // Напечатает только blue, green
}

Также, возможно, в язык добавится оператор yield, с помощью которого можно легко и красиво писать кастомные итераторы.

Arrow-функции

В ECMAScript 6 появятся arrow functions:

let squares = [ 1, 2, 3 ].map(x => x * x);

Код выше эквивалентен этому:

let squares = [ 1, 2, 3 ].map(function (x) { return x * x });

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

let jane = {
   name: "Jane",
        
   sayHello: function (friends) {
      friends.forEach(friend => { console.log(this.name + " says hello to " + friend) });
   }
}

jane.sayHello([ 'Mark', 'John' ]);

выведет

Jane says hello to Mark
Jane says hello to John

как и ожидалось. А

let jane = {
   name: "Jane",
        
   sayHello: function (friends) {
      friends.forEach(function(friend) { console.log(this.name + " says hello to " + friend) });
   }
}

выведет:

 says hello to Mark
 says hello to John

Проблема в том, что this из анонимной функции function(friend) { ... }) перекрывает this из окружающего контекста. Для того, чтобы этого избежать, можно использовать старый прием с var self = this или использовать функцию bind:

var jane = {
   name: "Jane",
        
   sayHello: function (friends) {
      friends.forEach(function (friend) {
         console.log(this.name + " says hello to " + friend)
       }.bind(this));
   }
}

Т.е. по сути своей arrow functions — опять же синтаксический сахар над существующими анонимными функциями:

(x, y) => x + y + this.z

есть ничто иное как:

function (x, y) { return x + y + this.z }.bind(this)

Другие отличия arrow-функций от обычных функций:

  • Нельзя использовать arrow-функций как конструкторы (new (() => {} кинет ошибку)
  • Arrow-функции не могут обратиться к переменной arguments (да и незачем)

В остальном arrow-функции не отличаются от обычных функций. Они поддерживают значения по умолчанию, переменное количество параметров, операторы typeof и instanceof:

typeof () => {}; // 'function'
() => {} instanceof Function; // true

Заключение

Я описал далеко не всё, что появится в новом стандарте ECMAScript 6. И очень возможно, что что-то из того, о чем я написал выше, может измениться или вообще не появиться в стандарте. Тем не менее, все, что я описал, — это не слухи. Это вещи, реально обсуждаемые комитетом TC39. И к концу этого (2013) года стандарт должен быть утвержден.

Ссылки

Большая часть информации взята из блога доктора Axel'а Rauschmayer'a, послушать которого и повидать вживую мне посчастливилось на конференции CodeFest в Новосибирске.

PS. Спасибо 2GIS за организацию конференции!

Автор: orionll

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js