Один пацан писал все на JavaScript, и клиент, и сервер, говорил что нравится, удобно, читабельно. Потом его в дурку забрали, конечно.
— С просторов интернета
setTimeout
, относятся к web-стандартам. Консоль не стандартизована вовсе — приходится полагаться на стандарт де-факто.Array#forEach, Function#bind, Object.create
и консоли, и есть, например, Node.js, на которой многие уже вовсю используют возможности грядущего ES6.noConflict
, откатывающий изменения, возможность конфликтов с другими библиотеками минимальна, лучше других упомянутых здесь способов подходит как безопасная зависимость для других библиотек.Function#bind
и еще несколькими, игнорируя большую часть стандартов. Что же касается конфликтов, то здесь всё совсем плохо. Подобные библиотеки часто расширяют нативные объекты методами с одним именем, но разной сигнатурой. Во избежание конфликтов, при разработке конечного приложения не стоит применять больше одной библиотеки, расширяющей нативные объекты, не считая полифилов, а при написании библиотеки такие зависимости вообще недопустимы.console.log(Array.from(new Set([1, 2, 3, 2, 1]))); // => [1, 2, 3]
console.log('*'.repeat(10)); // => '**********'
Promise.resolve(32).then(console.log); // => 32
setImmediate(console.log, 42); // => 42
core
, либо в модульную систему. Например, конструктор Promise
доступен как core.Promise
, а метод Array.from
как core.Array.from
. Методы, предназначенные к добавлению в прототип уже существующих, а не добавляемых библиотекой, конструкторов становятся статическими, например, core.String.repeat
это статическая версия метода String.prototype.repeat
.var log = core.console.log;
log(core.Array.from(new core.Set([1, 2, 3, 2, 1]))); // => [1, 2, 3]
log(core.String.repeat('*', 10)); // => '**********'
core.Promise.resolve(32).then(log); // => 32
core.setImmediate(log, 42); // => 42
npm i core-js
// Максимальная сборка:
require('core-js');
// Сборка без расширения нативных объектов:
var core = require('core-js/library');
// Сборка, содержащая только полифилы:
require('core-js/shim');
npm i -g grunt-cli
npm i core-js
cd node_modules/core-js && npm i
grunt build:date,console,library --path=custom uglify
custom.js
, custom.min.js
весом 4.8кб и custom.min.map
. Флаг library
указывает на сборку без расширения нативных объектов. Посмотреть, к какому модулю относится необходимый функционал, можно здесь (последний столбец).-1
, если значение не найдено.[1, 2, 3, 2, 1].indexOf(2); // => 1
[1, 2, 3, 2, 1].lastIndexOf(2); // => 3
[1, 2, 3].forEach(function(val, key){
console.log(val); // => 1, 2, 3
console.log(key); // => 0, 1, 2
});
[1, 2, 3].map(function(it){
return it * it;
}); // => [1, 4, 9]
[1, 2, 3].filter(function(it){
return it % 2;
}); // => [1, 3]
function isNum(it){
return typeof it == 'number';
}
[1, '2', 3].every(isNum); // => false
[1, '2', 3].some(isNum); // => true
function add(a, b){
return a + b;
}
[1, 2, 3].reduce(add); // => 6
[1, 2, 3].reduceRight(add, ''); // => '321'
'string'[2] // => 'r'
, а в старых IE таковыми они не является. В случае применения данных методов в контексте строк, приводим строки к массиву. Для решения этой же проблемы, при необходимости подменяем в старых IE Array#slice
и Array#join
.Array.prototype.map.call('123', function(it){
return it * it;
}); // => [1, 4, 9]
Array.prototype.slice.call('qwe', 1); // => ['w', 'e']
Array.prototype.join.call('qwe', '|'); // => 'q|w|e'
for-in
. Это не только медленно, но и вынуждает, если нужна поддержка IE8-, проверять, является ли ключ собственным — иначе выполните обход не только элементов массива, но и методов его прототипа :)Array.isArray([1, 2, 3]); // => true
Array.isArray(Object.create(Array.prototype)); // => false
null
, можно создать объект без прототипа, что сделать на базе ECMAScript 3 невозможно. Приходится использовать лютый трэш на базе iframe
. Зачем это нам будет раскрыто # во второй части. Опционально принимает объект # дескрипторов, аналогично Object.defineProperties
.function Parent(/*...*/){ /*...*/ }
Parent.prototype = {constructor: Parent /*, ... */}
function Child(/*...*/){
Parent.call(this /*, ...*/);
// ...
}
// Было в ES3 (нутрянка всяких inherit и extend'ов):
function Tmp(){}
Tmp.prototype = Parent.prototype;
Child.prototype = new Tmp;
Child.prototype.constructor = Child;
// Стало с ES5:
Child.prototype = Object.create(Parent.prototype, {constructor: {value: Child}});
var dict = Object.create(null);
dict.key = 42;
console.log(dict instanceof Object); // => false
console.log(dict.toString) // => undefined
console.log(dict.key) // => 42
constructor
, возможно прототипом будет constructor.prototype
. Для объектов, созданных через Object.create
, добавим # символ, содержащий прототип, и будем игнорировать его при # получении ключей. Но на инстансе конструктора, прототип которого был переопределен без указания «правильного» свойства constructor
, Object.getPrototypeOf
будет работать некорректно.var parent = {foo: 'bar'}
, child = Object.create(parent);
console.log(Object.getPrototypeOf(child) === parent); // => true
function F(){}
console.log(Object.getPrototypeOf(new F) === F.prototype); // => true
F.prototype = {constructor: F /*, ...*/};
console.log(Object.getPrototypeOf(new F) === F.prototype); // => true
F.prototype = {};
console.log(Object.getPrototypeOf(new F) === F.prototype); // В IE8- будет работать некорректно
Object.keys
, вроде, всё просто — перебираем объект через for-in
и проверяем, являются ли свойства собственными. Если бы не баг с «неперечисляемыми перечисляемыми» свойствами в IE. Так что приходится проверять наличие таковых свойств отдельно. Аналогично, с дополнительной проверкой по списку имеющихся скрытых свойств, работает и Object.getOwnPropertyNames
.console.log(Object.keys({q: 1, w: 2, e: 3})); // => ['q', 'w', 'e']
console.log(Object.keys([1, 2, 3])); // => ['0', '1', '2']
console.log(Object.getOwnPropertyNames([1, 2, 3])); // => ['0', '1', '2', 'length']
Object#__define[GS]etter__
, но отсутствует Object.defineProperty
, давно вымерли. В старых IE есть возможность создать объект с геттерами / сеттерами через извращения с VBScript, но это отдельная тема. enumerable: false
свойства есть, их не задать, но есть возможность проверить является ли оно таковым, через Object#propertyIsEnumerable. В IE8 есть методы для работы с дескрипторами, но лучше бы их не было (работают только с DOM объектами). Итого, всё, что мы можем сделать для IE8- — заглушки. Установка значения свойства по value
дескриптора в Object.defineProperty и Object.defineProperties да честное получение value
и enumerable
в Object.getOwnPropertyDescriptor.Object.freeze, Object.preventExtensions, Object.seal
? Мало того, что их эмуляция невозможна, можно сделать разве что заглушки, но есть и такая точка зрения:Object.freeze, Object.preventExtensions, Object.seal, with, evalИ я с ней полностью согласен, так что обойдемся без них.
Crazy shit that you will probably never need. Stay away from it.
— Felix Geisendörfer
var fn = console.log.bind(console, 42);
fn(43); // => 42 43
+new Date
.Date.now(); // => 1400263401642
'\n строка \n'.trim(); // => 'строка'
JSON
, то он поддерживается IE8 и, в рамках данной библиотеки, реализовывать его я не вижу смысла. Если он вам понадобится в совсем уж доисторических IE — никто не мешает использовать, например, этот полифил.chrome://flags/#enable-javascript-harmony
), а Node.js запустить с флажком --harmony
. Что-то доступно и без флажка, например, Promise
, WeakMap
и WeakSet
, а начиная с Chrome 38 доступны и Symbol
, Map
, Set
, итераторы. Node.js в этом плане сильно отстаёт, так как, особенно в стабильной ветке, v8 там обновляется редко. Зато её, в отличии от браузера пользователя, вам никто не помешает запустить с флажком.Proxy
(а это одна из самых вкусных возможностей), на базе ECMAScript 5 и уж тем более ECMAScript 3 реализовать невозможно, но большую часть можно реализовать если не полностью, то хотя бы частично и «нечестно».Symbol.iterator
. По умолчанию — es6-shim и es6-symbol. Их легко заменяет наша библиотека, что делает данный препроцессор идеальной её парой. Преобразует код в ECMAScript 5, но, главным образом, это касается стандартной библиотеки — с заглушками методов вроде # Object.defineProperties
почти всё будет работать и в старых IE.var foo = {q: 1, w: 2}
, bar = {e: 3, r: 4}
, baz = {t: 5, y: 6};
Object.assign(foo, bar, baz); // => foo = {q: 1, w: 2, e: 3, r: 4, t: 5, y: 6}
Object.mixin
, который копировал еще и неперечисляемые свойства, учитывал дескрипторы и переназначал родителя, получаемого через ключевое слово super
. Однако, его добавление решили отложить. Его аналог есть в # велосипедной части библиотеки.==
с его приведениями, посмотрим на ===
:NaN === NaN // => false
0 === -0 // => true, но при этом:
1/0 === 1/-0 // => false
NaN
равен NaN
, а +0
и -0
различны. В ECMAScript 6 его хотели вынести наружу как операторы is
и isnt
, но, похоже, поняв, что операторов сравнения в языке уже и так не мало, да и для обратной совместимости, вынесли как метод Object.is. Пример:Object.is(NaN, NaN); // => true
Object.is(0, -0); // => false
Object.is(42, 42); // => true, аналогично '==='
Object.is(42, '42'); // => false, аналогично '==='
NaN
равен NaN
, и, в отличии от предыдущего, -0
равен +0
. Им обеспечивается уникальность ключа # коллекций, он применяется при проверке вхождения элемента в коллекцию через # Array#includes
.Object.setPrototypeOf
. Пример:function Parent(){}
function Child(){}
Object.setPrototypeOf(Child.prototype, Parent.prototype);
new Child instanceof Child; // => true
new Child instanceof Parent; // => true
function fn(){}
Object.setPrototypeOf(fn, []);
typeof fn == 'function'; // => true
fn instanceof Array; // => true
var object = {};
Object.setPrototypeOf(object, null);
object instanceof Object; // => false
__proto__
. На текущий момент, оно поддерживается всеми актуальными браузерами, кроме IE10-, в текущей реализации — геттер / сеттер в прототипе Object
.Object.setPrototypeOf
выглядела бы просто — выдернем сеттер __proto__
и сделаем из него функцию:var setPrototypeOf = Function.call.bind(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set);
__proto__
нельзя использовать как функцию. Для реализации Object.setPrototypeOf
в них остается только установка значения по ключу __proto__
.Object.setPrototypeOf
отсутствует полностью, так как невозможна.Object.setPrototypeOf
не работает, если в цепочке прототипов цели отсутствует Object.prototype
или свойство __proto__
переопределено через, например, Object.defineProperty
.Object#toString
. Тема серьёзная, но о ней мы поговорим # во второй части статьи.Array.from
и Array.of
— дженерики, если они запущены в контексте функции, отличной от Array
, они создают её инстансы. Если есть желание ознакомиться с этим подробней — хорошо описано в этой статье по новым методам массива.Array.from
. Это универсальное приведение к массиву # итерируемых и array-like объектов. Во большинстве случаев он заменит Array.prototype.slice.call
без указания начальной и конечной позиций. Дополнительно, метод принимает опциональный map-коллбэк и контекст его исполнения. В случае передачи итерируемого объекта и без map-коллбэка, результат аналогичен использованию оператора # spread в литерале массива — [...iterable]
. Пример:Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3]
Array.from({0: 1, 1: 2, 2: 3, length: 3}); // => [1, 2, 3]
Array.from('123', Number); // => [1, 2, 3]
Array.from('123', function(it){
return it * it;
}); // => [1, 4, 9]
Array
, как аналог литерала массива []
. Пример:Array.of(1); // => [1]
Array.of(1, 2, 3); // => [1, 2, 3]
function isOdd(val){
return val % 2;
}
[4, 8, 15, 16, 23, 42].find(isOdd); // => 15
[4, 8, 15, 16, 23, 42].findIndex(isOdd); // => 2
[4, 8, 15, 16, 23, 42].find(isNaN); // => undefined
[4, 8, 15, 16, 23, 42].findIndex(isNaN); // => -1
Array(5).map(function(){
return 42;
}); // => [undefined × 5], потому как .map пропускает "дырки" в массиве
Array(5).fill(42); // => [42, 42, 42, 42, 42]
String#contains
, но # Array#includes
утянул его за собой, пока доступен и по старому имени) проверяет вхождение подстроки в строку. String#startsWith и String#endsWith проверяют, начинается ли или заканчивается ли строка на заданную подстроку. Эти 3 метода принимают дополнительный аргумент — стартовую позицию. Пример:'foobarbaz'.includes('bar'); // => true
'foobarbaz'.includes('bar', 4); // => false
'foobarbaz'.startsWith('foo'); // => true
'foobarbaz'.startsWith('bar', 3); // => true
'foobarbaz'.endsWith('baz'); // => true
'foobarbaz'.endsWith('bar', 6); // => true
'string'.repeat(3); // => 'stringstringstring'
enumerable: false
свойства, что скроет свойства объекта от перечисления в for-in
и от Object.keys
, но это не обеспечит надежного сокрытия — ключ не уникален, его можно легко подобрать, могут возникнуть конфликты имен и, из-за необходимости использования Object.defineProperty
с объектом-дескриптором, это довольно громоздко.var Person = (function(){
var NAME = Symbol('name');
function Person(name){
this[NAME] = name;
}
Person.prototype.getName = function(){
return this[NAME];
};
return Person;
})();
var person = new Person('Вася');
console.log(person.getName()); // => 'Вася'
console.log(person['name']); // => undefined
console.log(person[Symbol('name')]); // => undefined, каждый вызов Symbol возвращает уникальный ключ
for(var key in person)console.log(key); // => только 'getName', символы не участвуют в обходе объекта
console.log(typeof Symbol()); // => 'symbol'
Object.getOwnPropertySymbols
возвращает собственные символы объекта, что даёт возможность отладки и низкоуровневых операций вроде клонирования. Хранение по настоящему приватных данных можно реализовать на базе # WeakMap. Хотя, ИМХО, более удачным решением проблемы было бы добавление полностью приватной версии символов.for-in
и не возвращаемых Object.keys
ключей — реализуется довольно просто, например, так:window.Symbol || (function(){
var id = 0;
window.Symbol = function(description){
if(this instanceof Symbol)throw new TypeError('Symbol is not a constructor');
var symbol = Object.create(Symbol.prototype)
, tag = 'Symbol(' + description + ')_' + (++id + Math.random()).toString(36);
symbol.tag = tag;
Object.defineProperty(Object.prototype, tag, {
configurable: true,
set: function(it){
Object.defineProperty(this, tag, {
enumerable : false,
configurable: true,
writable : true,
value : it
});
}
});
return symbol;
}
Symbol.prototype.toString = function(){
return this.tag;
}
})();
Symbol
мы генерируем уникальный ключ-строку, например, "Symbol(description)_m.y9oth2pcqaypsyvi"
, и по этому ключу в Object.prototype
устанавливаем сеттер. При попытке установить значение по строке-ключу, к которой приведется наш «символ», сеттер устанавливает enumerable: false
свойство в текущий объект. Однако, у подобных «символов» есть огромное количество минусов, вот только часть:Object.prototype
: не стоит особо злоупотреблять, может повлиять на производительность.Object.defineProperty
), не будет сеттеров в Object.prototype
, «символы» будут перечисляемы.Object.prototype
(например, Object.create(null)
), «символы» будут перечисляемы.Symbol() in {}
вернет true
— нужно проверять наличие символов в объекте иным способом.typeof Symbol()
будет равен 'object'
— средствами JavaScript мы не можем добавить новый тип данных.Object.getOwnPropertySymbols
— лишняя обертка для Object.getOwnPropertyNames
плохо скажется на производительности, соответственно, Object.getOwnPropertyNames
будет возвращать, в том числе, и «символы».Object.prototype
, данное решение вполне сойдет. Для остальных случаев добавим в библиотеку пару хелперов. Symbol.pure
, если доступны нативные символы, возвращающий символ, нет — возвращающий уникальную строку-ключ без добавления сеттера в Object.prototype
, и Symbol.set
, если доступны нативные символы — просто устанавливающий в объект значение по ключу, нет — устанавливающий значение, используя Object.defineProperty
с enumerable: false
. Таким примитивным образом, избавляемся от половины описанных выше проблем. Использованный выше пример с использованием данных хелперов вместо вызова Symbol
выглядит так:var Person = function(){
var NAME = Symbol.pure('name');
function Person(name){
Symbol.set(this, NAME, name);
}
Person.prototype.getName = function(){
return this[NAME];
};
return Person;
}();
Object.getOwnPropertySymbols
мы не добавляем. А хочется иметь более или менее универсальный, притом стандартизованный, способ для обхода всех ключей, как строк, так и символов. ECMAScript 6 добавляет модуль Reflect
— в первую очередь, набор заглушек для Proxy
. Так как мы не имеем возможность эмулировать Proxy
, модуль Reflect
нам особо и не нужен. Однако в нём есть метод Reflect.ownKeys
, возвращающий все собственные ключи объекта — как строки, так и символы, т.е. Object.getOwnPropertyNames + Object.getOwnPropertySymbols
. Добавим этот метод. Пример:var O = {a: 1};
Object.defineProperty(O, 'b', {value: 2});
O[Symbol('c')] = 3;
Reflect.ownKeys(O); // => ['a', 'b', Symbol(c)]
Symbol.for
ищет в регистре и возвращает символ по ключу-строке, не находит — создаёт новый, добавляет в регистр и возвращает его. Symbol.keyFor
возвращает строку, которой в регистре соответствует переданный символ. Пример:var symbol = Symbol.for('key');
symbol === Symbol.for('key'); // true
Symbol.keyFor(symbol); // 'key'
Symbol.iterator
и # Symbol.toStringTag
.Map, Set, WeakMap
и WeakSet
. Есть еще типизированные массивы, но пока обойдёмся без них.Map
и Set
.forEach
, количество элементов доступно через свойство .size
. Пример:var a = [1];
var map = new Map([['a', 1], [42, 2]]);
map.set(a, 3).set(true, 4);
console.log(map.size); // => 4
console.log(map.has(a)); // => true
console.log(map.has([1])); // => false
console.log(map.get(a)); // => 3
map.forEach(function(val, key){
console.log(val); // => 1, 2, 3, 4
console.log(key); // => 'a', 42, [1], true
});
map.delete(a);
console.log(map.size); // => 3
console.log(map.get(a)); // => undefined
console.log(Array.from(map)); // => [['a', 1], [42, 2], [true, 4]]
Map
, есть возможность обхода. Пример:var set = new Set(['a', 'b', 'a', 'c']);
set.add('d').add('b').add('e');
console.log(set.size); // => 5
console.log(set.has('b')); // => true
set.forEach(function(it){
console.log(it); // => 'a', 'b', 'c', 'd', 'e'
});
set.delete('b');
console.log(set.size); // => 4
console.log(set.has('b')); // => false
console.log(Array.from(set)); // => ['a', 'c', 'd', 'e']
.forEach
, нет свойства .size
. Это еще один способ хранения приватных данных, более «честный», но и более ресурсоёмкий, по сравнению с использованием # символов. Если в будущем в JavaScript таки добавят abstract references, для подобных приватных полей появится удобный синтаксис. Пример:var a = [1]
, b = [2]
, c = [3];
var wmap = new WeakMap([[a, 1], [b, 2]]);
wmap.set(c, 3).set(b, 4);
console.log(wmap.has(a)); // => true
console.log(wmap.has([1])); // => false
console.log(wmap.get(a)); // => 1
wmap.delete(a);
console.log(wmap.get(a)); // => undefined
// Так можно хранить приватные данные
var Person = (function(){
var names = new WeakMap;
function Person(name){
names.set(this, name);
}
Person.prototype.getName = function(){
return names.get(this);
};
return Person;
})();
var person = new Person('Вася');
console.log(person.getName()); // => 'Вася'
for(var key in person)console.log(key); // => только 'getName'
var a = [1]
, b = [2]
, c = [3];
var wset = new WeakSet([a, b, a]);
wset.add(c).add(b).add(c);
console.log(wset.has(b)); // => true
console.log(wset.has([2])); // => false
wset.delete(b);
console.log(wset.has(b)); // => false
Map, Set
и WeakMap
. В ночных сборках появился и WeakSet
. Map
и Set
принимают итерируемый объект. Map
и Set
имеют итераторы и метод .forEach
.WeakMap
и WeakSet
. В более ранних версиях коллекции доступны # с флажком экспериментальных возможностей. До самых последних версий v8 конструкторы не принимали итерируемый объект, а у Map
и Set
отсутствовали итераторы и метод .forEach
, что делало нативные Map
и Set
чуть менее, чем полностью бесполезными.Map, Set
и WeakMap
. Конструкторы не принимают итерируемый объект. Map
и Set
не имеют итераторов, но у них есть метод .forEach
.Map, Set
и WeakMap
. Инициализация итератором отсутствует. Вроде бы, есть методы, возвращающие итераторы, но, внезапно, у некоторых итераторов отсутствует метод next
. Есть forEach
, но метод передаёт в коллбэк не 3 аргумента, как должно быть, а только 1 для Set
и 2 для Map
..add
и .set
не возвращают this
— заполнить коллекцию цепочкой этих методов не получится. Но это легко лечится.Map
? В инстансе Map
— 2 массива, ключи и значения. При получении элемента, ищем совпадение в массиве ключей и возвращаем элемент из массива значений по полученному индексу. Или, для оптимизации удаления, альтернативный вариант — цепочка объектов-вхождений. Что не так и в том, и в другом случае? Это очень медленно, сложность поиска элемента — O(n), сложность операции uniq — O(n2). Чем это нам грозит? Вот небольшой тест:var array = [];
for(var i = 0; i < 100000; i++)array.push([{}, {}]);
array = array.concat(array);
console.time('Map test');
var map = new Map(array);
console.timeEnd('Map test');
console.log('Map size: ' + map.size);
Map
.Map
, например, в Firefox:Map test: таймер запущен
Map test: 46.25мс
Map size: 100000
Map
из самого популярного полифила ECMAScript 6:Map test: таймер запущен
Map test: 506823.31мс
Map size: 100000
Object
, принимающий в качестве ключа исключительно строки. В протестированном выше полифиле есть небольшая оптимизация — поисковый индекс для ключа-строки или числа через простую функцию, уменьшающий среднюю сложность поиска элемента коллекции до O(1):function fastKey(key){
if(typeof key === 'string')return '$' + key;
else if(typeof key === 'number')return key;
return null;
};
Map
, в качестве ключей которого можно эффективно использовать только примитивы? Object.create(null)
с этим # вполне справляется.var STOREID = Symbol('storeId')
, id = 0;
function fastKey(it){
// Возвращаем с префиксом 'S' если строка и с префиксом 'P' если другой примитив
if(it !== Object(it))return (typeof it == 'string' ? 'S' : 'P') + it;
// Если у объекта отсутствует идентификатор - добавляем
if(!Object.hasOwnProperty.call(it, STOREID))it[STOREID] = ++id;
// Возвращаем идентификатор с префиксом 'O'
return 'O' + it[STOREID];
}
Map
не на 2 массивах или цепочке объектов-элементов, а на 2 хэшах Object
, так же для ключей и значений. Другого хранилища ключей / значений для обхода коллекции в порядке добавления нам не нужно: во всех движках ключи объектов хранятся в порядке добавления, за исключением ключей-чисел, но так как здесь у всех ключей префикс-буква, таковые отсутствуют. Итого, получаем:Map test: таймер запущен
Map test: 669.93мс
Map size: 100000
Set
реализуется аналогично, на 1 хэше..forEach
, свойства .size
. В случае хранения ключей и значений в объекте коллекции, она уже не будет слабосвязанной — ключи / значения не будут удаляться, получим просто урезанную версию Set
и Map
. Единственное более или менее разумное решение давно известно — хранить значения на ключе, а в объекте коллекции — только её идентификатор. Так как значения хранятся на ключе, в полифиле теряется полная приватность хранимых данных.window.WeakMap || (function(){
var id = 0
, has = Function.call.bind(Object.prototype.hasOwnProperty)
, WEAKDATA = Symbol('WeakData')
, ID = Symbol('ID');
window.WeakMap = function(){
if(!(this instanceof WeakMap))throw TypeError();
this[ID] = id++;
}
Object.assign(WeakMap.prototype, {
'delete': function(key){
return this.has(key) && delete key[WEAKDATA][this[ID]];
},
has: function(key){
return key === Object(key) && has(key, WEAKDATA) && has(key[WEAKDATA], this[ID]);
},
get: function(key){
if(key === Object(key) && has(key, WEAKDATA))return key[WEAKDATA][this[ID]];
},
set: function(key, value){
if(key !== Object(key))throw TypeError();
if(!has(key, WEAKDATA))key[WEAKDATA] = {};
key[WEAKDATA][this[ID]] = value;
return this;
}
});
})();
// <- тут делаем snapshot 1
var array = [];
for(var i = 0; i < 100000; i++)array[i] = {};
var wm = new WeakMap();
for(var i = 0; i < 100000; i++)wm.set(array[i], {});
// <- тут делаем snapshot 2
array = null;
// <- тут делаем snapshot 3
WeakMap
жили дольше, чем их ключи. Кто-то пытается обойти проблему утечки памяти, но это из разряда эзотерики — память утекает точно в тех же случаях.WeakSet
эта проблема остается, но сведена к минимуму — вместо значения, что может быть тяжелым объектом, на ключе хранится только флаг наличия в коллекции.Symbol.iterator
for-of
$for
, что реализует часть возможностей этих синтаксических конструкций. Если собираетесь использовать библиотеку с # препроцессором ES6+, можете спокойно собирать её без данного модуля..next
, который должен возвращать объект с полями .done
— завершен ли обход итератора, и .value
— значение текущего шага. Пример — метод, создающий итератор для положительного числа, позволяющий обойти все целые от 0 до заданного (песочница):function NumberIterator(number){
var i = 0;
return {
next: function(){
return i < number
? {done: false, value: i++}
: {done: true};
}
}
}
var iter = NumberIterator(3);
iter.next(); // => {done: false, value: 0}
iter.next(); // => {done: false, value: 1}
iter.next(); // => {done: false, value: 2}
iter.next(); // => {done: true}
Symbol.iterator
содержится метод, возвращающий для него итератор. Соответственно, что бы и итератор был итерируемым, по ключу Symbol.iterator
у него должен быть метод, возвращающий this
. Для примера, сделаем числа итерируемыми (песочница):Number.prototype[Symbol.iterator] = function(){
return NumberIterator(this);
}
Array.from(10); // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Symbol.iterator
. В Firefox поддерживается протокол итераторов, но в нём, в стабильных сборках, пока нет # символов и вместо Symbol.iterator
используется строка "@@iterator"
. В ночных сборках появились символы и даже Symbol.iterator
, но в протоколе итераторов пока продолжает использоваться строка "@@iterator"
. Чтобы не сломать протокол итераторов в Firefox, в нем в нашей библиотеке мы будем дублировать методы для получения итератора и по ключу Symbol.iterator
(создаём, если отсутствует), и по ключу "@@iterator"
. В v8 полноценная поддержка протокола итераторов появилась с Chrome 38.Number.prototype[Symbol.iterator] = function*(){
for(var i = 0; i < this;)yield i++;
}
Array.from(10); // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for-of
предназначен для обхода итерируемых объектов. На нашем примере с итерируемыми числами он работает так (песочница):for(var num of 5)console.log(num); // => 0, 1, 2, 3, 4
String, Array, Map, Set
и Arguments
. Кроме того, Array, Map
и Set
имеют методы .keys, .values
и .entries
, которые возвращают итераторы соответственно по ключам, значениям и паре ключ-значение. Core.js добавляет данные итераторы и методы. Вместе с циклом for-of
это выглядит так (песочница):var string = 'abc';
for(var val of string)console.log(val); // => 'a', 'b', 'c'
var array = ['a', 'b', 'c'];
for(var val of array)console.log(val); // => 'a', 'b', 'c'. Итератор по умолчанию - .values
for(var val of array.values())console.log(val); // => 'a', 'b', 'c'
for(var key of array.keys())console.log(key); // => 0, 1, 2
for(var [key, val] of array.entries()){
console.log(key); // => 0, 1, 2
console.log(val); // => 'a', 'b', 'c'
}
var map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for(var [key, val] of map){ // Итератор по умолчанию - .entries
console.log(key); // => 'a', 'b', 'c'
console.log(val); // => 1, 2, 3
}
for(var val of map.values())console.log(val); // => 1, 2, 3
for(var key of map.keys())console.log(key); // => 'a', 'b', 'c'
for(var [key, val] of map.entries()){
console.log(key); // => 'a', 'b', 'c'
console.log(val); // => 1, 2, 3
}
var set = new Set([1, 2, 3, 2, 1]);
for(var val of set)console.log(val); // => 1, 2, 3. Итератор по умолчанию - .values
for(var val of set.values())console.log(val); // => 1, 2, 3
for(var key of set.keys())console.log(key); // => 1, 2, 3. Итератор .keys аналогичен .values
for(var [key, val] of set.entries()){ // У Set в итераторе .entries ключ и значение равны
console.log(key); // => 1, 2, 3
console.log(val); // => 1, 2, 3
}
var list = (function(){return arguments})(1, 2, 3);
for(var val of list)console.log(val); // => 1, 2, 3
arguments
. Цикл работает, но на базе собственного, устаревшего, протокола.for-of
, начиная с Chrome 38, работает корректно (не считая деструкции массива) и доступны все данные итераторы, кроме arguments
. В старых версиях (с флажком) ожидал итератор, а не итерируемый объект.for-of
(песочница):$for(new Set([1, 2, 3, 2, 1])).of(function(it){
console.log(it); // => 1, 2, 3
});
// 2й аргумент $for - флаг entries - в коллбэк передаётся 2 аргумента
$for([1, 2, 3].entries(), true).of(function(key, value){
console.log(key); // => 0, 1, 2
console.log(value); // => 1, 2, 3
});
// 2й аргумент .of - контекст исполнения коллбэка
$for('abc').of(console.log, console); // => 'a', 'b', 'c'
// Можно прервать обход итератора, вернув из коллбэка false
$for([1, 2, 3, 4, 5]).of(function(it){
console.log(it); // => 1, 2, 3
if(it == 3)return false;
});
arguments
— Object.prototype
, так что положить метод для получения его итератора в прототип мы не можем. Также есть вариант сборки core.js как библиотеки, без расширения нативных объектов. По этим причинам, вынесем наружу пару хелперов, для проверки, является ли объект итерируемым и для получения итератора объекта — $for.isIterable(foo)
, как аналог Symbol.iterator in foo
и $for.getIterator(foo)
, как аналог foo[Symbol.iterator]()
:var list = (function(){return arguments})(1, 2, 3);
console.log($for.isIterable(list)); // => true
console.log($for.isIterable({})); // => false
var iter = $for.getIterator(list);
console.log(iter.next()); // => {value: 1, done: false}
console.log(iter.next()); // => {value: 2, done: false}
console.log(iter.next()); // => {value: 3, done: false}
console.log(iter.next()); // => {value: undefined, done: true}
Map, Set, WeakMap, WeakSet
, массивов # через Array.from
, их ожидают методы Promise.all, Promise.race
для работы с # обещаниями.[...new Set([1, 2, 3, 2, 1])]; // => [1, 2, 3]
console.log(1, ...[2, 3, 4]); // => 1, 2, 3, 4
var map = new Map([[1, 'a'], [2, 'b']]);
new Array(...map.keys(), 3); // => [1, 2, 3]
filter
и map
для любых итерируемух объектов. Примеры, кроме последнего — не поддерживается деструкция массива, работают в FF. В 6to5 и Traceur работает всё. Пример:var ar1 = [for(i of [1, 2, 3])i * i]; // => [1, 4, 9]
var set = new Set([1, 2, 3, 2, 1]);
var ar2 = [for(i of set)if(i % 2)i * i]; // => [1, 9]
var iter = (for(i of set)if(i % 2)i * i);
iter.next(); // => {value: 1, done: false}
iter.next(); // => {value: 9, done: false}
iter.next(); // => {value: undefined, done: true}
var map1 = new Map([['a', 1], ['b', 2], ['c', 3]]);
var map2 = new Map((for([k, v] of map1)if(v % 2)[k + k, v * v])); // => Map {aa: 1, cc: 9}
$for
. Вызов $for
возвращает итератор, расширенный методами of
, о котором выше, filter, map
и array
. Методы filter
и map
возвращают итератор, что, соответственно, фильтрует или преобразует значения предыдущего итератора. Этот итератор расширен теми же методами, что и итератор $for
. Метод array
преобразует текущий итератор в массиву, он принимает опциональный map-коллбэк. У всех этих методов второй, опциональный, аргумент — контекст исполнения. Если $for
принимает флаг entries
, все коллбэки цепочки запускаются с парой аргументов.var ar1 = $for([1, 2, 3]).array(function(v){
return v * v;
}); // => [1, 4, 9]
var set = new Set([1, 2, 3, 2, 1]);
var ar1 = $for(set).filter(function(v){
return v % 2;
}).array(function(v){
return v * v;
}); // => [1, 9]
var iter = $for(set).filter(function(v){
return v % 2;
}).map(function(v){
return v * v;
});
iter.next(); // => {value: 1, done: false}
iter.next(); // => {value: 9, done: false}
iter.next(); // => {value: undefined, done: true}
var map1 = new Map([['a', 1], ['b', 2], ['c', 3]]);
var map2 = new Map($for(map1, true).filter(function(k, v){
return v % 2;
}).map(function(k, v){
return [k + k, v * v];
})); // => Map {aa: 1, cc: 9}
$for
, что дало бы универсальный и ленивый (таков протокол итераторов) способ обхода и преобразования итерируемых объектов. Но отложим это на будущее. А может и к чёрту.Dict
ожидает итерируемый объект, но не будем в эту главу перетаскивать совсем уж все велосипеды, связанные с итераторами.setTimeout
и setInterval
, обеспечиваются web-стандартами W3C и WHATWG, # о них поговорим чуть дальше. Разве что функции как объекты первого класса — удобная передача коллбэков. Это порождает коллбэк-ад. Один из способов упрощения параллельного и последовательного исполнения асинхронных функций — библиотеки вроде async.js.try-catch
не работает, приходится передавать ошибки аргументами коллбэка, что иногда еще больше запутывает код. В конце цепочки обещаний можно подписаться на любую ошибку, что может в ней возникнуть — как брошенную методом reject
, так и через throw
..then
(первый аргумент — функция, что выполнится при успешном завершении, второй — при возникновении ошибки).Promise
, принимающий функцию, в которую передается 2 коллбэка — первый разрешает обещание, второй завершает с ошибкой. Кроме then
, обещания ES6 содержат метод catch
— сокращение для then
с пропущенным первым аргументом, с помощью него можно подписаться на ошибку. Пример:var log = console.log.bind(console);
function sleepRandom(time){
return new Promise(function(resolve, reject){
// resolve разрешает обещание успешно, reject - с ошибкой
// разрешим обещание через заданное время
setTimeout(resolve, time * 1e3, 0 | Math.random() * 1e3);
});
}
log('Поехали'); // => Поехали
sleepRandom(5).then(function(result){
log(result); // => 869, через 5 сек.
return sleepRandom(10);
}).then(function(result){
log(result); // => 202, через 10 сек.
}).then(function(){
log('Сразу после прошлого'); // => Сразу после прошлого
throw Error('Ашыпка!');
}).then(function(){
log('не будет выведено - ошибка');
}).catch(log); // => Error: 'Ашыпка!'
process.nextTick
и способы из полифила # setImmediate.Promise.resolve
и Promise.reject
возвращают завершенное, соответственно, успешно или с ошибкой обещание с переданным им значением. Если Promise.resolve
принимает обещание — его и возвращает. Также его можно использовать для преобразования других thenable (например, jQuery Deferred) в обещания, в старой версии стандарта для этого был отдельный метод — Promise.cast
. Пример:Promise.resolve(42).then(log); // => 42
Promise.reject(42).catch(log); // => 42
Promise.resolve($.getJSON('/data.json')); // => ES6 promise
Promise.all
возвращает обещание, которое разрешится, когда разрешатся все обещания из переданной ему # итерируемой коллекции (в v8 сейчас работает только с массивами, исправлять в рамках библиотеки пока не стал — не вижу особого смысла использовать его с чем-то ещё). Элементы коллекции, не являющиеся обещаниями, приводятся к обещаниям через Promise.resolve
. Пример:Promise.all([
'foo',
sleepRandom(5),
sleepRandom(15),
sleepRandom(10) // через 15 секунд выведет что-то вроде
]).then(log); // => ['foo', 956, 85, 382]
Promise.race
похож на предыдущий, но возвращает обещание, которое разрешится, когда разрешится хотя бы одно обещание из переданной ему коллекции. По моему скромному мнению, в отличие от Promise.all
, чуть менее, чем полностью, бесполезен. Разве что с его помощью становится чуть проще повесить ограничение на время разрешения обещания. Пример:function timeLimit(promise, time){
return Promise.race([promise, new Promise(function(resolve, reject){
setTimeout(reject, time * 1e3, Error('Await > ' + time + ' sec'));
})]);
}
timeLimit(sleepRandom(5), 10).then(log); // => через 5 секунд получили результат 853
timeLimit(sleepRandom(15), 10).catch(log); // Error: Await > 10 sec
async / await
и базирующиеся на # генераторах, обещаниях и генераторах, возвращающих обещания :) Данный синтаксис уже поддерживается и Traceur, и 6to5. Пример:var delay = time => new Promise(resolve => setTimeout(resolve, time));
async function sleepRandom(time){
await delay(time * 1e3);
return 0 | Math.random() * 1e3;
}
async function sleepError(time, msg){
await delay(time * 1e3);
throw Error(msg);
}
(async () => {
try {
log('Поехали'); // => Поехали
log(await sleepRandom(5)); // => 936, через 5 сек.
var [a, b, c] = await Promise.all([
sleepRandom(5),
sleepRandom(15),
sleepRandom(10)
]);
log(a, b, c); // => 210 445 71, через 15 сек.
await sleepError(5, 'Ашыпка!');
log('Не будет выведено');
} catch(e){
log(e); // => Error: 'Ашыпка!', через 5 сек.
}
})();
fetch
— простая и удобная обертка над XMLHttpRequest
, возвращающая обещание. Предлагают добавить, и, думаю, добавят, простую, но часто необходимую, функцию delay
, аналогичную функции из предыдущего примера, возвращающую обещание, что разрешится через заданное время — прощай, setTimeout
. Пожалуй, delay
было бы неплохо добавить и в данную библиотеку..length
и элементы по ключу [от 0 до .length
), которые могут быть пропущены — тогда возникают «дырки». Они не содержат под собой Array.prototype
, соответственно не имеют и методов массива. Это объект arguments
, строки (формально — IE8+), типизированные массивы (массивы, но не содержат под собой Array.prototype
), коллекции элементов DOM, jQuery объекты и т.д..concat
не совсем. Думаю, многим знакомы такие конструкции:Array.prototype.slice.call(arguments, 1);
// или
[].slice.call(arguments, 1);
Array.from
, с его помощью можно привести к массиву итерируемые и array-like объекты.Array.from(arguments).slice(1);
Array.slice(arguments, 1);
Array.join('abcdef', '+'); // => 'a+b+c+d+e+f'
var form = document.getElementsByClassName('form__input');
Array.reduce(form, function(memo, it){
memo[it.name] = it.value;
return memo;
}, {}); // => например, {name: 'Вася', age: '42', sex: 'yes, please'}
setTimeout
и setInterval
. Многие не знают, что по стандарту (W3C, WHATWG) эти функции кроме коллбэка и времени задержки, принимают дополнительные аргументы, с какими запускается переданный коллбэк. Но тут, как обычно, проблема в IE. В IE9- setTimeout
и setInterval
принимают только 2 аргумента, это лечится оберткой в несколько строчек.// Было:
setTimeout(log.bind(null, 42), 1000);
// Стало:
setTimeout(log, 1000, 42);
RangeError: Maximum call stack size exceeded
. Это число, в зависимости от платформы, варьирует от нескольких сотен до нескольких десятков тысяч. Асинхронный рекурсивный вызов спасает от переполнения стека. Правда, рекурсивный вызов обычно легко переписывается в обычный цикл, что предпочтительней. setTimeout
с минимальной задержкой, но получится очень неспешно. Минимальная задержка setTimeout
по спецификации — 4 мс, а на некоторых платформах и того больше. Итого, максимум, ~250 рекурсивных вызовов в секунду в современных браузерах и, например, ~64 в IE8. Приходилось, да и приходится, велосипедить, так как есть способы сделать эффективное отложенное исполнение.process.nextTick
, то на клиент помощь пришла откуда не ждали. В IE10 Microsoft добавил метод setImmediate, устанавливающий задачу, выполняемую сразу после того, как отработает ввод / вывод, и предложил его для стандартизации W3C. Позже он появился и на Node.js. FF и Chromium его добавлять не спешат. Популярен такой полифил.process.nextTick
postMessage
MessageChannel
script.onreadystatechange
setTimeout
с минимальной задержкойsetImmediate(function(arg1, arg2){
console.log(arg1, arg2); // => Сообщение будет выведено асинхронно с минимальной задержкой
}, 'Сообщение будет выведено асинхронно', 'с минимальной задержкой');
clearImmediate(setImmediate(function(){
console.log('Сообщение не будет выведено');
}));
setImmediate
, существует его более быстрая альтернатива — концепция asap
(as soon as possible, библиотека) — по возможности, создаём microtask, что выполнится до любого ввода / вывода. Добавить такой глобальный метод в стандарт языка думают и в tc39. Может и его стоит добавить в библиотеку?console
определена только тогда, когда пользователь за ней наблюдает. Ну и, конечно, не все методы доступны на всех платформах. Свою консоль в стиле Firebug Lite мы изобретать не будем, просто сделаем заглушки для методов, дабы можно было их использовать, не проверяя наличия.// Было:
if(window.console && console.log)console.log(42);
// Стало:
console.log(42);
console
, так что если нужно передать их в качестве коллбэка придется привязать — например, # console.log.bind(console)
. В IE, Firebug и на Node.js можно передавать, не привязывая, что гораздо удобней. Соответственно, привяжем методы к объекту console
.// Было:
setTimeout(console.log.bind(console, 42), 1000);
[1, 2, 3].forEach(console.log, console);
// Стало:
setTimeout(console.log, 1000, 42);
[1, 2, 3].forEach(console.log);
console.disable();
console.warn('Консоль отключена, вы не увидите этого сообщения.');
console.enable();
console.warn('Консоль снова включена.');
Вариантов ноль, вариантов нет.
Хотелось полмира — хватило на велосипед.
— Петля пристрастия
Undefined, Null, Boolean, String, Number
и Object
. ECMAScript 6 добавляет еще один тип данных — # Symbol
. Для определения типа данных есть оператор typeof
. Вот только работает он специфично. Так уж исторически сложилось, что typeof null
возвращает 'object'
и попытка исправить это в Harmony успехом не увенчалась. Для типа Object
typeof
возвращает либо 'object'
, либо 'function'
, в зависимости от наличия внутреннего метода [[Call]]
. Итого получаем, что оператор typeof
возвращает именно тип данных только для примитивов, и то не всех.null
просто, достаточно её с ним сравнить, то с Object
придется либо каждый раз писать сотни кода, либо хелпер. Можно, конечно, сделать так — Object(foo) === foo
, но данное решение далеко не самое быстрое — приводит примитив к объекту. В ранних черновиках ES6 присутствовал метод Object.isObject
, но, видимо, из-за попытки исправить typeof null
, был удален. А проверять, является ли переменная объектом, приходится постоянно. Так что добавим хелпер Object.isObject
, реализованный проще некуда (песочница):Object.isObject = function(it){
return it != null && (typeof it == 'object' || typeof it == 'function');
}
// ...
typeof {}; // => 'object'
typeof isNaN; // => 'function'
typeof null; // => 'object'
Object.isObject({}); // => true
Object.isObject(isNaN); // => true
Object.isObject(null); // => false
instanceof
проверяет цепочку прототипов. Если создать объект из прототипа или установить объекту в качестве прототипа Function.prototype
, это его не сделает функцией. Свойство инстанса constructor
не может ничего гарантировать, а constructor.name
мало того, что теряет всякий смысл при сжатии кода, еще и не поддерживается IE. Помочь с классификацией объекта может внутреннее свойство [[Class]]
. Единственный способ выдрать его наружу — хорошо знакомая многим страшная конструкция Object.prototype.toString.call(foo).slice(8, -1)
. Пример:Object.prototype.toString.call(1).slice(8, -1); // => 'Number'
Object.prototype.toString.call([]).slice(8, -1); // => 'Array'
Object.prototype.toString.call(/./).slice(8, -1); // => 'RegExp'
Array.isArray
: Object.isType в Sugar, _.isType в Undescore и т.п.Object.classof
для классификации данных, похожий на оператор typeof!
из LiveScript (пример).Object.prototype.toString.call(foo).slice(8, -1)
есть пара проблем:null, undefined
и arguments
, данная конструкция возвращает "Object"
. Это легко лечится дополнительными проверками."Object"
. Тут на помощь приходит измененная логика работы Object#toString
в ECMAScript 6.[[Class]]
. Object#toString
ES6, через проверку специальных внутренних свойств, возвращает принадлежность переменной к Undefined, Null, Array, String, Arguments, Function, Error, Boolean, Number, Date
или RegExp
, а у остальных ищет подсказку по # символу Symbol.toStringTag
. Если метод находит подсказку и она не является именем одного из встроенных «классов» — возвращает её, не находит — Object
.Object#toString
, благо благодаря # одному противному, но веселому, багу мы можем сделать это и в IE8-, не сломав при этом for-in
. Ну и, конечно, реализуем данный подход в методе Object.classof
. В качестве бонуса мы получаем возможность классификации инстансов пользовательских конструкторов. Пример:var classof = Object.classof;
classof(null); // => 'Null'
classof(undefined); // => 'Undefined'
classof(1); // => 'Number'
classof(true); // => 'Boolean'
classof('string'); // => 'String'
classof(Symbol()); // => 'Symbol'
classof(new Number(1)); // => 'Number'
classof(new Boolean(true)); // => 'Boolean'
classof(new String('string')); // => 'String'
var fn = function(){}
, list = (function(){return arguments})(1, 2, 3);
classof({}); // => 'Object'
classof(fn); // => 'Function'
classof([]); // => 'Array'
classof(list); // => 'Arguments'
classof(/./); // => 'RegExp'
classof(new TypeError); // => 'Error'
classof(new Set); // => 'Set'
classof(new Map); // => 'Map'
classof(new WeakSet); // => 'WeakSet'
classof(new WeakMap); // => 'WeakMap'
classof(new Promise(fn)); // => 'Promise'
classof([].values()); // => 'Array Iterator'
classof(new Set().values()); // => 'Set Iterator'
classof(new Map().values()); // => 'Map Iterator'
classof(Math); // => 'Math'
classof(JSON); // => 'JSON'
function Example(){}
Example.prototype[Symbol.toStringTag] = 'Example';
classof(new Example); // => 'Example'
JSON
, так и минусы.Object.prototype
), ломает базовые операции над словарем. Для объектов же это ограничения на расширение Object.prototype
.Map
. Его быстродействие иногда даже выше, чем у объектов (само собой, не касается полифилов). Вот только ИМХО в большинстве случаев их не заменит. У Map
, в отличии от словарей-объектов, отсутствует простая запись литералом, доступ к свойствам происходит через методы — не такой лаконичный. Map
'ы далеки от любимого всеми JSON
и не столь универсальны. Да и обычно в ключах словаря не требуется ничего, кроме строк.Object.prototype
и словариDict
Object.prototype
, как подсказывает Mozilla Developer Network, в зависимости от реализации, могут находиться:Object.prototype.constructor();
Object.prototype.hasOwnProperty();
Object.prototype.isPrototypeOf();
Object.prototype.propertyIsEnumerable();
Object.prototype.toLocaleString();
Object.prototype.toString();
Object.prototype.valueOf();
Object.prototype.__proto__;
Object.prototype.__count__;
Object.prototype.__parent__;
Object.prototype.__noSuchMethod__;
Object.prototype.__defineGetter__();
Object.prototype.__defineSetter__();
Object.prototype.__lookupGetter__();
Object.prototype.__lookupSetter__();
Object.prototype.eval();
Object.prototype.toSource();
Object.prototype.unwatch();
Object.prototype.watch();
var phone = (function(){
var db = {
'Вася': '+7987654',
'Петя': '+7654321'
};
return {
has: function(name){
return name in db;
},
get: function(name){
return db[name];
},
set: function(name, phone){
db[name] = phone;
},
delete: function(name){
delete db[name];
}
};
})();
console.log(phone.has('Вася')); // => true
console.log(phone.get('Вася')); // => '+7987654'
console.log(phone.has('Дима')); // => false
console.log(phone.get('Дима')); // => undefined
console.log(phone.has('toString')); // => true
console.log(phone.get('toString')); // => function toString() { [native code] }
in
аналогично проверяет его наличие. Давайте добавим / заменим in
на метод hasOwnProperty
, проверяющий наличие свойства в объекте без учета цепочки прототипов. Получаем:// ...
has: function(name){
return db.hasOwnProperty(name);
},
get: function(name){
if(db.hasOwnProperty(name))return db[name];
},
// ...
console.log(phone.get('Вася')); // => '+7987654'
phone.set('hasOwnProperty', '+7666666'); // Добавляем нового "абонента"
console.log(phone.get('Вася')); // TypeError: string is not a function
hasOwnProperty
, отвязанный от объекта. Получаем необходимость использования на каждый чих громоздких проверок. Примерно такой трэш:// ...
has: function(name){
return Object.prototype.hasOwnProperty.call(db, name);
},
get: function(name){
if(Object.prototype.hasOwnProperty.call(db, name))return db[name];
},
// ...
in
.phone.set('__proto__', '+7666666'); // Добавляем нового "абонента"
console.log(phone.get('__proto__')); // => undefined
Object.prototype
есть еще и «магический» геттер / сеттер __proto__
, установка примитива по этому ключу будет игнорироваться, а объекта — повредить, например, при обходе свойств. В старых движках были и другие «магические» свойства. Тут поможет разве что Object.defineProperty
(песочникца):// ...
set: function(name, phone){
Object.defineProperty(db, name, {
enumerable : true,
configurable: true,
writable : true,
value : phone
});
},
// ...
for-in
можно обойтись без проверки hasOwnProperty
. Вот только баг с # «неперечисляемыми перечисляемыми» свойствами всё равно делает обход через for-in
словарей, под которыми находится Object.prototype
, неполноценным в старых IE.Object.create(null)
, с ним можно использовать реализацию методов, предложенную изначально (песочница):var phone = (function(){
var db = Object.create(null);
Object.assign(db, {
'Вася': '+7987654',
'Петя': '+7654321'
});
return {
has: function(name){
return name in db;
},
get: function(name){
return db[name];
},
set: function(name, phone){
db[name] = phone;
},
delete: function(name){
delete db[name];
}
};
})();
Object.assign
, не особо компактны.toString
и valueOf
. Чем это нам грозит?+Object.create(null)
, или строке, '' + Object.create(null)
, — TypeError
==
, с примитивами — TypeError
Dict
Object.create(null)
и его заполнение куда более громоздко, чем при создании словаря через {}
. Конечно, самым красивым решением было бы добавление в язык литерала словаря, но это, по крайней мере, в ближайшей перспективе, маловероятно. Отсутствует возможность инициализации литералом. Есть запись {__proto__: null, foo: 'bar'}
, но поддерживается не везде, на данный момент приводит к деоптимизации кода, да и всё равно довольно громоздкая.Dict
как сокращение Object.create(null)
. Как оно сейчас поживает и поживает ли вообще, я не в курсе. Но почему бы не взять его, слегка расширив? Заодно получим пространство имен для методов для работы с объектами как словарями. Добавим возможность инициализации итератором entries
или объектом без итератора, этакая версия # Array.from
для словарей.Dict() instanceof Dict
работать не будет и # Object.classof(Dict())
будет возвращать 'Object'
, добавим для идентификации словарей метод Dict.isDict
.function Dict(props){
var dict = Object.create(null);
if(props != null){
if(Symbol.iterator in props){
for(var [key, val] of props)dict[key] = val;
} else Object.assign(dict, props);
}
return dict;
}
Dict.prototype = null;
Dict.isDict = function(it){
return Object.isObject(it) && Object.getPrototypeOf(it) === null;
}
// ...
var map = new Map([['a', 1], ['b', 2], ['c', 3]]);
Dict(); // => {__proto__: null}
Dict({a: 1, b: 2, c: 3}); // => {__proto__: null, a: 1, b: 2, c: 3}
Dict(map); // => {__proto__: null, a: 1, b: 2, c: 3}
Dict([1, 2, 3].entries()); // => {__proto__: null, 0: 1, 1: 2, 2: 3}
Dict((for([k, v] of map)if(v % 2)[k + k, v * v])); // => {__proto__: null, aa: 1, cc: 9}
Dict.isDict({}); // => false
Dict.isDict(Dict()); // => true
Dict.has
— банально, статическая версия hasOwnProperty
. В черновиках ECMAScript 6, в модуле Reflect
— наборе заглушек для Proxy
, до недавнего времени присутствовала статическая версия метода hasOwnProperty
— метод Reflect.hasOwn
. Однако в последних версиях черновика спецификации данный метод удалили.Dict.get
— получение значения по ключу с проверкой, является ли свойство собственным. Не является — возвращаем undefined
.Dict.set
— метод для совсем уж параноиков. Позволяет установить свойство словаря, игнорирую сеттеры, такие, как __proto__
. Использует defineProperty
.delete
и так работает как надо.var dict = {a: 1, b: 2, c: 3};
console.log(Dict.has(dict, 'a')); // => true
console.log(Dict.has(dict, 'toString')); // => false
console.log(Dict.get(dict, 'a')); // => 1
console.log(Dict.get(dict, 'toString')); // => undefined
Dict.set(dict, '__proto__', 42);
console.log(Dict.get(dict, '__proto__')); // => 42
forEach, map, some
и т.п.), очень удобны. Их статические аналоги для словарей присутствуют в практически всех фреймворках / библиотеках общего назначения. А вот подвижек с добавлением их в стандарт нет.Dict
. Тут всё просто, методы аналогичны статическим версиям методов массива. Это: Dict.forEach, Dict.map, Dict.filter, Dict.some, Dict.every, Dict.find, Dict.findKey, Dict.keyOf,
# Dict.includes
, Dict.reduce,
# Dict.turn
. Key
в названии соответствует index
у методов массива. «Правые» версии и опциональный аргумент-индекс (пока?) отсутствуют, так как порядок обхода ключей объектов пока не везде одинаков. Перебираются только собственные перечисляемые элементы объекта. Эти методы — дженерики в том же плане, что и Array.from
или Array.of
. Например, Dict.map(dict, fn)
вернет новый Dict
, а Dict.map.call(Object, dict, fn)
— новый Object
. А в общем, всё примитивно, скучно и как везде (песочница):var dict = {a: 1, b: 2, c: 3};
Dict.forEach(dict, console.log, console);
// => 1, 'a', {a: 1, b: 2, c: 3}
// => 2, 'b', {a: 1, b: 2, c: 3}
// => 3, 'c', {a: 1, b: 2, c: 3}
Dict.map(dict, function(it){
return it * it;
}); // => {a: 1, b: 4, c: 9}
Dict.filter(dict, function(it){
return it % 2;
}); // => {a: 1, c: 3}
Dict.some(dict, function(it){
return it === 2;
}); // => true
Dict.every(dict, function(it){
return it === 2;
}); // => false
Dict.find(dict, function(it){
return it > 2;
}); // => 3
Dict.find(dict, function(it){
return it > 4;
}); // => undefined
Dict.findKey(dict, function(it){
return it > 2;
}); // => 'c'
Dict.findKey(dict, function(it){
return it > 4;
}); // => undefined
Dict.keyOf(dict, 2); // => 'b'
Dict.keyOf(dict, 4); // => undefined
Dict.includes(dict, 2); // => true
Dict.includes(dict, 4); // => false
Dict.reduce(dict, function(memo, it){
return memo + it;
}); // => 6
Dict.reduce(dict, function(memo, it){
return memo + it;
}, ''); // => '123'
Dict.turn(dict, function(memo, it, key){
memo[key + key] = it;
}); // => {aa: 1, bb: 2, cc: 3}
Dict.turn(dict, function(memo, it, key){
it % 2 && memo.push(key + it);
}, []); // => ['a1', 'c3']
Dict
их нет, и не предвидятся. Спасением тут могут стать abstract references. А вот в рамках # $for
и # Map
вполне возможно и появятся.for-of
. Вот только объектам как словарям от этого ни тепло, ни холодно — для них в ES6 итераторы не предусмотрены. Соответственно, нет простого способа перебрать их через for-of
, инициализировать # Map
словарем и т.п. Добавление методов .keys, .values
и .entries
в Object.prototype
маловероятно — там и так достаточно мусора, см. описание предыдущей проблемы. Зато вполне вероятны два других сценария:Dict
— Dict.{keys, values, entries}
. Но как уже писал, что с перспективой добавления этого модуля в стандарт мне неизвестно.Object.{values, entries}
, по типу Object.keys
, возвращающих массив, а не итератор, и уже через итератор массива обходить объект.var dict = {a: 1, b: 2, c: 3};
console.log(Object.values(dict)); // => [1, 2, 3]
console.log(Object.entries(dict)); // => [['a', 1], ['b', 2], ['c', 3]]
for(var key of Dict.keys(dict))console.log(key); // => 'a', 'b', 'c'
for(var [key, val] of Dict.entries(dict)){
console.log(key); // => 'a', 'b', 'c'
console.log(val); // => 1, 2, 3
}
$for(Dict.values(dict)).of(console.log); // => 1, 2, 3
new Map(Dict.entries(dict)); // => Map {a: 1, b: 2, c: 3}
new Map((for([k, v] of Dict.entries(dict))if(v % 2)[k + k, v * v])); // => Map {aa: 1, cc: 9}
Dict
не просто сокращением для Object.create(null)
с возможностью инициализации итератором и объектом, а полноценным конструктором с прототипом, не содержащим ключей-строк, только # символы. Примерно так:function Dict(props){
if(!(this instanceof Dict))return new Dict(props);
if(props != null){
if(Symbol.iterator in props){
for(var [key, val] of props)this[key] = val;
} else Object.assign(this, props);
}
}
Dict.prototype = Object.create(null);
Dict.prototype[Symbol.toStringTag] = 'Dict';
Dict.prototype[Symbol.iterator] = function(){
return Dict.entries(this);
};
new Dict instanceof Dict
.for(var [key, value] of dict){...}, new Map(dict)
без необходимости получать итератор через Dict.entries
.Object.classof(new Dict)
возвращал бы 'Dict'
, а не 'Object'
.Dict
туда попадёт.Function#bind
. Вот только возможности частичного применения данный метод раскрывает далеко не полностью. В этой главе мы рассмотрим такие вещи, как:Function#bind
совмещает в себе частичное применение и привязку контекста this
. Последнее нужно далеко не всегда, и в этом случае привязываемый this
не только «лишний» аргумент, что нужно писать. Если контекст, в котором должна запускаться частично применённая функция, заранее неизвестен, метод Function#bind
неприменим. Например, если это метод прототипа (песочница):Array.prototype.compact = [].filter.bind(Array.prototype, function(val){
return val != null;
});
[0, null, 1, undefined, 2].compact(); // => [] - метод запускается в контексте Array.prototype, а не данного массива
// Придется частично применять вручную:
Array.prototype.compact = function(){
return this.filter(function(val){
return val != null;
});
};
[0, null, 1, undefined, 2].compact(); // => [0, 1, 2];
this
— Function#part
(песочница):Array.prototype.compact = [].filter.part(function(val){
return val != null;
});
[0, null, 1, undefined, 2].compact(); // => [0, 1, 2];
var fn = console.log.part(1, 2);
fn(3, 4); // => 1, 2, 3, 4
Function#bind
нам помочь не сможет — придется писать обертку вручную под каждый конкретный случай.function fn1(a, c){
console.log(a, 2, c, 4);
};
fn1(1, 3); // => 1, 2, 3, 4
function fn2(b, c){
console.log(1, b, c, 4);
};
fn2(2, 3); // => 1, 2, 3, 4
_
(как, например, в LiveScript, песочница), однако, эту переменную используют библиотеки Undescore.js (кстати, в ней это тоже плейсхолдер для _.partial
) и LoDash как свой неймспейс. Во избежание конфликтов с ними, создаём новый глобальный объект _
только если таковой отсутствует, а во время работы используем глобальный объект _
, что бы там ни лежало. В случае сборки без расширения нативных объектов, в качестве плейсхолдера используем объект core._
. Пример:var fn1 = console.log.part(_, 2, _, 4);
fn1(1, 3); // => 1, 2, 3, 4
var fn2 = console.log.part(1, _, _, 4);
fn2(2, 3); // => 1, 2, 3, 4
fn1(1, 3, 5); // => 1, 2, 3, 4, 5
fn1(1); // => 1, 2, undefined, 4
Function#by
, аналогичный Function#bind
, но с возможностью использования плейсхолдера для аргументов. Можно было бы обернуть Function#bind
, заставив работать с плейсхолдером, но это — нарушение спецификации, да и метод этот и так довольно тормозной почти во всех движках.var fn = console.log.by(console, _, 2, _, 4);
fn(1, 3, 5); // => 1, 2, 3, 4, 5
fn(foo.bar.baz.bind(foo.bar))
. Мы вынуждены писать foo.bar
2 раза, это явное нарушение принципа DRY. Надеюсь, в будущем от этой проблемы спасут abstract references, но предложенная реализация проблему не решает. Пожалуй, самым вкусным и красивым решением было бы добавление в язык оператора доступа с сохранением контекста, аналогичного ~
из LiveScript — fn(foo.bar~baz)
(песочница)._.bindKey
из LoDash (но с ранним связыванием), однако он тоже довольно громоздкий и еще больше ухудшает читаемость, либо аналогичный по функционалу метод в Object.prototype
, например, Object#boundTo
из Eddy.js.Object.prototype
. Рисковать, расширяя Object.prototype
методом по короткому ключу-строке, мы, по крайней мере, пока, не будем — сложно избежать конфликтов, да и сломаем for-in
в IE8-. Ранее в этой главе мы уже использовали глобальную переменную _
. Дабы не плодить лишних сущностей и для краткости, применим её и здесь. Заменим у объекта _
метод toString
(соответственно, если использовать совместно с Undescore.js или LoDash — нужно подключать core.js после них). Он будет возвращать уникальный ключ-строку, аналогично # ключу полифила символа. Добавим по этому ключу метод в Object.prototype
. За счет использования грязного хака с # веселым багом, мы добавляем этот метод и в IE8-, при этом не сломав for-in
.fn(foo.bar[_]('baz'))
— далековато от идеала, но хоть от второго упоминания объекта избавились. Возвращаемый метод кэшируется. Примеры:['foobar', 'foobaz', 'barbaz'].filter(/bar/[_]('test')); // => ['foobar', 'barbaz']
var has = {}.hasOwnProperty[_]('call');
console.log(has({key: 42}, 'foo')); // => false
console.log(has({key: 42}, 'key')); // => true
var array = []
, push = array[_]('push');
push(1);
push(2, 3);
console.log(array); // => [1, 2, 3];
tie, boundTo, bindKey
или что-то в этом духе, подобрав наименее конфликтный ключ.Proxy
из ES6 здесь было бы куда симпатичней — обычный доступ к свойству вместо передачи ключа в метод — fn(foo.bar[_].baz)
, но без Proxy
подобное (геттер, обход объекта и привязка всех методов), серьёзно не теряя при этом в производительности, мы пока себе позволить не можем.var _ = Symbol();
Object.defineProperty(Object.prototype, _, {
get: function(){
return new Proxy(this, {
apply: function(){ /* аналогично текущей логике [_] для обратной совместимости */ },
get: function(context, name){
return context[name].bind(context);
}
});
}
});
['foobar', 'foobaz', 'barbaz'].filter(/bar/[_].test); // => ['foobar', 'barbaz']
var has = {}.hasOwnProperty[_].call;
console.log(has({key: 42}, 'foo')); // => false
console.log(has({key: 42}, 'key')); // => true
var array = []
, push = array[_].push;
push(1);
push(2, 3);
console.log(array); // => [1, 2, 3];
parseInt
— довольно невразумительный, никто не мешает приводить строки к числу, например, используя Number
, что не ожидает дополнительных аргументов. Дело тут не в их «опасности», а в необходимости писать лишнюю обертку.[1, 2, 3].forEach(console.log);
// => 1 0 [1, 2, 3]
// => 2 1 [1, 2, 3]
// => 3 2 [1, 2, 3]
.forEach
, как и многие другие, передаёт коллбэку необязательные аргументы — индекс и сам массив. А нам они не нужны. Так что каждый раз коллбэк придется оборачивать в еще одну функцию:[1, 2, 3].forEach(function(it){
console.log(it);
}); // => 1, 2, 3
Function#only
. Реализуем его вариант. Первый аргумент — максимальное число аргументов, второй, опциональный — контекст. Пример:[1, 2, 3].forEach(console.log.only(1)); // => 1, 2, 3
var date = new Date;
function lz2(it){
return it > 9 ? it : '0' + it;
}
var format = [date.getDate(), date.getMonth() + 1, date.getFullYear()].map(lz2).join('.') + ' ' +
[date.getHours(), date.getMinutes(), date.getSeconds()].map(lz2).join(':');
console.log(format); // => '18.11.2014 06:07:25 '
Date#toLocaleString, Date#toLocaleDateString, Date#toLocaleTimeString
, добавляя в них 2 аргумента: локализация и опции формата. Используя их, строку, близкую к упомянутому выше формату, можно получить так:new Date().toLocaleString('ru-RU', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
second: '2-digit'
}); // => 'вторник, 18 ноября 2014 г., 6:07:25'
Intl
. Здесь же весь модуль работы с датой — несколько десятков строк.Date#format
и его UTC версию Date#formatUTC
(песочница):new Date().format('W, D MM Y г., h:mm:ss', 'ru'); // => 'Вторник, 18 Ноября 2014 г., 6:07:25'
new Date().formatUTC('W, D MM Y г., h:mm:ss', 'ru'); // => 'Вторник, 18 Ноября 2014 г., 0:07:25'
s | Секунды | 0-59
ss | Секунды, 2 цифры | 00-59
m | Минуты | 0-59
mm | Минуты, 2 цифры | 00-59
h | Часы | 0-23
hh | Часы, 2 цифры | 00-23
D | Дата | 1-31
DD | Дата, 2 цифры | 01-31
W | День недели, строка | Вторник
N | Месяц | 1-12
NN | Месяц, 2 цифры | 01-12
M | Месяц, строка | Ноябрь
MM | Месяца, строка | Ноября
Y | Год, полный | 2014
YY | Год, 2 цифры | 14
ru
) и английскую (en
) локали. Локаль задается либо методом core.locale
, либо вторым аргументом методов Date#format
и Date#formatUTC
(песочница):new Date().format('W, D MM Y', 'ru'); // => 'Вторник, 18 Ноября 2014'
new Date().format('W, D MM Y'); // => 'Tuesday, 18 November 2014'
core.locale('ru');
new Date().format('W, D MM Y'); // => 'Вторник, 18 Ноября 2014'
core.addLocale
, но из-за возможности сборки библиотеки без расширения нативных объектов, универсальный модуль-локаль будет выглядеть так:(typeof core != 'undefined' ? core : require('core-js/library')).addLocale('ru', {
weekdays: 'Воскресенье,Понедельник,Вторник,Среда,Четверг,Пятница,Суббота',
months: 'Январ:я|ь,Феврал:я|ь,Март:а|,Апрел:я|ь,Ма:я|й,Июн:я|ь,Июл:я|ь,Август:а|,Сентябр:я|ь,Октябр:я|ь,Ноябр:я|ь,Декабр:я|ь'
});
new Date().format('DD.NN.YY'); // => '18.11.14'
new Date().format('hh:mm:ss'); // => '06:07:25'
new Date().format('DD.NN.Y hh:mm:ss'); // => '18.11.2014 06:07:25'
new Date().format('W, D MM Y года'); // => 'Вторник, 18 Ноября 2014 года'
new Date().format('D MM, h:mm'); // => '18 Ноября, 6:07'
new Date().format('M Y'); // => 'Ноябрь 2014'
Object.defineProperty
/ Object.defineProperties
, что вынуждает передавать на каждое свойство полный (если не хочется таких дополнительных опций, как неперечисляемость или невозможность переопределить) объект дескриптора, а это громоздко.Object.assign
, в ECMAScript 6 планировалось добавить метод Object.mixin
, который копировал свойства объекта-источника в целевой объект с учетом дескрипторов. Кроме этого, метод должен был переназначать родителя методов объекта-источника, получаемого через ключевое слово super
. Однако, его решили переработать и отложили добавление в стандарт.Object.define
, работающий как описанный Object.mixin
— копирующий свойства объекта-источника в цель с учетом дескрипторов, но не переопределяющий родителя, за отсутствием ключевого слова super
в ECMAScript 5.// Было:
Object.defineProperty(target, 'c', {
enumerable: true,
configurable: true,
get: function(){
return this.a + this.b;
}
});
// Стало:
Object.define(target, {
get c(){
return this.a + this.b;
}
});
Object.create
. Было бы неплохо добавлять собственные свойства объекта при создании, но вторым аргументом Object.create
, как и Object.defineProperties
, принимает объект, содержащий объекты дескрипторов свойств, что страшно громоздко.Object.make
— аналог Object.create
, вторым аргументом ожидающий не объект дескрипторов, а простой объект, из которого копируются собственные свойства в создаваемый объект с учетом дескрипторов.// Поверхностное копирование объекта с учетом прототипа и дескрипторов:
var copy = Object.make(Object.getPrototypeOf(src), src);
// Пример с наследованием:
function Vector2D(x, y){
this.x = x;
this.y = y;
}
Object.define(Vector2D.prototype, {
get xy(){
return Math.hypot(this.x, this.y);
}
});
function Vector3D(x, y, z){
Vector2D.apply(this, arguments);
this.z = z;
}
Vector3D.prototype = Object.make(Vector2D.prototype, {
constructor: Vector3D,
get xyz(){
return Math.hypot(this.x, this.y, this.z);
}
});
var vector = new Vector3D(9, 12, 20);
console.log(vector.xy); // => 15
console.log(vector.xyz); // => 25
vector.y++;
console.log(vector.xy); // => 15.811388300841896
console.log(vector.xyz); // => 25.495097567963924
Object.defineProperties
и Object.create
и, в некоторой степени, альтернатива нашим Object.make
и Object.define
. Вот только слишком громоздкая.Array#contains
, переименован из-за бага MooTools, пока доступен и по старому имени) планируется к добавлению в ECMAScript 7. Он банально проверяет вхождение элемента в массив. В отличии от Array#indexOf
, использует алгоритм сравнения # SameValueZero и не игнорирует «дырки». Второй, опциональный, аргумент — стартовая позиция. Примеры:[1, 2, 3].includes(2); // => true
[1, 2, 3].includes(4); // => false
[1, 2, 3].includes(2, 2); // => false
[NaN].indexOf(NaN); // => -1
[NaN].includes(NaN); // => true
Array(1).indexOf(undefined); // => -1
Array(1).includes(undefined); // => true
Array#turn
— плод моей больной фантазии. Хотя, как выяснилось, не уникальный — ему аналогичен метод _.transform
из LoDash. Это альтернатива методу Array#reduce
для свёртки массива в произвольный объект-аккумулятор (по умолчанию — новый массив) без необходимости возвращать аккумулятор из коллбэка. Сигнатура метода и коллбэка аналогична Array#reduce
. Можно прервать обход коллекции, вернув из коллбэка false
. Примеры:// Свёртка в словарь:
[1, 2, 3, 4, 5].reduce(function(memo, it){
memo['key' + it] = !!(it % 2);
return memo;
}, {}); // => {key1: true, key2: false, key3: true, key4: false, key5: true}
[1, 2, 3, 4, 5].turn(function(memo, it){
memo['key' + it] = !!(it % 2);
}, {}); // => {key1: true, key2: false, key3: true, key4: false, key5: true}
// filter + map + slice, делаем лишнюю работу:
[1, 2, 3, 4, 5, 6, 7, 8, 9].map(function(it){
return it * it;
}).filter(function(it){
return it % 2;
}).slice(0, 2); // => [1, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9].turn(function(memo, it){
it % 2 && memo.push(it * it);
if(memo.length == 2)return false;
}); // => [1, 9]
for-of
, простая генерация массива заданной длины через # Array.from
(а может и # spread) и т.д. Так что добавим итератор чисел, хоть и не в такой примитивной реализации. Примеры:// Классический цикл:
for(var i = 0; i < 3; i++)console.log(i); // => 0, 1, 2
// for-of с итератором числа:
for(var i of 3)console.log(i); // => 0, 1, 2
// При отсутствии for-of, хелпер:
$for(3).of(console.log); // => 0, 1, 2
// Генерация массива заданной длины:
// .map пропускает "дырки" в массиве
Array(10).map(Math.random); // => [undefined × 10]
// ES5 костыль, пара вспомогательных массивов:
Array.apply(undefined, Array(10)).map(Math.random); // => [0.9442228835541755, 0.8101077508181334, ...]
// ES6 костыль, заполняем вспомогательный массив:
Array(10).fill(undefined).map(Math.random); // => [0.5587614295072854, 0.009569905698299408, ...]
// Number Iterator:
Array.from(10, Math.random); // => [0.9817775336559862, 0.02720663254149258, ...]
Array.from(10); // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Array.from(10, function(it){
return this + it * it;
}, .42); // => [0.42, 1.42, 4.42, 9.42, 16.42, 25.42, 36.42, 49.42, 64.42, 81.42]
// Comprehensions:
[for(i of 10)if(i % 2)i * i]; // => [1, 9, 25, 49, 81]
Dict((for(i of 3)['key' + i, !(i % 2)])); // => {key0: true, key1: false, key2: true}
$for(10).filter(function(i){
return i % 2;
}).array(function(i){
return i * i;
}); // => [1, 9, 25, 49, 81]
Dict($for(3).map(function(i){
return ['key' + i, !(i % 2)];
})); // => {key0: true, key1: false, key2: true}
Number.prototype
— из разряда приятных мелочей. Точно также как в Sugar и MooTools, вынесем методы из объекта Math
в Number.prototype
. Тут и говорить особо нечего — контекст становится первым аргументом математической функции. Может и дублирует уже имеющийся, стандартизованный, функционал, но это довольно удобно :)Number#random
. Он возвращает случайное число между числом-контекстом и переданным аргументом (по умолчанию — 0).3..pow(3); // => 27
(-729).abs().sqrt(); // => 27
10..random(20); // => Случайное число (10, 20), например, 16.818793776910752
10..random(20).floor(); // => Случайное целое [10, 19], например, 16
var array = [1, 2, 3, 4, 5];
array[array.length.random().floor()]; // => Случайный элемент массива, например, 4
escapeHTML
и unescapeHTML
в прототипе строки. Не будем нарушать данную традицию:'<script>doSomething();</script>'.escapeHTML(); // => '<script>doSomething();</script>'
'<script>doSomething();</script>'.unescapeHTML(); // => '<script>doSomething();</script>'
RegExp
, где-то метод String.prototype
. Давно обсуждается добавление такого метода в ECMAScript. Надеюсь, мы дождемся этого, а пока реализуем предложенный вариант в нашей библиотеке:RegExp.escape('Привет -[]{}()*+?.,\\^$|'); // => 'Привет \-\[\]\{\}\(\)\*\+\?\.\,\\\^\$\|'
Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.
Комментарии (40)