Для начала, ликбез и несколько фактов:
- 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