ECMAScript сегодня и в будущем.
Дмитрий Сошников
http://dmitrysoshnikov.com
ES5 : Новый API объектов
Два вида свойств:
Обычные data-свойства (явная ассоциация имени и значения)
Accessor-свойства (геттеры/сеттеры) (неявная ассоциация имени с accessor-функциями)
Работа со свойствами
Object
// работа с атрибутами
.defineProperty
Работа со свойствами
Object
// работа с атрибутами
.defineProperty
// сразу несколько свойств
.defineProperties
Работа со свойствами
Object
// работа с атрибутами
.defineProperty
// сразу несколько свойств
.defineProperties
// анализ атрибутов (дескриптор)
.getOwnPropertyDescriptor
Работа со свойствами
Object
// работа с атрибутами
.defineProperty
// сразу несколько свойств
.defineProperties
// анализ атрибутов (дескриптор)
.getOwnPropertyDescriptor
// список свойств
.keys
.getOwnPropertyNames
// статика
.preventExtensions / .isExtensible
// «опечатывание»
.seal / .isSealed
// «заморозка»
.freeze / .isFrozen
// наследование; прототипы
.getPrototypeOf
.create
Дескриптор data-свойства
{
value: 10, // значение
writable: true, // только чтение?
enumerable: false, // перечислимо в for-in?
configurable: true // можно удалить?
}
Data-свойство
var foo = Object.defineProperty({}, “x” {
value: 10,
writable: true,
enumerable: false,
configurable: true
});
Data-свойство
var foo = Object.defineProperty(,-, “x” ,
value: 10,
writable: true,
enumerable: false,
configurable: true
});
Object.defineProperty(foo, “MAX_SIZE” {
value: 20,
writable: false, // default
enumerable: false, // default
configurable: false // default
});
По умолчанию все атрибуты : false
Data-свойство
var foo = Object.defineProperty(,-, “x” ,
value: 10,
writable: true,
enumerable: false,
configurable: true
});
Object.defineProperty(foo, “MAX_SIZE” {
value: 20,
writable: false,
enumerable: false,
configurable: false
});
foo.z = 30;
Object.defineProperty(foo, “z” ,
value: 30,
writable: true,
enumerable: true,
configurable: true
});
ES3
По умолчанию все атрибуты : false
Создание свойства присваиванием -- атрибуты : true
enumerable: false
ES3
Object.prototype.x = 10;
var foo = {y: 20};
for (var k in foo) {
console.log(k); // y, x
}
ES5
Object.defineProperty(
Object.prototype, “x”, {
value: 10
}
);
for (var k in foo) {
console.log(k); // y
}
Дескриптор accessor-свойства
{
get: function () {...}, // чтение значения
set: function (v) {...}, // установка значения
enumerable: false, // перечислимо в for-in?
configurable: true // можно удалить?
}
Accessor-свойство
Object.defineProperty(foo, “z” {
get: function () {
return this.x + this.y;
},
set: undefined,
enumerable: false,
configurable: false
});
console.log(foo.z); // 30
foo.z = 40; // false, только геттер
console.log(foo.z); // не изменилось - 30
Декларативное объявление геттера/сеттера
var foo = {
x: 10,
y: 20,
get sum() {
return this.x + this.y;
}
};
foo.sum; // 30
Список свойств объекта
// только enumerable-свойства
Object.keys(foo); // *“y”, “z”+
// все родные свойства
Object. getOwnPropertyNames(foo); // *“x”, “y”, “z”+
Фиксация объектов
Статика: запрещает расширение объекта
Object.isExtensible(Object.preventExtensions(foo)); // false
foo.bar = 100; // false
console.log(“bar” in foo); // false
var foo = { x: 10 };
Фиксация объектов
Статика: запрещает расширение объекта
Object.isExtensible(Object.preventExtensions(foo)); // false
foo.bar = 100; // false
console.log(“bar” in foo); // false
Опечатывание (seal): + устанавливает всем свойствам configurable == false
Object.isSealed(Object.seal(foo)); // true
Object.getOwnPropertyDescriptor(foo, “x”).configurable; // false
var foo = { x: 10 };
Фиксация объектов
Статика: запрещает расширение объекта
Object.isExtensible(Object.preventExtensions(foo)); // false
foo.bar = 100; // false
console.log(“bar” in foo); // false
Опечатывание (seal): + устанавливает всем свойствам configurable == false
Object.isSealed(Object.seal(foo)); // true
Object.getOwnPropertyDescriptor(foo, “x”).configurable; // false
Заморозка (freeze): + устанавливает всем свойствам writable == false
Object.isFrozen(Object. freeze(foo)); // true
foo.y = 100; // false
console.log(foo.y); // не изменилось, 20
var foo = { x: 10 };
Получение прототипа объекта
Object.getPrototypeOf(foo)
===
Object.prototype; // true
Прототипное наследование
Object.create(proto, [properties])
// объект-родитель
var foo = {
x: 10,
y: 20,
z: 30
};
Прототипное наследование
Object.create(proto, [properties])
console.log(bar.x, bar.y, bar.z, bar.q); // 10, 20, 30, 40
// объект-родитель
var foo = {
x: 10,
y: 20,
z: 30
};
// наследуем bar от foo
var bar = Object.create(foo, {
q: {
value: 40
}
};
Обычные hash-таблицы без прототипа
var foo = Object.create(null);
alert(foo); // ?
Обычные hash-таблицы без прототипа
var foo = Object.create(null);
alert(foo); // error
Нет метода toString. Объект foo пустой и ни от кого не наследует.
Будущее > ES6
let : переменные в блоках
Блочная область видимости
// ES3
if (false) {
var a = 10;
}
alert(a); // ?
Блочная область видимости
// ES3
var a; // = undefined;
if (false) {
a = 10;
}
alert(a); // undefined
See: http://dmitrysoshnikov.com/notes/note-4-two-words-about-hoisting/
Все переменные создаются до запуска кода – при входе в контекст.
Так называемое «поднятие»
(hoisting) переменных.
Блочная область видимости
// ES6 Harmony
if (false) {
let a = 10;
}
alert(a); // ReferenceError
Блочная область видимости
// ES3, ES5
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // ?
data[1](); // ?
data[2](); // ?
Блочная область видимости
// ES3, ES5
var data = [];
var k;
for (k = 0; k < 3; k++) {
data[k] = function () {
alert(k);
};
}
data[0](); // 3
data[1](); // 3
data[2](); // 3
let : блочная область видимости
ES3, ES5
for (var k = 0; k < 3; k++) {
(function (x) {
data[x] = function () {
alert(x);
};
})(k);
}
data[0](); // 0
ES6
for (let k = 0; k < 3; k++) {
let x = k;
data[x] = function () {
alert(x);
};
}
data[0](); // 0
Деструктуризация или «нестогий pattern-matching»
Деструктуризация : массивы
// для массивов
let [x, y] = [10, 20, 30]; // нестрогий matching
console.log(x, y); // 10, 20
Деструктуризация: объекты
// для объектов
let foo = {value: 100, data: {x: 10, y: 20}};
let {value: v, data: {x: x, y: y}} = foo;
console.log(v, x, y); // 100, 10, 20
Деструктуризация параметров функции
function foo({a: a, b: [x, y]}) {
return a + x * y;
}
let data = {a: 1, b: [2, 3]};
foo(data); // 7
Деструктуризация: обмен переменных
// обмен двух переменных без третьей?
let x = 10;
let y = 20;
[x, y] = [y, x]; // легко
Замена arguments: ”rest” и ”spread”
Объект arguments
// ES3, ES5
function foo(name, /* rest */) {
var rest = [].slice.call(arguments, 1);
var squares = rest.map(function (x) { return x * x});
return squares;
}
foo(“squares”, 1, 2, 3); // [1, 4, 9]
Прощай, arguments
// ES3, ES5
function foo(name, /* rest */) {
var rest = [].slice.call(arguments, 1); // сложно
var squares = rest.map(function (x) { return x * x});
return squares;
}
foo(“squares”, 1, 2, 3); // *1, 4, 9+
Привет, ”rest”
// ES6 aka Harmony
function foo(name, …rest) { // настоящий массив
var squares = rest.map(function (x) { return x * x});
return squares;
}
foo(“squares”, 1, 2, 3); // [1, 4, 9]
А также ”spread”
// ES6 aka Harmony
function bar(x, y, z) {
return x + y * z;
}
let args = [1, 2, 3];
bar(…args); // 7
bar.apply(null, args); // или так, 7
”spread” в pattern-matching
// ES6 aka Harmony
let args = [“data”, 1, 2, 3];
let [name, ...values] = args;
console.log(name); // “data”
console.log(values); // [1, 2, 3]
Сокращенные нотации
Сокращения в деструктуризации
// полная нотация
let {x: x, y: y, z: z} = {x: 10, y: 20, z: 30};
// сокращенная нотация
let {x, y, z} = {x: 10, y: 20, z: 30};
Короткий синтаксис функций. #-функции
// обычные функции
[1, 2, 3].map(function (x) { return x * x; }); // [1, 4, 9]
// #-функции
[1, 2, 3].map(#(x) { x * x }); // [1, 4, 9]
Синтаксически:
• необязательный return;
• # вместо function
Семантика #-функций
// обычная функция
let object = {
start: function () {
setTimeout(function () { this.continue(); }, 500);
},
continue: function () { ... }
};
object.start(); // error
Динамически связываемый this
Решения: var that = this;
.bind(this)
Семантика #-функций
// #-функции
let object = {
start: function () {
setTimeout(#{ this.continue(); }, 500);
},
continue: function () { ... }
};
object.start(); // ok
this автоматом связан с лексическим контекстом
Семантика #-функций
// #-функции
let object = {
start: # (this) {
setTimeout(#{ this.continue(); }, 500);
},
continue: # (this) { ... }
};
object.start(); // ok
Динамический this в #-функциях
Proxy объекты : мета уровень
Proxy-объекты
/* handler – обработчик мета-уровня
* proto – прототип прокси-объекта */
Proxy.create(handler, [proto])
/* handler – мета-обработчик
* call – проксирование вызова
* construct – проксирование конструирования */
Proxy.createFunction(handler, [call, [construct]])
See: http://wiki.ecmascript.org/doku.php?id=harmony:proxies
Proxy-объекты
// original object
let foo = {
x: 10,
y: 20
};
// proxied object
let pFoo = Proxy.create({
get: function (rcvr, name) {
console.log(“get: ”, name);
return foo[name];
},
set: function (rcvr, name, value) {
console.log(“set: ”, name, value);
foo[name] = value;
}
}, Object.getPrototypeOf(foo));
Перехват чтения свойства
Перехват записи свойства
Proxy-объекты
// перехват чтения
pFoo.x; // get: x, 10
// перехват записи
pFoo.x = 100; // set: x, 100
// отображается на оригинале
foo.x; // 100
// proxied object
let pFoo = Proxy.create({
get: function (rcvr, name) {
console.log(“get: ”, name);
return foo[name];
},
set: function (rcvr, name, value) {
console.log(“set: ”, name, value);
foo[name] = value;
}
}, Object.getPrototypeOf(foo));
Мета-обработчик
Callable Proxy-объекты
// original object
let foo = {x: 10, y: 20};
function callTrap() {
console.log(“call”);
}
function constructTrap() {
console.log(“construct”);
}
pFoo(10, 20); // “call”
new pFoo(100); // “construct”
// proxied object
let pFoo = Proxy.createFunction({
get: function (rcvr, name) {
console.log(“get: ”, name);
return foo[name];
},
set: function (rcvr, name, value) {
console.log(“set: ”, name, value);
foo[name] = value;
}
}, callTrap, constructTrap);
Перехват вызова Перехват
конструирования
Proxy : примеры // логгеры (на чтение и запись)
Proxy.create(logHandler(object));
// множественное наследование (делегирующие примеси)
Proxy.create(mixin(obj1, obj2));
// noSuchMethod
Proxy.create(object, noSuchMethod)
// Массивы с отрицательными индексами (как в Python)
let a = Array.new([1, 2, 3]);
console.log(a[-1]); // 3
a[-1] = 10; console.log(a); // [1, 2, 10]
See: https://github.com/DmitrySoshnikov/es-laboratory/tree/master/examples
Система модулей
Модули в ES3, ES5 var Library = (function (global) {
/* save original */
var originalLibrary = global.Library;
function noConflict() {
global.Library = originalLibrary;
}
/* implementation */
function query() { ... }
/* exports, public API */
return {
noConflict: noConflict,
query: query
};
})(this);
1. Создать локальный скоп 2. Функция восстановления 3. Имплементация 4. Публичный интерфейс
Модули в ES3, ES5 var Library = (function (global) {
/* save original */
var originalLibrary = global.Library;
function noConflict() {
global.Library = originalLibrary;
}
/* implementation */
function query() { ... }
/* exports, public API */
return {
noConflict: noConflict,
query: query
};
})(this);
1. Создать локальный скоп 2. Функция восстановления 3. Имплементация 4. Публичный интерфейс
Слишком много синтаксического «шума».
Нужен «сахар».
Модули в ES6 module Library {
export function query(s) { ... }
export function ajax(...args) { ... }
}
import Library.*; // импортировать все
import Library.{query, ajax: xhr}; // импортировать только нужное
query(“#my-element”).hide();
xhr(“/books/store”, {
onSuccess: # (response) { ... }
})
Модули в ES6 module Widgets {
var collection= [ ... ]; // приватное свойство
function registerWidget(name, ...params) { ... } // приватное ?
// внутренний приватный модуль
module Register { ... }
// вложенный публичный модуль
export module Panel {
export function create() { ... }
}
// множественный export
export { register: registerWidget }
}
let panel = Widgets.Panel.create({title: “Options”});
// ошибка, нельзя присвоить export’у Widgets.Panel = false; // ошибка, нет такого export’а let bar = Widget.Window
В дополнение:
Внешние модули в ES6 // на файловой системе
module $ = “./library/selector.js”;
// глобально, из сети; сами определяем имя модуля
module CanvasLib = “http:// ... /js-modules/canvas.js”;
// используем напрямую
let rect = new CanvasLib.Rectangle({width: 30, height: 40, shadow: true});
// или импортируем нужные объекты
import CanvasLib.{Triangle, rotate};
rotate(-30, new Triangle($.query(...params)));
Спасибо за внимание
Дмитрий Сошников
http://dmitrysoshnikov.com
@DmitrySoshnikov