This commit is contained in:
Ilya Kantor 2015-07-06 16:29:36 +03:00
parent e1daacdc6f
commit 772060278a
36 changed files with 811 additions and 10 deletions

View file

@ -62,11 +62,15 @@
# Примеры на этом сайте
[warn header="Только при поддержке браузера"]
Запускаемые примеры с ES-2015 будут работать только если ваш браузер поддерживает соответствующую возможность стандарта.
[/warn]
Это означает, что при запуске примеров в браузере, который их не поддерживает, будет ошибка. Это не означает, что пример неправильный! Просто пока нет поддержки...
Рекомендуется [Chrome Canary](https://www.google.com/chrome/browser/canary.html), Edge или [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/channel/#developer).
Впрочем, если пример в браузере не работает (обычно проявляется как ошибка синтаксиса) -- вы можете запустить его при помощи Babel, на странице [Babel: try it out](https://babeljs.io/repl/). Там же увидите и преобразованный код.
Впрочем, если пример в браузере не работает (обычно проявляется как ошибка синтаксиса) -- почти все примеры вы можете запустить его при помощи Babel, на странице [Babel: try it out](https://babeljs.io/repl/). Там же увидите и преобразованный код.
На практике для кросс-браузерности всё равно используют Babel.

View file

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before After
Before After

View file

@ -362,7 +362,7 @@ function f() {
f(1); // 1
```
Выведется `1` из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` берутся из внешней функции.
Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` берутся из внешней функции.
Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов.
@ -403,11 +403,24 @@ function defer(f, ms) {
```
## Итого
Основные улучшения в функциях:
<ul>
<li>Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.</li>
<li>Оператор spread (троеточие) в объявлении позволяет функции получать оставшиеся аргументы в массив: `function f(arg1, arg2, ...rest)`.</li>
<li>Тот же оператор spread в вызове функции позволяет передать её массив как список аргументов (вместо `apply`).</li>
<li>У функции есть свойство `name`, оно содержит имя, указанное при объявлении функции, либо, если его нет, то имя свойства или переменную, в которую она записана. Есть и некоторые другие ситуации, в которых интерпретатор подставляет "самое подходящее" имя.</li>
<li>Объявление Function Declaration в блоке `{...}` видно только в этом блоке.</li>
<li>Появились функции-стрелки:
<ul>
<li>Без фигурных скобок возвращают выражение `expr`: `(args) => expr`.</li>
<li>С фигурными скобками требуют явного `return`.</li>
<li>Сохраняют `this` и `arguments` окружающего контекста.</li>
<li>Не могут быть использованы как конструкторы, с `new`.</li>
</ul>
</li>
</ul>

View file

@ -0,0 +1,289 @@
# Объекты и прототипы
В этом разделе мы рассмотрим нововведения, которые касаются именно объектов.
По классам -- чуть позже, в отдельном разделе, оно того заслуживает.
Для объектов есть очень приятные нововведения, которые касаются объявления свойств и методов. Новый синтаксис используется и в классах.
Нововведения в объектах и классах очень тесно взаимосвязаны, поэтому обе этих темы объединены в один раздел.
Мы начнём с объектов, а затем перейдём к классам.
## Короткое свойство
Зачастую у нас есть переменные, например, `name` и `isAdmin`, и мы хотим использовать их в объекте.
При объявлении объекта в этом случае достаточно указать только имя свойства, а значение будет взято из переменной с таким именем.
Например:
```js
//+ run
'use strict';
let name = "Вася";
let isAdmin = true;
*!*
let user = {
name,
isAdmin
};
*/!*
alert( JSON.stringify(user) ); // {"name": "Вася", "isAdmin": true}
```
## Вычисляемые свойства
В качестве имени свойства можно использовать выражение, например:
```js
//+ run
'use strict';
let propName = "firstName";
let user = {
*!*
[propName]: "Вася"
*/!*
};
alert( user.firstName ); // Вася
```
Или даже так:
```js
//+ run
'use strict';
let a = "Мой ";
let b = "Зелёный ";
let c = "Крокодил";
let user = {
*!*
[(a + b + c).toLowerCase()]: "Вася"
*/!*
};
alert( user["мой зелёный крокодил"] ); // Вася
```
## Геттер-сеттер для прототипа
В ES5 для прототипа был метод-геттер:
<ul>
<li>`Object.getPrototypeOf(obj)`</li>
</ul>
В современной JavaScript также добавился сеттер:
<ul>
<li>`Object.setPrototypeOf(obj, newProto)`</li>
</ul>
...А также "узаконено" свойство `__proto__`, которое даёт прямой доступ к прототипу. Его, в качестве "нестандартного", но удобного способа работы с прототипом реализовали почти все браузеры (кроме IE10-), так что было принято решение добавить его в стандарт.
По стандарту оно реализовано через геттеры-сеттеры `Object.getPrototypeOf/setPrototypeOf`.
## Object.assign
Синтаксис:
```js
Object.assign(target, src1, src2...)
```
Функция `Object.assign` получает список объектов и копирует в первый `target` свойства из остальных.
Последующие свойства перезаписывают предыдущие.
Например:
```js
//+ run
'use strict';
let user = { name: "Вася" };
let visitor = { isAdmin: false, visits: true };
let admin = { isAdmin: true };
Object.assign(user, visitor, admin);
alert( JSON.stringify(user) ); // user: Вася, visits: true, isAdmin: true
```
## Методы объекта
Долгое время в JavaScript термин "метод объекта" был просто альтернативным названием для свойства-функции.
Теперь это уже не так, добавлены именно "методы объекта". Они отличаются от обычных свойств-функций наличием специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит.
Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`.
Например:
```js
//+ run
'use strict';
let name = "Вася";
let user = {
name,
*!*
/* вместо sayHi: function() { */
sayHi() {
alert(this.name);
}
*/!*
};
user.sayHi(); // Вася
```
Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`:
```js
//+ run
'use strict';
let name = "Вася", surname="Петров";
let user = {
name,
surname,
get fullName() {
return `${name} ${surname}`;
}
};
alert( user.fullName ); // Вася Петров
```
Основное отличие "методов" от "просто функций" -- возможность обратиться к "родительскому методу" через ключевое слово `super`, о котором пойдёт речь дальше.
## super
Ссылка `super` позволяет из метода обратиться к прототипу объекта.
Например, в коде ниже `super.walk` из `rabbit` обращается к `animal.walk`:
```js
//+ run
'use strict';
let animal = {
walk() {
alert("I'm walking");
}
};
let rabbit = {
__proto__: animal,
walk() {
*!*
alert(super.walk); // walk() { … }
super.walk(); // I'm walking
*/!*
}
};
rabbit.walk();
```
При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов.
Например, тот же код, но со свойством-функцией `walk` вместо метода в `rabbit`:
```js
//+ run
'use strict';
let animal = {
walk() {
alert("I'm walking");
}
};
let rabbit = {
__proto__: animal,
*!*
walk: function() {
super.walk(); // Будет ошибка!
}
*/!*
};
rabbit.walk();
```
Будет ошибка, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`.
Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции.
Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`:
```js
//+ run
'use strict';
let animal = {
walk() {
alert("I'm walking");
}
};
let rabbit = {
__proto__: animal,
walk() {
*!*
setTimeout(() => super.walk()); // I'm walking
*/!*
}
};
rabbit.walk();
```
[smart header="Свойство `[[HomeObject]]` -- не изменяемое"]
При создании метода -- он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить независимо:
```js
//+ run
'use strict';
let animal = {
walk() { alert("I'm walking"); }
};
let rabbit = {
__proto__: animal,
walk() {
super.walk();
alert(this);
}
};
let walk = rabbit.walk; // скопируем метод в переменную
*!*
walk();
// I'm walking
// undefined
*/!*
```
В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`.
Это относится именно к `super`. Правила `this` для методов те же, в примере выше будет `undefined`.
[/smart]

View file

@ -0,0 +1,169 @@
# Тип данных Symbol
Новый примитивный тип данных Symbol служит для создания уникальных идентификаторов.
## Объявление
Синтаксис:
```js
//+ run
'use strict';
let name = Symbol();
alert( typeof name ); // symbol
```
Обратим внимание, не `new Symbol`, а просто `Symbol`, так как это -- примитив.
Каждый символ -- уникален.
У функции `Symbol` есть необязательный аргумент "имя символа". Можно его использовать для описания символа, в целях отладки:
```js
//+ run
'use strict';
let name = Symbol("name");
alert( name.toString() ); // Symbol(name)
```
При этом если у двух символов одинаковое имя, то они *не равны*:
```js
//+ run
alert( Symbol("name") == Symbol("name") ); // false
```
То есть, ещё раз заметим, что каждый символ -- уникален.
## Глобальные символы
Существует "глобальный реестр" символов, который позволяет, при необходимости, разделять символы между частями программы.
Для чтения (или создания, если нет) "глобального" символа служит вызов `Symbol.for(имя)`:
```js
//+ run
'use strict';
// создание символа в реестре
let name = Symbol.for("name");
// символ уже есть, чтение из реестра
alert( Symbol.for("name") == name ); // true
```
Вызов `Symbol.keyFor(sym)` позволяет получить по глобальному символу его имя:
```js
//+ run
'use strict';
// создание символа в реестре
let name = Symbol.for("name");
// получение имени символа
alert( Symbol.keyFor(name) ); // name
```
[warn header="`Symbol.keyFor` возвращает `undefined`, если символ не глобальный"]
Заметим, что `Symbol.keyFor` работает *только для глобальных символов*, для остальных будет возвращено `undefined`:
```js
//+ run
'use strict';
alert( Symbol.keyFor(Symbol.for("name")) ); // name, глобальный
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный символ
```
[/warn]
## Использование символов
Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации:
```js
//+ run
'use strict';
let isAdmin = Symbol("isAdmin");
let user = {
name: "Вася",
age: 30,
[isAdmin]: true
};
alert( Object.keys(user) ); // name, age
for(let key in user) alert(key); // name, age
```
В примере выше выведутся все свойства, кроме символьного.
Это нужно в первую очередь для различных системных свойств, которых в современном JavaScript очень много.
Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1).
В спецификации принято для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`.
Например:
<ul>
<li>`Symbol.toPrimitive` -- идентификатор для свойства, задающего функцию преобразования объекта в примитив.</li>
<li>`Symbol.iterator` -- идентификатор для свойства, задающего функцию итерации по объекту.</li>
<li>...и т.п.</li>
</ul>
Смысл здесь в том, что допустим надо добавить к объекту "особый" функционал, например преобразование к примитиву или функцию итерации, или ещё что-то...
Новый стандарт не мог просто сказать, что "свойство obj.toPrimitive теперь системное, оно делает то-то и то-то". Ведь свойство с таким именем, вполне возможно, используется в существующем коде. И он сломался бы.
Поэтому ввели целый тип "символы", которые можно использовать для задания свойств, которые уникальны, не участвуют в итерации и заведомо не конфликтуют со старым кодом.
Например:
```js
//+ run
'use strict';
let obj = {
iterator: 1,
[Symbol.iterator]: function() {}
}
alert(obj.iterator); // 1, символ не конфликтует
```
Чтобы получить символы, есть особый вызов [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols).
Эта функция возвращает все символы в объекте. Заметим, что `getOwnPropertyNames` символы не возвращает.
```js
//+ run
'use strict';
let obj = {
iterator: 1,
[Symbol.iterator]: function() {}
}
// один символ в объекте
alert( Object.getOwnPropertySymbols(obj) ); // Symbol(Symbol.iterator)
```
Один из самых известных и полезных символов -- это `Symbol.iterator`. Мы будем активно его использовать позже, в главе про итераторы.
## Итого
<ul>
<li>Символы -- новый примитивный тип, предназначенный для уникальных идентификаторов.</li>
<li>Все символы уникальны, символы с одинаковым именем не равны друг другу.</li>
<li>Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`.</li>
<li>Основная область использования символов -- это системные свойства объектов. Поддержка у них пока небольшая, но она растёт. Символы позволяют добавлять в стандарт новые "особые" свойства объектов, при этом не резервируя соответствующие названия.</li>
</ul>

View file

@ -0,0 +1,325 @@
# Классы
В современном JavaScript появился новый, "более красивый" синтаксис для классов. Он естественным образом продолжает синтаксис для объектов и методов, который мы рассмотрели раньше.
## Class
Новая конструкция `class` -- удобный "синтаксический сахар" для задания конструктора вместе с прототипом.
Например:
```js
//+ run
'use strict';
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
let user = new User("Вася");
user.sayHi(); // Вася
```
Это объявление примерно аналогично такому:
```js
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
alert(this.name);
};
```
В обоих случаях `new User` будет создавать объекты. Метод `sayHi` -- также в обоих случаях находится в прототипе.
Но есть и отличия при объявлении через `class`:
<ul>
<li>`User`, объявленный как класс, нельзя вызывать без `new`, будет ошибка.</li>
<li>Объявление класса с точки зрения области видимости ведёт себя как `let`.</li>
</ul>
Методы, объявленные внутри `class`, также имеют ряд особенностей:
<ul>
<li>Метод `sayHi` является именно методом, то есть имеет доступ к `super`.</li>
<li>Все методы класса работают в `use strict`, даже если он не указан.</li>
<li>Все методы класса не перечислимы, то есть в `for..in` по объекту их не будет.</li>
</ul>
## Class Expression
Так же, как и Function Expression, классы можно задавать "инлайн" в выражении.
Это называется Class Expression:
```js
//+ run
'use strict';
let User = class {
sayHi() { alert('Привет!'); }
};
new User().sayHi();
```
В примере выше у класса нет имени, что один-в-один соответствует синтаксису функций. Но имя можно дать. Тогда оно, как и в Named Function Expression, будет доступно только внутри класса:
```js
//+ run
'use strict';
let SiteGuest = class User {
sayHi() { alert('Привет!'); }
};
new SiteGuest().sayHi(); // Привет
*!*
new User(); // ошибка
*/!*
```
В примере выше имя `User` будет доступно только внутри класса и может быть использовано, например для создания новых объектов данного типа.
## Геттеры, сеттеры и вычисляемые свойства
В классах, как и в обычных объектах, можно объявлять геттеры и сеттеры через `get/set`, а также использовать `[…]` для свойств с вычисляемыми именами:
```js
//+ run
'use strict';
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// геттер
*!*
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
*/!*
// сеттер
*!*
set fullName(newValue) {
[this.firstName, this.lastName] = newValue.split(' ');
}
*/!*
["test".toUpperCase()]: true
};
let user = new User("Вася", "Пупков");
alert( user.fullName ); // Вася Пупков
user.fullName = "Иван Петров";
alert( user.fullName ); // Иван Петров
alert( user.TEST ); // true
```
При чтении `fullName` будет вызван метод `get fullName()`, при присвоении -- метод `set fullName` с новым значением.
## Статические свойства
Статические свойства класса -- это свойства непосредственно класса `User`.
Например:
```js
//+ run
'use strict';
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
*!*
static createGuest() {
return new User("Гость", "Сайта");
}
*/!*
};
let user = User.createGuest();
alert( user.firstName ); // Гость
alert( User.createGuest ); // createGuest ... (функция)
```
Как правило, они используются для операций, не требующих наличия объекта, например -- для фабричных, как в примере выше, то есть как альтернативные варианты конструктора.
Или же, можно добавить метод `User.compare`, который будет сравнивать двух пользователей для целей сортировки.
## Наследование
Синтаксис:
```js
class Child extends Parent {
...
}
```
В примере ниже объявлено два класса: `Animal` и наследующий от него `Rabbit`:
```js
//+ run
'use strict';
class Animal {
constructor(name) {
this.name = name;
}
walk() {
alert("I walk: " + this.name);
}
}
*!*
class Rabbit extends Animal {
*/!*
walk() {
super.walk();
alert("...and jump!");
}
}
new Rabbit("Вася").walk();
// I walk: Вася
// and jump!
```
[smart header="Обычные прототипы"]
При наследовании формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`:
```js
//+ run
'use strict';
class Animal { }
class Rabbit extends Animal { }
alert( Rabbit.prototype.__proto__ == Animal.prototype ); // true
```
[/smart]
Как видно из примера выше, методы родителя можно переопределить в наследнике. При этом для обращения к родительскому методу используют `super.method()`.
Немного особая история -- с конструктором.
Конструктор `constructor` родителя наследуется автоматически. То есть, если в потомке не указан свой `constructor`, то используется родительский.
Если его переопределить, то родительский конструктор вызывается через `super()`, а не через `super.constructor()`.
Например, вызовем конструктор `Animal` в `Rabbit`:
```js
//+ run
'use strict';
class Animal {
constructor(name) {
this.name = name;
}
walk() {
alert("I walk: " + this.name);
}
}
class Rabbit extends Animal {
*!*
constructor() {
// вызвать конструктор Animal с аргументом "Кроль"
super("Кроль"); // то же, что и Animal.apply(this, arguments)
}
*/!*
}
new Rabbit().walk(); // I walk: Кроль
```
...Однако, здесь есть небольшие ограничения:
<ul>
<li>Вызвать конструктор родителя можно только изнутри конструктора потомка. В частности, `super()` нельзя вызвать из произвольного метода.</li>
<li>В конструкторе потомка мы обязаны вызвать `super()` до обращения к `this`. До вызова `super` не существует `this`, так как по спецификации в этом случае именно `super` инициализует `this`.</li>
</ul>
Второе ограничение выглядит несколько странно, поэтому проиллюстрируем его примером:
```js
//+ run
'use strict';
class Animal {
constructor(name) {
this.name = name;
}
}
class Rabbit extends Animal {
*!*
constructor() {
alert(this); // ошибка, this не определён!
// обязаны вызвать super() до обращения к this
}
*/!*
}
new Rabbit();
```
## Итого
Концепция классов, которая после долгих обсуждений получилась в стандарте EcmaScript, носит название "максимально минимальной". То есть, в неё вошли только те возможности, которые уж точно необходимы.
В частности, не вошли "приватные" и "защищённые" свойства. То есть, все свойства и методы класса технически доступны снаружи. Возможно, они появятся в будущих редакциях стандарта.
В классах нет возможности ограничивать

View file

@ -18,6 +18,7 @@
```js
function PropertyError(property) {
Error.call(this, property) ;
this.name = "PropertyError";
this.property = property;
@ -71,10 +72,10 @@ Error.captureStackTrace(this, PropertyError);
</dd>
</dl>
[smart header="Конструктор родителя здесь не нужен"]
Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядел бы как `Error.call(this, message)`.
[smart header="Конструктор родителя здесь не обязателен"]
Обычно, когда мы наследуем, то вызываем конструктор родителя. В данном случае вызов выглядит как `Error.call(this, message)`.
Однако, встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Поэтому и вызывать его здесь нет необходимости.
Строго говоря, этот вызов здесь не обязателен. Встроенный конструктор `Error` ничего полезного не делает, даже свойство `this.message` (не говоря уже об `name` и `stack`) не назначает. Единственный возможный смысл его вызова -- он ставит специальное внутреннее свойство `[[ErrorData]]`, которое выводится в `toString` и позволяет увидить, что это ошибка. Поэтому по стандарту вызывать конструктор `Error` при наследовании в таких случаях рекомендовано.
[/smart]