Мы работаем! Пишите WhatsApp / Viber / Telegram: +7 951 127-23-57, Skype: creograf

Конспект книги 'ES6 и за его пределами', Кайл Симпсон. Часть 2.

Конспект книги 'ES6 и за его пределами', Кайл Симпсон. Часть 2.

11:42:33 23.12.2016

Управление асинхронными операциями

Обещания

Основным механизмом для управления асинхронностью раньше всегда был обратный вызов функции. Обещания ориентированы не на замещение обратных вызовов. Это что-то вроде достойного доверия посредника для управления обратными вызовами — другими словами, они находятся между вызывающим кодом и выполняющим задачу асинхронным кодом.
Обещание можно представить и как слушателя, который позволяет вам подписаться на прослушивание события, срабатывающего один раз и дающего сигнал о завершении какой-либо задачи.

Обещания можно соединять в цепочку, задающую последовательность асинхронно выполняемых задач. В комбинации с такими высокоуровневыми абстракциями, как метод all(..) (в классической терминологии «ворота») и метод race(..) (в классической терминологии «защелка»), цепочки обещаний предоставляют аппроксимацию управления асинхронными операциями.

Еще обещания можно представить как будущее значение, то есть обернутый вокруг значения независимый от времени контейнер. Обещание представляет собой асинхронную версию значения, возвращаемого синхронной функцией.

Разрешение обещания завершается одним из двух способов: оно может быть выполнено или отклонено с необязательным единственным значением. Если обещание выполнено, окончательное значение называется осуществлением, в противном случае — причиной (как во фразе «причина для отказа»). Разрешение обещаний (осуществление или отказ) возможно только один раз. Дальнейшие попытки попросту будут проигнорированы. Соответственно, как только обещание оказывается разрешено, мы получаем недоступное для редактирования значение.

Promise(..):
var p = new Promise( function(resolve,reject){
	// ..
} );

При вызове функции reject(..) обещание отклоняется, а переданное в нее значение становится причиной для отказа.
Вызов функции resolve(..) без какого-либо значения или со значением, не являющимся обещанием, приводит к выполнению обещания.
Если в вызываемую функцию resolve(..) передается еще одно обещание, основное обещание просто заимствует его состояние (исполнение или отказ) — вне зависимости от того, мгновенным или окончательным оно является.

Пример callback'a:

function ajax(url,cb) {
	// делаем запрос, в конечном счете вызываем 'cb(..)'
}
	// ..
ajax( "http://some.url.1", function handler(err,contents){
	if (err) {
		// обрабатываем ошибку ajax
	}
	else {
		// обрабатываем удачное завершение 'contents'
	}
} );

И переписанный вариант с обещанием:

function ajax(url) {
	return new Promise( function pr(resolve,reject){
		// делаем запрос, в конечном счете вызываем
		// или 'resolve(..)', или 'reject(..)'
	} );
}
// ..
ajax( "http://some.url.1" )
.then(
	function fulfilled(contents){
		// обрабатываем успешное завершение 'contents'
	},
	function rejected(reason){
		// обрабатываем причину ошибки ajax
	}
)
.then(
	//... следующий вызов ajax
);

Обещания обладают методом then(..), принимающим одну или две функции обратного вызова. Первая (если она есть) интерпретируется как обработчик, который вызывается в случае успешного выполнения обещания, вторая (если она есть) — как обработчик, вызываемый в случае явного отказа или в ситуации, когда в процессе разрешения была перехвачена ошибка или исключение.
Если они не заданы, стандартный успешный обратный вызов передает свое значение завершения, а стандартный ошибочный обратный вызов — причину отказа. Сокращенный вид вызова then(null,handleRejection) выглядит так: catch(handleRejection).

Объекты thenable

Термином thenable называется любой объект (или функция), обладающий методом then(..). Это любые значения, напоминающие обещания, но созданные не конструктором Promise(..). Обработчик успешно завершенной операции вызывается в цикле, в то время как обычные обещания должны разрешаться всего один раз.

var th = {
	then: function thener( fulfilled ) {
		// бесконечно вызывает'fulfilled(..)' каждые 100 мс
		setInterval( fulfilled, 100 );
	}
};

Любой объект в произвольном месте кода, обладающий методом then(..) или вызывающий его, потенциально может быть принят за thenable, если появляется вместе с обещаниями.

API обещаний

1. Promise.resolve(..)
Возвращает объект, который успешно выполнен с указанным значением.
Передача значения в метод Promise.resolve(..) позволяет из неизвестного объекта получить обещание. Если значение распознается как обещание или объект thenable, его состояние/разрешение просто заимствуется, избавляя вас от проблем с некорректным поведением. Если же выясняется, что перевами непосредственное значение, оно «оборачивается» в истинное обещание для обеспечения требуемого асинхронного поведения.

var p1 = Promise.resolve( 42 );
var p2 = new Promise( function pr(resolve){ // p1 и p2 ведут себя одинаково
	resolve( 42 );
} );

2. Promise.reject(..)
Метод создает немедленно отклоненное обещание. В качестве причины указывается само обещание или объект thenable соответственно, а не его основное значение.

var p1 = Promise.reject( "Oops" );
var p2 = new Promise( function pr(resolve,reject){
	reject( "Oops" );
} );

3. Promise.all([ .. ])
Метод принимает массив из одного или нескольких значений (непосредственных значений, обещаний или объектов thenable). Он возвращает обещание, которое будет выполнено при условии выполнения всех значений или отвергнуто, если отвергается хотя бы одно из них.

var p1 = Promise.resolve( 42 );
var p2 = new Promise( function pr(resolve){
	setTimeout( function(){
		resolve( 43 );
	}, 100 );
} );
var v3 = 44;
var p4 = new Promise( function pr(resolve,reject){
	setTimeout( function(){
		reject( "Oops" );
	}, 10 );
} );

Promise.all( [p1,p2,v3] )
.then( function fulfilled(vals){
	console.log( vals ); // [42,43,44]
} );

Promise.all( [p1,p2,v3,p4] )
.then(
	function fulfilled(vals){
	// сюда мы никогда не попадаем
},
function rejected(reason){
	console.log( reason ); // Oops
});

4. Promise.race([..])
Ждет или первого принятия, или первого отказа.

Promise.race( [p2,p1,v3] )
.then( function fulfilled(val){
	console.log( val ); // 42
} );

Если метод Promise.all([]) будет выполнен сразу же (без каких-либо значений), то метод Promise.race([]) зациклится. Это странное противоречие наводит на мысль, что данные методы в принципе нельзя использовать с пустыми массивами.

Генераторы и обещания

Для управления асинхронными операциями в программе наборы обещаний можно соединить в цепочку.

step1()
.then(
	step2,
	step2Failed
)
.then(
	function(msg) {
		return Promise.all( [
			step3a( msg ),
			step3b( msg ),
			step3c( msg )
		] )
	}
)
.then(step4);

Генератор способен сформировать обещание, после чего можно сделать так, чтобы завершающее значение обещания возобновляло работу генератора.

function *main() {
	var ret = yield step1();
	try {
		ret = yield step2( ret );
	}
	catch (err) {
		ret = yield step2Failed( err );
	}
	ret = yield Promise.all( [
		step3a( ret ),
		step3b( ret ),
		step3c( ret )
	] );
	yield step4( ret );
}

Объединение надежности обещаний с синхронностью кода генераторов позволяет эффективно избавиться от всех основных недостатков обратных вызовов. Кроме того, такие служебные программы, как Promise.all([ .. ]), позволяют красиво реализовать параллелизм на одном шаге yield генератора.

function run(gen) {
	var args = [].slice.call( arguments, 1), it;
	it = gen.apply( this, args );
	return Promise.resolve()
		.then( function handleNext(value){
			var next = it.next( value );
			return (function handleResult(next){
				if (next.done) {
					return next.value;
				}
				else {
				return Promise.resolve( next.value )
					.then(
						handleNext,
						function handleErr(err) {
							return Promise.resolve(
								it.throw( err )
							)
							.then( handleResult );
						}
					);
				}
			})( next );
		} );
}

// вызов main
run( main )
.then(
	function fulfilled(){
		// '*main()' успешно завершена
	},
	function rejected(reason){
		// Ой, что-то пошло не так
	}
);

По сути, везде, где есть два и более асинхронных шага в потоке управляющей логики, можно и нужно использовать для управления в синхронном стиле выдающий обещания генератор, приводимый в действие запускающей программой. Это сильно облегчит понимание и поддержку кода.

Коллекции

Коллекции map напоминают объекты (пары «ключ/значение»), но на самом деле это всего лишь строка для ключа, а значение вы можете использовать любое — даже другой объект или коллекцию map. Коллекции set напоминают массивы (списки значений), но все значения в них уникальны; если вы добавляете дубликат, он просто игнорируется. Существуют и «слабые» (в плане сборки мусора) эквиваленты: WeakMap и WeakSet.

TypedArrays

var buf = new ArrayBuffer( 32 );
buf.byteLength; // 32

Здесь buf — бинарный буфер длиной в 32 байта (256 бит), который заранее инициализирован значениями 0. Единственный доступный способ взаимодействия с этим буфером — проверка его свойства byteLength. В этот буфер массива можно сверху поместить «представление» в форме типизированного массива.

var arr = new Uint16Array( buf );
arr.length;

Если бинарные данные созданы на платформе с одним, а интерпретируются на платформе с противоположным порядком байтов, это может стать проблемой. Порядок определяется тем, где именно, справа или слева, в многобайтовом числе — например, в 16-битном целом без знака, которое мы создали в предыдущем фрагменте кода, — находится младший байт (набор из 8 бит). Быстрый способ от MDN, позволяющий проверить порядок байтов ваших сценариев JavaScript. Функция littleEndian может принимать значение true или false; для большинства браузеров возвращается значение true.

var littleEndian = (function() {
	var buffer = new ArrayBuffer( 2 );
	new DataView( buffer ).setInt16( 0, 256, true );
	return new Int16Array( buffer )[0] === 256;
})();

Множественные представления
К одному буферу можно присоединить несколько представлений:

var buf = new ArrayBuffer( 2 );
var view8 = new Uint8Array( buf );
var view16 = new Uint16Array( buf );

view16[0] = 3085;
view8[0]; // 13
view8[1]; // 12
view8[0].toString( 16 ); // "d"
view8[1].toString( 16 ); // "c"

// меняем порядок (как в случае порядка байтов!)
var tmp = view8[0];
view8[0] = view8[1];
view8[1] = tmp;
view16[0]; // 3340

Конструкторы типизированных массивов

 

Int8Array (8-битные целые со знаком), Uint8Array (8-битные целые без знака);
—Uint8ClampedArray (8-битные целые без знака, со значениями в диапазоне 0-255);
—Int16Array (16-битные целые со знаком), Uint16Array (16-битные целые без знака);
—Int32Array (32-битные целые со знаком), Uint32Array (32-битные целые без знака);
—Float32Array (32-битные с плавающей точкой, IEEE-754);
—Float64Array (64-битные с плавающей точкой, IEEE-754).

Параметры конструкторов:
[constructor\](length) — создает новое представление в новом буфере с числом байт length;
[constructor\](typedArr) — создает новое представление и новый буфер и копирует содержимое из представления typedArr;
[constructor\](obj) — создает новое представление и буфер и в цикле просматривает напоминающий массив объект obj с целью копирования его содержимого.

Сортировка по умолчанию численная. Метод TypedArray#sort(..) принимает в качестве необязательного аргумента функцию сравнения, аналогично Array#sort(..).

var a = [ 10, 1, 2, ];
a.sort(); // [1,10,2]
var b = new Uint8Array( [ 10, 1, 2 ] );
b.sort(); // [1,2,10]

Карты

При этом основной недостаток применения объектов в качестве карт — невозможность взять в качестве ключа нестроковое значение. В качестве ключа карты может использоваться любое значение, но, как правило, это объекты, так как строки и другие примитивы уже зарезервированы в качестве ключей обычных объектов.

var m = new Map();
var x = { id: 1 },
y = { id: 2 };
m.set( x, "foo" );
m.set( y, "bar" );
m.get( x ); // "foo"
m.get( y ); // "bar"

m.size; // 2

m.delete( y );  // удалить один элемент
m.clear();	// удалить все элементы

Конструктор Map(..) также может получать итерируемый объект, который должен сгенерировать список массивов, причем первый элемент каждого будет ключом, а второй — значением.

var m2 = new Map( m.entries() ); // копия карты
var m2 = new Map( m );	// тоже копия карты, т.к. карта - итерируемый объект

var vals = [ ...m.entries() ];
vals[0][0] === x; // true
vals[0][1]; // "foo"
vals[1][0] === y; // true
vals[1][1]; // "bar"

Создание карты из массива массивов, первый элемент - ключ, второй - значение.

var m = new Map( [
	[ x, "foo" ],
	[ y, "bar" ]
] );

Для получения списка значений карты применяется метод values(..), возвращающий итератор. Для получения списка ключей карты используется метод keys(), возвращающий итератор.

var vals = [ ...m.values() ];
vals; // ["foo","bar"]
Array.from( m.values() ); // ["foo","bar"]

var keys = [ ...m.keys() ];
keys[0] === x; // true
keys[1] === y; // true

Если в качестве ключа карты используется объект, который позднее оказывается удаленным (исчезают все ссылки на него), для освобождения памяти при проходе сборщика мусора, карта все равно будет возвращать эту запись. Чтобы сделать запись карты доступной для сборщика мусора, потребуется ее удаление.

Объекты WeakMap

Слабые карты принимают в качестве ключей только объекты, и те удерживаются слабо: если такой объект удаляется сборщиком мусора, то удаляется и соответствующая запись в коллекции WeakMap. У коллекций WeakMap отсутствует свойство size и метод clear(), а еще нет итераторов для ключей, значений или элементов.

var m = new WeakMap();
var x = { id: 1 },
y = { id: 2 };

m.set( x, "foo" );
m.has( x ); // true
m.has( y ); // false

Особенно полезны они бывают в случае с объектами, которые вы не можете полностью контролировать, например с элементами DOM.

WeakMap слабо удерживает только ключи, но не значения:

var m = new WeakMap();
var x = { id: 1 },
y = { id: 2 },
z = { id: 3 },
w = { id: 4 };
m.set( x, y );
x = null; // { id: 1 } доступен для сборщика мусора
y = null; // { id: 2 } доступен для сборщика мусора
		  // только потому, что { id: 1 }

m.set( z, w );
w = null; // { id: 4 } недоступен для сборщика мусора

Объекты Set

Объекты set представляют собой коллекции уникальных значений. Вместо метода set(..) используется метод add(..), а get(..) отсутствует.
В методе has(..) используется почти такой же алгоритм сравнения, как в методе Object.is(..), но –0 и 0 интерпретируются как одно значение, а не как разные.

var s = new Set();
var x = { id: 1 },
y = { id: 2 };
s.add( x );
s.add( y );
s.add( x ); // дубликаты игнорируются

s.size; // 2
s.delete( y );
s.size; // 1

s.clear();
s.size; // 0

Конструкторы

var s = new Set( [x,y] );	// массив объектов

Итераторы коллекций Set
По умолчанию для коллекции set используется итератор values(). Все примерно как у карт:

var s = new Set();
var x = { id: 1 },
y = { id: 2 };
s.add( x ).add( y );

var keys = [ ...s.keys() ],
keys[0] === x;
keys[1] === y;

vals = [ ...s.values() ],
vals[0] === x;
vals[1] === y;

entries = [ ...s.entries() ];
entries[0][0] === x;
entries[0][1] === x;
entries[1][0] === y;
entries[1][1] === y;

Приведения типов при проверке уникальности не происходит:

var s = new Set( [1,2,3,4,"1",2,4,"5"] ),
uniques = [ ...s ];
uniques; // [1,2,3,4,"1","5"]

WeakSets

Коллекции WeakSet слабо держат значения (ключей у них просто нет).

var s = new WeakSet();
var x = { id: 1 },
y = { id: 2 };

s.add( x );
s.add( y );

x = null; // 'x' доступен для сборщика мусора
y = null; // 'y' доступен для сборщика мусора

Дополнения к API

Массив

1. Array.of(..)
Если передать ему только один числовой аргумент, будет создан не массив из одного элемента, а пустой массив со свойством length, равным переданному числу. Поэтому функция Array(..) теперь заменена функцией Array.of(..)

var a = Array( 3 );
a.length; // 3
a[0]; // undefined

var b = Array.of( 3 );
b.length; // 1
b[0]; // 3

var c = Array.of( 1, 2, 3 );
c.length; // 3
c; // [1,2,3]

Array.of(..) нужен, когда:
- во-первых, это наличие обратного вызова, который должен охватывать переданный в массив аргумент или аргументы.
- наличие производного класса Array, в экземпляре которого вы хотите создавать и инициализировать элементы.

class MyCoolArray extends Array {
	sum() {
		return this.reduce( function reducer(acc,curr){
			return acc + curr;
		}, 0 );
	}
}

var x = new MyCoolArray( 3 );
x.length; // 3 - ой!
x.sum(); // 0 - ой!

var y = [3]; // Array, не MyCoolArray
y.length; // 1
y.sum(); // 'sum' - это не функция

var z = MyCoolArray.of( 3 ); // единственное решение
z.length; // 1
z.sum(); // 3

2. Array.from(..)
Используется для:
- дублирования настоящего массива
- создания массива из объекта, напоминающий массив

// объект, напоминающий массив
var arrLike = {
	length: 3,
	0: "foo",
	1: "bar"
};

var arr = Array.from( arrLike ); // массив из объекта
var arrCopy = Array.from( arr ); // копирование

Но если передать в качестве первого аргумента методу Array.from(..) объект, напоминающий массив, он просто начнет перебирать значения в цикле, обращаясь к именованным свойствам, имена которых начинаются с 0 и заканчиваются значением свойства length. Так как позиций 0, 1 и 3 в объекте arrLike не существует, для каждого из этих слотов результатом становится значение undefined (пустые слоты).

Array.from( arrLike ); // [ undefined, undefined, "foo", undefined ]

var c = Array.from( { length: 4 } ); // четыре значения 'undefined'

Намеренно создавать пустые слоты не следует, так как это практически гарантированно приведет к непредсказуемому поведению вашего кода.

Второй аргумент, если он присутствует, представляет собой отображающий обратный вызов (почти то же самое, чего ожидает обычный метод Array#map(..)), который активируется, когда нужно отобразить/преобразовать каждое значение из источника в возвращаемый целевой объект.

Array.from( arrLike, function mapper(val,idx){
	if (typeof val == "string") { 
		return val.toUpperCase();
	}
	else {
		return idx;
	}
} );
// [ 0, 1, "FOO", 3 ]

3. .from() и .of() в подклассах
Оба метода, of(..) и from(..), используют для создания массива конструктор, из которого они были вызваны. Соответственно, если в качестве основы использовать метод Array.of(..), получится экземпляр класса Array, в то время как метод MyCoolArray.of(..) даст вам экземпляр MyCoolArray.

class MyCoolArray extends Array {
    ..
}
MyCoolArray.from( [1, 2] ) instanceof MyCoolArray;    // true
 
Array.from(
    MyCoolArray.from( [1, 2] )
) instanceof MyCoolArray;                             // false

Это поведение для других методов можно переопределить для методов прототипов с помощью @@species. Методы .of и .from ее не применяют; они задействуют только связывание с помощью ключевого слова this.

class MyCoolArray extends Array {
    // принудительно превращаем `species` в родительский конструктор
    static get [Symbol.species]() { return Array; }
}
 
var x = new MyCoolArray( 1, 2, 3 );
 
x.slice( 1 ) instanceof MyCoolArray;                  // false
x.slice( 1 ) instanceof Array;                        // true

4. .copyWithin(..)
Копирует фрагмент массива в другую позицию, переписывая бывшие там ранее значения.
Аргументы:
- target (индекс позиции, в которую осуществляется копирование),
- start (начальный индекс позиции источника копируемых элементов)
- и по желанию end (конечный индекс позиции источника).

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

[1,2,3,4,5].copyWithin( 3, 0 );            // [1,2,3,1,2]
 
[1,2,3,4,5].copyWithin( 3, 0, 1 );         // [1,2,3,1,5]
 
[1,2,3,4,5].copyWithin( 0, -2 );           // [4,5,3,4,5]
 
[1,2,3,4,5].copyWithin( 0, -2, -1 );       // [4,2,3,4,5]

5. Метод прототипа fill(..)
Аргументы:
- чем заполнить массив,
- начальный индекс (необязательный),
- конечный индекс (необязательный).

var a = Array( 4 ).fill( undefined );
a; // [undefined,undefined,undefined,undefined]

«var a = [ null, null, null, null ].fill( 42, 1, 3 );
a;                                // [null,42,42,null]

6. Методы прототипа find(..) и findIndex(..)
.indexOf() не позволяет контролировать способ сопоставления элементов при поиске и всегда использует строгое сопоставление ===.
.some() позволяет контролировать способ сопоставления, но не возвращает найденный элемент:

var a = [1,2,3,4,5];
 
a.some( function matcher(v){
    return v == "2";
} );                                // true
 
a.some( function matcher(v){
    return v == 7;
} );                                // false

.find() аналогичен .some(), но возвращает найденый элемент:

var a = [1,2,3,4,5];
 
a.find( function matcher(v){
    return v == "2";
} );                                // 2
 
a.find( function matcher(v){
    return v == 7;                  // undefined
});

Аналогично можно искать по сложный элементам массивов.
findIndex(..) работает так же, только возвращает индекс найденного элемента или -1.
Методы обладают вторым необязательным аргументом, позволяющим определять связывание посредством this для переданного в качестве первого аргумента обратного вызова.

7. Методы прототипа entries(), values(), keys()
Массивы могут считаться коллекциями благодаря наличию методов итераторов.

var a = [1,2,3];
 
[...a.values()];                    // [1,2,3]
[...a.keys()];                      // [0,1,2]
[...a.entries()];                   // [ [0,1], [1,2], [2,3] ]
 
[...a[Symbol.iterator]()];          // [1,2,3] - values - итератор по-умолчанию

Объект

1. Object.is(..)
сравнивает значения еще более строгим образом, чем оператор === и позволяет строго идентифицировать значение NaN или -0:

var x = NaN, y = 0, z = -0;
 
x === x;                          // false
y === z;                          // true
 
Object.is( x, x );                // true
Object.is( y, z );                // false

Number.isNaN(x) == Object.is(x,NaN)
(x == 0 && 1 / x === -Infinity) == Object.is(x,-0)

2. Object.getOwnPropertySymbols(..)
извлекает из объектов исключительно их символьные свойства.

var    o = {
    foo: 42,
    [ Symbol( "bar" ) ]: "hello world",
    baz: true
};
 
Object.getOwnPropertySymbols( o );        // [ Symbol(bar) ]

3. Object.setPrototypeOf(..)
задает прототип объекта [[Prototype]] для делегирования поведения:

var    o1 = {        
    foo() { console.log( "foo" ); }
};
var    o2 = {
    // .. определение o2 ..
};
 
Object.setPrototypeOf( o2, o1 );
 
// делегирует в `o1.foo()`
o2.foo();                            // foo

Это равнозначно

var    o1 = {
    foo() { console.log( "foo" ); }
};
 
var    o2 = Object.setPrototypeOf( {
    // .. определение o2 ..
}, o1 );
// делегирует в `o1.foo()`
o2.foo();                            // foo

Разумно задавать [[Prototype]] сразу после создания объекта и не менять его в последствии.

4. Статическая функция Object.assign(..)
копирует свойства из других объектов. Аргументы:
- целевой объект (target),
— объекты-источники (sources), которые обрабатываются в порядке перечисления.
Перечисляемые и собственные (то есть не «унаследованные») ключи каждого источника, в том числе и символы, копируются так же, как и в случае оператора присваивания =. Метод Object.assign(..) возвращает целевой объект.

var    target = {},
    o1 = { a: 1 }, o2 = { b: 2 },
    o3 = { c: 3 }, o4 = { d: 4 };
 
// устанавливаем свойство только для чтения
Object.defineProperty( o3, "e", {
    value: 5,
		enumerable: true,
    writable: false,
    configurable: false
} );
 
// устанавливаем неперечисляемое свойство
Object.defineProperty( o3, "f", {
    value: 6,
    enumerable: false
} );
o3[ Symbol( "g" ) ] = 7;
 
// устанавливаем неперечисляемый символ
Object.defineProperty( o3, Symbol( "h" ), {
    value: 8,
    enumerable: false
} );
 
Object.setPrototypeOf( o3, o4 );

//В целевой объект будут скопированы только свойства a, b, c, e и Symbol("g").
Object.assign( target, o1, o2, o3 );
 
target.a;                // 1
target.b;                // 2
target.c;                // 3
 
Object.getOwnPropertyDescriptor( target, "e" );
// { value: 5, writable: true, enumerable: true,
// configurable: true } e копируется как обычное свойство присваивания, а не как предназначенное только для чтения.

Object.getOwnPropertySymbols( target );
// [Symbol("g")]

Альтернативный способ setPrototypeOf(..) :

var o1 = {
    foo() { console.log( "foo" ); }
};
 
var o2 = Object.assign(
    Object.create( o1 ),
    {
        // .. определение o2 ..
    }
);
 
// делегирует в `o1.foo()`
o2.foo();                // foo

Math

Тригонометрия:
cosh(..) Гиперболический косинус числа.
acosh(..) Гиперболический арккосинус.
sinh(..) Гиперболический синус.
asinh(..) Гиперболический арксинус.
tanh(..) Гиперболический тангенс.
atanh(..) Гиперболический арктангенс.
hypot(..) Квадратный корень суммы квадратов (то есть обобщенный вид теоремы Пифагора).

Арифметика:
cbrt(..) Кубический корень.
clz32(..) Подсчет ведущих нулей в 32-битном бинарном представ­лении.
expm1(..) То же самое, что и exp(x) - 1.
log2(..) Двоичный логарифм числа (с основанием 2).
log10(..) Десятичный логарифм числа.
log1p(..) То же самое, что и log(x + 1).
imul(..) Произведение двух 32-битных целых чисел.

Мета-методы:
sign(..) Возвращает знак числа.
trunc(..) Возвращает целую часть числа.
fround(..) Округляет до ближайшего 32-битного (с одинарной точностью) значения с плавающей точкой.»

Объект Number

1. Статические свойства
Number.EPSILON - Минимальное значение между любыми двумя числами: 2^-52 (погрешность)
Number.MAX_SAFE_INTEGER - Максимальное целое число, которое «безопасно» может быть представлено в JS: 2^53 - 1.
Number.MIN_SAFE_INTEGER - Минимальное целое число, которое «безопасно» может быть представлено в JS: -(2^53 - 1) или (-2)^53 + 1.

2. Статическая функция Number.isNaN(..)
Стандартная глобальная функция isNaN(..) возвращает true для не являющихся числами значений.

Number.isNaN( "NaN" );               // false - исправлено!

3. Статическая функция Number.isFinite(..)
проверяет является ли аргумент конечным числом, не выполняя приведение типа:

var a = NaN, b = Infinity, c = 42, d = "42";
 
Number.isFinite( a );            // false
Number.isFinite( b );            // false
 
Number.isFinite( c );            // true

isFinite( d );                   // true
Number.isFinite( d );            // false

4. Статические функции, связанные с целыми числами

Number.isInteger(..) == (x === Math.floor( x ))
Number.isInteger( NaN );              // false
Number.isInteger( Infinity );         // false

Это позволяет определить:

function isFloat(x) {
    return Number.isFinite( x ) && !Number.isInteger( x );
}
isFloat( 4.2 );                       // true
isFloat( 4 );                         // false
 
isFloat( NaN );                       // false
isFloat( Infinity );                  // false

Number.isSafeInteger(..) проверяет, является ли значение целым и при этом попадает ли оно в диапазон между Number.MIN_SAFE_INTEGER и Number.MAX_SAFE_INTEGER (включительно).

Объект String

1. Методы String.fromCodePoint(..), String#codePointAt(..) и String#normalize(..). Они были добавлены, чтобы улучшить поддержку Unicode.
2. String.raw(..)
3. Функция прототипа repeat(..)

"foo".repeat( 3 );                // "foofoofoo

4. Функции проверки строки startsWith(..), endsWidth(..) и includes(..). Аргументы:
- что искать,
- индекс, с которого искать.

Метапрограмми­рование

Имена функций

Если функции присвоено значение name, то именно оно обычно используется при трассировке стека в инструментах разработчика.
Cвойство name функции - это ее лексическое имя, если оно есть.

var abc = function() {};// abc.name "abc"
(function(){ .. });                  // name:
(function*(){ .. });                 // name:
window.foo = function(){ .. };       // name:
 
class Awesome {
    constructor() { .. }             // name: Awesome
    funny() { .. }                   // name: funny
}
 
var    c = class Awesome { .. };     // name: Awesome
 
var    o = {
    foo() { .. },                    // name: foo
    *bar() { .. },                   // name: bar
    baz: () => { .. },               // name: baz
    bam: function(){ .. },           // name: bam
    get qux() { .. },                // name: get qux
    set fuz() { .. },                // name: set fuz
    ["b" + "iz"]:
        function(){ .. },              // name: biz
    [Symbol( "buz" )]:
        function(){ .. }               // name: [buz]
};
 
var    x = o.foo.bind( o );            // name: bound foo
(function(){ .. }).bind( o );          // name: bound
 
export default function() { .. }       // name: default
var    y = new Function();             // name: anonymous

Метасвойство new.target было описано в части 1.

Известные символы

1. Symbol.iterator

Символ @@itera­tor, который автоматически используется оператором распространения ... и циклом for..of. Его можно переопределять:

var arr = [4,5,6,7,8,9];
// определяем итератор, продуцирующий значения
// только из нечетных индексов
arr[Symbol.iterator] = function*() {
    var idx = 1;
    do {
        yield this[idx];
    } while ((idx += 2) < this.length);
};
 
for (var v of arr) {
    console.log( v );
}
// 5 7 9

2. Symbol.toStringTag и Symbol.hasInstance
Позволяют управлять поведением метода toString() и оператором instanceof для анализ значений и определение их типа.
Символ @@toStringTag прототипа (или самого экземпляра) указывает строковое значение, которое будет использоваться в строковом описании объекта по умолчанию.
Символ @@hasInstance представляет собой метод в функции конструктора, который получает значение экземпляра объекта и возвращает true или false, тем самым показывая, можно ли считать его экземпляром.

function Foo(greeting) {
    this.greeting = greeting;
}
 
Foo.prototype[Symbol.toStringTag] = "Foo";
Object.defineProperty( Foo, Symbol.hasInstance, {
    value: function(inst) {
        return inst.greeting == "hello";
    }
} );

var a = new Foo( "hello" ),
    b = new Foo( "world" );
b[Symbol.toStringTag] = "cool";
 
a.toString();                // [object Foo]
String( b );                 // [object cool]
 
a instanceof Foo;            // true
b instanceof Foo;            // false

3. Symbol.species
@@spe­cies, контролирует, каким конструктором воспользуются встроенные методы класса, котороым нужно породить новые экземпляры.

«class Cool {
    // отдаем `@@species` производному конструктору»
		static get [Symbol.species]() { return this; }
		//...
}
class Awesome extends Cool {
    // принудительно превращаем `@@species` в родительский конструктор
    static get [Symbol.species]() { return Cool; }
}

4. Symbol.toPrimitive
Символ @@toPrimitive свойство любого объектного значения, можно настроить для приведения ToPrimitive, указав соответствующий метод.

var arr = [1,2,3,4,5];
 
arr + 10;                    // 1,2,3,4,510
 
arr[Symbol.toPrimitive] = function(hint) {
    if (hint == "default" || hint == "number") {  // "string", "number" или "default" 
        // суммируем все числа
        return this.reduce( function(acc,curr){
            return acc + curr;
        }, 0 );
    }
};
 
arr + 10;                    // 25

5. Символы регулярных выражений
Переопреление этих символов влияет на работу функций String.prototype.
- @@match: Значение регулярного выражения Symbol.match представляет собой метод, позволяющий целиком или частично совместить строковое значение с указанным регулярным выражением. Оно используется методом String.prototype.match(..).
Если символу Symbol.match присваивается false, то isRegExp вернет false.
- @@replace: Значение регулярного выражения Symbol.replace представляет собой метод, используемый в функции String.prototype.replace(..) для замены внутри строки одного или всех вхождений последовательности символов, совпадающих с указанным шаблоном регулярного выражения.
- @@search: Значение регулярного выражения Symbol.search представляет собой метод, используемый в функции String.prototype.search(..) для поиска внутри строки другой, меньшей строки, совпадающей с заданным регулярным выражением.
- @@split: Значение регулярного выражения Symbol.split представляет собой метод, используемый в функции String.prototype.split(..) для разбиения строки на подстроки в месте или местах расположения разделителей, совпадающих с предоставленным регулярным выражением.

6. Symbol.isConcatSpreadable
Символ @@isConcatSpreadable - булево свойство итерируемого объекта (Symbol.isConcatSpreadable), следует ли расширить его, когда он передается методу массива concat(..):

«var    a = [1,2,3],
    b = [4,5,6];
 
b[Symbol.isConcatSpreadable] = false;
 
[].concat( a, b );                // [1,2,3,[4,5,6]]

7. Symbol.unscopables
- какие свойства могут фигурировать в операторе with в качестве лексических переменных. Вообще with устарел, и свойство тоже перестало быть актуально.

var    o = { a:1, b:2, c:3 },
    a = 10, b = 20, c = 30;
o[Symbol.unscopables] = {
    a: false,
    b: true,	//скрыто из переменных лексической области видимости
    c: false
};
 
with (o) {
    console.log( a, b, c );        // 1 20 3
}

Proxy

Вид объекта, который «заключает в себя» другой, обычный объект. Его перехватчики (traps), которые будут вызываться при выполнении с прокси-объектом различных операций. Пример прокси get, который перехватывает операцию [[Get]] при попытке доступа к свойству объекта:

var obj = { a: 1 },
    handlers = {
        get(target,key,context) {
            // примечание: target === obj,
            // context === pobj
            console.log( "обращение: ", key );
            return Reflect.get(
                target, key, context
            );
        }
    },
    pobj = new Proxy( obj, handlers );
 
obj.a;
// 1
 
pobj.a;
// обращение: a
// 1

Все доступные перехватчики прокси имеют соответствующую функцию Reflect с таким же именем.
Список обработчиков, которые можно задать через прокси для целевого объекта/функции:
- get(..)
Через [[Get]], доступ к свойству — через прокси-объект (Reflect.get(..), оператор свойства . или оператор свойства [ .. ]).
- set(..)
Через [[Set]], значение свойства задается в прокси-объекте (Reflect.set(..), оператор присваивания = или деструктурирующее присваивание, если целью является свойство объекта).
- deleteProperty(..)
Через [[Delete]], свойство удаляется из прокси-объекта (Reflect.deleteProperty(..) или delete).
- apply(..) (если цель — функция)
Через [[Call]], прокси-объект вызывается как обычная функция/метод (Reflect.apply(..), call(..), apply(..) или оператор вызова (..)).
- construct(..) (если цель — функция конструктора)
Через [[Construct]], прокси-объект вызывается как функция конструктора (Reflect.construct(..) или new).
- getOwnPropertyDescriptor(..)
Через [[GetOwnProperty]], из прокси-объекта извлекается дескриптор свойства (Object.getOwnPropertyDescriptor(..) или Reflect.get.OwnPropertyDescriptor(..)).
- defineProperty(..)
Через [[DefineOwnProperty]], в прокси-объекте задается дескриптор свойства (Object.defineProperty(..) или Reflect.defineProperty(..)).
- getPrototypeOf(..)
Через [[GetPrototypeOf]], извлекается [[Prototype]] прокси-объекта (Object.getPrototypeOf(..), Reflect.getPro­to­ty­peOf(..), __proto__, Object#isPrototypeOf(..) или instanceof).
- setPrototypeOf(..)
Через [[SetPrototypeOf]], задается [[Prototype]] прокси-объекта (Object.setPrototypeOf(..), Reflect.setPrototypeOf(..) или __proto__).
- preventExtensions(..) Через [[PreventExtensions]], прокси-объект делается нерасширяемым (Object.preventExtensions(..) или Reflect.preventExtensions(..)).
- isExtensible(..)
Через [[IsExtensible]], проверяется расширяемость прокси-объекта (Object.isExtensible(..) или Reflect.isExten­sible(..)).
- ownKeys(..)
Через [[OwnPropertyKeys]], извлекается набор собственных свойств и/или собственных символьных свойств прокси-объекта (Object.keys(..), Object.getOwnPropertyNames(..), Object.getOwnSymbolProperties(..), Reflect.ownKeys(..) или JSON.stringify(..)).
- enumerate(..)
Через [[Enumerate]], запрашивается итератор для перечислимых собственных и «унаследованных» свойств прокси-объекта (Reflect.enumerate(..) или for..in).
has(..)
Через [[HasProperty]], проверяется наличие у прокси-объекта собственного или «унаследованного» свойства (Reflect.has(..), Object#hasOwnProperty(..) или "prop" в obj).

Обработчики getOwnPropertyDescriptor(..) и defineProperty(..) активируются косвенно при действии обработчика set(..) в момент, когда задается значение свойства.

Прокси не перехватывает следующие операции:

typeof obj;
String( obj );
obj + "";
obj == pobj;
obj === pobj

Отзываемые прокси (revocable proxy)

Отзываемый прокси-объект создается методом Proxy.revocable(..) и позволяет остановить вызовы через прокси. Как только прокси отозван, любые попытки доступа к нему (активация любых его перехватчиков) приведут к ошибке TypeError.
Примером использования отзываемого прокси может служить его передача другой части приложения, которая управляет данными в вашей модели, вместо ссылки на реальную модель самого объекта. При изменении или перемещении этого объекта достаточно отключить прокси, и другая часть программы узнает (путем генерации ошибок), что нужно запросить обновленную ссылку на модель.

var obj = { a: 1 },
    handlers = {
        get(target,key,context) {
            // примечание: target === obj,
            // context === pobj
            console.log( "обращение: ", key );
            return target[key];
        }
    },
    { proxy: pobj, revoke: prevoke } =
        Proxy.revocable( obj, handlers ); //возврощает объект с двумя свойствами: proxy и revoke
 
pobj.a;
// обращение: a
// 1
 
// позднее:
prevoke();
 
pobj.a; // TypeError»

Прокси в начале, прокси в конце

1. прокси в начале (proxy first), сперва мы взаимодействуем именно с прокси: прокси «заключает в себя» целевой объект. Он становится основным объектом, с которым связывается код, а реальный целевой объект остается скрытым/защищенным.

var    messages = [],
    handlers = {
        get(target,key) {
            // строковое значение?
            if (typeof target[key] == "string") {
                // отфильтровываем пунктуацию
                return target[key]
                    .replace( /[^\w]/g, "" );
            }
 
            // передаем все остальное
            return target[key];
        },
        set(target,key,val) {
            // задаются только уникальные строки, нижний регистр
            if (typeof val == "string") {
                val = val.toLowerCase();
                if (target.indexOf( val ) == -1) {
                    target.push(
                        val.toLowerCase()
                    );
                }
            }
            return true;
        }
    },
    messages_proxy =
        new Proxy( messages, handlers );

	// в другом месте:
	messages_proxy.push(
	    "heLLo...", 42, "wOrlD!!", "WoRld!!"
	);
	 
	messages_proxy.forEach( function(val){
	    console.log(val);
	} );
	// hello world
	 
	messages.forEach( function(val){
	    console.log(val);
	} );
	// hello... world!!

2. прокси в конце (proxy last), прокси-объект задействуется только при последнем обращении: целевой объект будет взаимодействовать с прокси, а не наоборот.Реализуется помещением прокси-объекта в цепочку [[Prototype]] основного объекта.

var    handlers = {
        get(target,key,context) {
            return function() {
                context.speak(key + "!");
            };
        }
    },
    catchall = new Proxy( {}, handlers ),
    greeter = {
        speak(who = "someone") {
            console.log( "hello", who );
        }
    };
 
// настраиваем `greeter` на возвращение  к `catchall`
Object.setPrototypeOf( greeter, catchall );
greeter.speak();                            // hello someone
greeter.speak( "world" );                   // hello world
 
greeter.everyone();                         // hello everyone!

3. Защита от «Нет такого свойства/метода»
Для методов get(..) и set(..) операция перенаправляется только в случае существования свойства целевого объекта; в противном случае генерируется ошибка.

var obj = {
        a: 1,
        foo() {
            console.log( "a:", this.a );
        }
    },
    handlers = {
        get(target,key,context) {
            if (Reflect.has( target, key )) {
								return Reflect.get(
                    target, key, context
                );
            }
            else {
                throw "Нет такого свойства/метода!";
            }
        },
        set(target,key,val,context) {
            if (Reflect.has( target, key )) {
                return Reflect.set(
                    target, key, val, context
                );
            }
            else {
                throw "Нет такого свойства/метода!";
            }
        }
    },
    pobj = new Proxy( obj, handlers );
 
pobj.a = 3;
pobj.foo();                  // a: 3
 
pobj.b = 4;                  // Error: Нет такого свойства/метода!
pobj.bar();                  // Error: Нет такого свойства/метода!

Аналогичный вариант для прокси в конце:

var handlers = {
        get() {
            throw "Нет такого свойства/метода!";
        },
        set() {
            throw "Нет такого свойства/метода!";
        }
    },
    pobj = new Proxy( {}, handlers ),
	  obj = {
        a: 1,
        foo() {
            console.log( "a:", this.a );
        }
    };
 
// настраиваем `obj` вернуться к `pobj`
Object.setPrototypeOf( obj, pobj );
 
obj.a = 3;
obj.foo();                   // a: 3
 
obj.b = 4;                   // Error: Нет такого свойства/метода!
obj.bar();                   // Error: Нет такого свойства/метода!

4. Эмуляция расширения Prototype
Если у объекта свойство не обнаруживается, операция [[Get]] автоматически переходит к объекту [[Pro­totype]]. Перехватчик метода get(..) у прокси можно применять для эмуляции [[Proto­type]].
Имитация зацикленной ссылки. На самом деле создать зацикленную цепочку [[Prototype]] нельзя, так как движок сгенерирует сообщение об ошибке.

var handlers = {
			get(target,key,context) {
	            if (Reflect.has( target, key )) {
	                return Reflect.get(
	                    target, key, context
	                );
	            }
	            // имитация зацикленного `[[Prototype]]`
	            else {
	                return Reflect.get(
	                    target[
	                        Symbol.for( "[[Prototype]]" )
	                    ],
	                    key,
	                    context
	                );
	            }
	        }
	    },
			set(..){...}, //аналогично
	    obj1 = new Proxy(
	        {
	            name: "obj-1",
	            foo() {
	                console.log( "foo:", this.name );
	            }
	        },
	        handlers
	    ),
			obj2 = Object.assign(
	    Object.create( obj1 ),
	    {
	        name: "obj-2",
	        bar() {
	            console.log( "bar:", this.name );
	            this.foo();
	        }
	    }
	);
	 
	// имитация зацикленной ссылки `[[Prototype]]`
	obj1[ Symbol.for( "[[Prototype]]" ) ] = obj2;
	 
	obj1.bar();
	// bar: obj-1 <-- через прокси, имитирующий [[Prototype]]
	// foo: obj-1 <-- контекст `this` все еще сохранен
	 
	obj2.foo();
	// foo: obj-2 <-- через [[Prototype]]

Объект obj2 — это [[Prototype]], связанный с obj1 посредством оператора Object.create(..). Для формирования обратной (циклической) ссылки мы создаем свойство у объекта obj1 в месте расположения символа Symbol.for("[[Prototype]]").

5. Эмоляция множественного наследования

var obj1 = {
        name: "obj-1",
        foo() {
            console.log( "obj1.foo:", this.name );
        },
    },
    obj2 = {
        name: "obj-2",
        foo() {
            console.log( "obj2.foo:", this.name );
        },
        bar() {
            console.log( "obj2.bar:", this.name );
        }
    },
    handlers = {
        get(target,key,context) {
            if (Reflect.has( target, key )) {
                return Reflect.get(
                    target, key, context
							 );
            }
            // имитируем множественный `[[Prototype]]`
            else {
                for (var P of target[
                    Symbol.for( "[[Prototype]]" )
                ]) {
                    if (Reflect.has( P, key )) {
                        return Reflect.get(
                            P, key, context
                        );
                    }
                }
            }
        },
				set(..){...} //аналогично
    },
    obj3 = new Proxy(
        {
            name: "obj-3",
            baz() {
                this.foo();
                this.bar();
            }
        },
        handlers);
	 
	// имитируем множественные ссылки `[[Prototype]]`
	obj3[ Symbol.for( "[[Prototype]]" ) ] = [
	    obj1, obj2
	];
	 
	obj3.baz();
	// obj1.foo: obj-3
	// obj2.bar: obj-3

Reflect API

Reflect - объект со статическими функциями, имеющищи однозначные соответствия с методами-обработчиками (перехватчиками), определенных для прокси-объектов.
Методы ведут себя аналогично методам Object, но методы Object пытаются привести целевой объект к объекту, если он им не является, а методы Reflect выдают сообщения об ошибке.

Reflect.getOwnPropertyDescriptor(..);
Reflect.defineProperty(..);
Reflect.getPrototypeOf(..);
Reflect.setPrototypeOf(..);
Reflect.preventExtensions(..);
Reflect.isExtensible(..).

Доступ к ключам объекта и их анализ:
- Reflect.ownKeys(..)
Возвращает список всех собственных ключей (не «унаследованных»), как методы Object.getOwnPropertyNames(..) и Object.getOwnPropertySymbols(..).
- Reflect.enumerate(..)
Возвращает итератор, который производит набор всех ключей, не являющихся символьными (собственных и «унаследованных»), но при этом перечислимых.
- Reflect.has(..)
Проверяет, принадлежит свойство объекту или цепочке [[Prototype]] этого объекта. Например, метод Reflect.has(o,"foo") проверяет наличие строки "foo" в объекте o.
- Reflect.apply(..)
Reflect.apply(foo,thisObj,[42,"bar"]) вызывает функцию foo(..) с thisObj на месте ключевого слова this, передавая в нее в качестве аргументов значения 42 и "bar".
- Reflect.construct(..)
Reflect.construct(foo,[42,"bar"]) == new foo(42,"bar").
- Reflect.get(..)
Reflect.get(o,"foo") == o.foo.
- Reflect.set(..)
Reflect.set(o,"foo",42) == o.foo = 42.
- Reflect.deleteProperty(..)
Reflect.deleteProperty(o,"foo") == o.foo.

Порядок свойств

Порядок гарантирован только для метода Reflect.ownKeys(..) (и, следовательно, для методов Object.getOwnPropertyNames(..) и Object.getOwnPropertySymbols(..)).
1. в возрастающем порядке нумеруются любые собственные свойства, являющиеся целыми индексами.
2. в порядке создания нумеруются имена остальных собственных строковых свойств.
3. в порядке создания нумеруются собственные символьные свойства.

var o = {};
o[Symbol("c")] = "yay";
o[2] = true;
o[1] = true;
o.b = "awesome";
o.a = "cool";

Reflect.ownKeys( o ); // [1,2,"b","a",Symbol(c)]
Object.getOwnPropertyNames( o ); // [1,2,"b","a"]
Object.getOwnPropertySymbols( o ); // [Symbol(c)]

Тестирование функциональных особенностей

Нужен способ изолировать процедуру тестирования от начального этапа компиляции, через который проходит программа.

try {
	new Function( "( () => {} )" );
	ARROW_FUNCS_ENABLED = true;
}
catch (err) {
	ARROW_FUNCS_ENABLED = false;
}

Тестирование позволяет определить, какой из набора JS-файлов следует загрузить. Эта техника называется разделенной поставкой (split delivery).

https://featuretests.io/ - библиотеку можно загрузить к себе на страницу и подгружать новейшие определения тестов, чтобы были доступны все варианты функционального тестирования. По возможности оно выполняется в режиме фоновой обработки с использованием Web Workers для уменьшения издержек производительности. Также применяется средство долговременного хранения LocalStorage, кэширующее результаты таким образом, чтобы их можно было использовать на всех посещаемых вами сайтах, где задействована данная служба. Это резко уменьшает объем тестирования, необходимого для каждого экземпляра браузера. Это гарантирует, что для любой среды будет загружаться только наиболее подходящий код.

Оптимизация хвостовой рекурсии

Хвостовой вызов представляет собой оператор return в вызове функции, после которого не должно происходить ничего, кроме возврата значения.

При вызове одной функции из другой для отдельного управления переменными и состоянием этого второго вызова выделяется второй стековый кадр (stack frame), что увеличивает время работы и требует дополнительной памяти. При рекурсии, глубина стека вызова может легко составить сотни, а то и тысячи уровней. Память утекает.

function bar(x) {
	// не хвостовая рекурсия 
	return 1 + foo( x );
}

После завершения вызова foo(x) необходимо выполнить еще и операцию 1 + .., соответственно, нам приходится сохранять состояние вызова bar(..).

function bar(x) {
	x = x + 1;
	if (x > 10) {
		return foo( x );	// корректная рекурсия
	}
	else {
		return bar( x + 1 );	// корректная рекурсия
	}
}

Решения:

1. Перезапись хвостового вызова

"use strict";
var foo = (function(){
	function _foo(acc,x) {
		if (x <= 1) return acc;
			return _foo( (x / 2) + acc, x - 1 );
	}
	return function(x) {
			return _foo( 1, x );
	};
})();
foo( 123456 ); // 3810376848.5

2. Трамплин (trampolining)

"use strict";
function trampoline( res ) {
	while (typeof res == "function") {
		res = res();
	}
	return res;
}

var foo = (function(){
	function _foo(acc,x) {
		if (x <= 1) return acc;
		return function partial(){
			return _foo( (x / 2) + acc, x - 1 );
		};
	}
	return function(x) {
		return trampoline( _foo( 1, x ) );
	};
})();

foo( 123456 ); // 3810376848.5

3. Разворачивание рекурсии (recursion unrolling)

"use strict";
function foo(x) {
	var acc = 1;
	while (x > 1) {
		acc = (x / 2) + acc;
		x = x - 1;
	}
	return acc;
}
foo( 123456 ); // 3810376848.5

Похожие записи

© 2002-2022 Креограф. Все права защищены законом РФ
 Русский /  English