Обзор ECMAScript 6, следующей версии 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 за организацию конференции!

ссылка на оригинал статьи http://habrahabr.ru/post/175371/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *