⬆️ ⬇️

Reflections on the standard JavaScript library. Core.js

One boy wrote everything in JavaScript, and the client and the server said what he liked, convenient, readable. Then they took him to Durkee, of course.

- From the Internet


What is it for me? Fun stuff - javascript. The basis of the modern web and on the frontend has no alternatives as such.



JavaScript is, among other things, the standard library , which will be discussed here. By standard library, I mean modules, constructors, methods that must be present on any platform, be it a browser or server, without undue action from the programmer, not including the platform-specific API. Even if you do not write in JavaScript, but in the language compiled into it, most likely you will have to deal with its standard library.



Vanilla JavaScript standard library is generally not bad. This is not only a standard library on the specification of the ECMA-262 language of current versions - from 3 to draft 6. Part of the API is in separate specifications, for example, the ECMA-402 internationalization API. Many features, without which JavaScript is hard to imagine, for example, setTimeout , belong to web standards. The console is not standardized at all - you have to rely on the de facto standard.

')

That's just not so standard, it is different everywhere. There are old IE in which we get out of the box the standard ES3 library of the 90-bearded year even without Array#forEach, Function#bind, Object.create and consoles, and there is, for example, Node.js, on which many are already using the possibilities of the future ES6.



I want to have a universal, truly standard library, both on the server and in any browser, as closely as possible with modern standards, as well as implementing the necessary functionality, which (for now?) Is not standardized. The article is devoted to the core.js library - the implementation of my thoughts on the standard JavaScript library. In addition, this article is also a cheat sheet on the modern standardized standard JavaScript library and notes on its prospects.



Content, or what we get at the output:





I warn you, mnogabukf and the first chapters are quite banal, there is no desire to read everything - the table of contents is higher, scroll to the section you are interested in.



# Approaches




There are three main approaches to creating libraries, which can be called standard, in JavaScript:



The first is the use of only polyfiles , only standardized functionality . Reinforced concrete confidence that over time, the API will not break. To work with such a library, its knowledge is not needed, you only need knowledge of the corresponding API language. Usually, polyphiles are limited to one standard or part of it. For example, es5-shim or es6-shim . For this reason, in order to provide the features that I would like to have by default, you have to connect several polyfiles. Their internal components often duplicate each other, so this set often grows to hundreds of kilobytes. And not all the possibilities that I would like to have are standardized. The possibility of conflicts with other libraries is insignificant, but I would not use them as a dependency when writing a library.



The second is a set of utilities in its own namespace . Either export in a modular system, or the creation of a single global object. For example, Undescore or its fork LoDash . Usually, quite compact, but the possibilities are limited to a set of simple utilities. Since it does not extend native objects and the noConflict method is often present, which rolls back changes, the possibility of conflicts with other libraries is minimal, better than the other methods mentioned here as a safe dependency for other libraries.



The third is the extension of native objects not only by standardized functionality . For example, adding your own methods to an array prototype, which is usually more convenient than passing an array to a function. Now in this category is driving Sugar , in its time - MooTools and Prototype . They add a lot of useful functionality, but often the methods almost completely duplicate each other. Here polyfiles should be turned around - but from polyphiles such libraries are usually limited to methods of array prototype, Function#bind and several more, ignoring most of the standards. As for conflicts, everything is very bad here. Such libraries often extend native objects with methods with the same name but different signatures. In order to avoid conflicts, when developing the final application you should not use more than one library that extends native objects, not counting polyfiles, and when writing a library, such dependencies are generally unacceptable.



Instead of one universal standard library, to provide the features that we would like to have without unnecessary problems, we have to pull the hodgepodge of Undescore / LoDash / Sugar + es5-shim, es6-shim, es6-symbol, setImmediate.js / asap, Moment.js / Intl.js, console stub ... and so on.



# We will try to take the best from each of these approaches. The core.js concept is :





# In the case of a normal build, working with core.js is quite obvious:



 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 


# In the case of assembly without extending native objects, the functionality is exported either to the global core object or to the modular system. For example, the Promise constructor is available as core.Promise , and the Array.from method as core.Array.from . Methods designed to add existing constructors to the prototype, rather than those added by the library, become static, for example, core.String.repeat is a static version of the String.prototype.repeat method.



 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 


An assembly containing only polyfiles, respectively, adds them only. Actually, in the example with the usual assembly only polifila and used.



# Installation on Node.js:



 npm i core-js 


You can connect to choose one of the assemblies:



 //  : require('core-js'); //     : var core = require('core-js/library'); // ,   : require('core-js/shim'); 


# Build for the browser :





# If none of these assemblies suits you, you can make your own. For example, you need only a console module and simple date formatting, and without extending native objects. To do this, put Node.js, then install grunt-cli, core-js with the dependencies necessary for the build and build:



 npm i -g grunt-cli npm i core-js cd node_modules/core-js && npm i grunt build:date,console,library --path=custom uglify 


As a result, we will get the custom.js , custom.min.js weighing 4.8kb and custom.min.map . The library flag indicates a build without extending native objects. You can see which module the required functionality belongs to here (last column).



Part One: Crutches


If someone does not understand, under crutches, in the context of the article, we mean the standardized functional polyfiles available in the library. So, let's go:



# ECMAScript 5




Perhaps everyone knows that adds ECMAScript 5 to the standard library. Almost all browsers that do not support ES5 have become extinct. Except for old IE. Until now, customers often ask for support for IE8, and in the most extreme cases, even IE6. I hope the situation will change soon. The most popular ES5 polyfil is this es5-shim , some of the features are present in Sugar, MooTools, Prototype, but only a part. Since this is far from new, we can do without unnecessary details - a brief description and, if necessary, some features of the implementation. Needless to say, it is important to remember that if the code is written with IE8 support, there is no question of working with descriptors.



# Array methods



# Let's start with the prototype array methods. This is all known:



Array # indexOf returns the index of the first element equal to the specified value, or -1 if the value is not found.

Array # lastIndexOf is similar to the previous one, but returns the index of the last element.

Array # forEach calls a function for each element of the array.

Array # map returns a new array with the result of calling the function for each element of the given array.

Array # filter returns a new array with all the elements of this array that satisfy the condition of the checking function.

Array # every checks if each element in the array satisfies the condition of the checking function.

Array # some checks if there is at least one array element that satisfies the condition of the checking function.

Array # reduce performs a convolution of an array using the function, from the left - to the right.

Array # reduceRight performs a convolution of an array using the function, on the right - on the left.



 [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' 


These methods are implemented elementary, but there is one feature. The array methods are generics and can be called in the context of not only an array, but also any array-like object, more information about this will be # below . So, according to the ES5 specification, strings are array-like objects, the letter of a string can be obtained by index, for example, 'string'[2] // => 'r' , and in old IE they are not. In the case of applying these methods in the context of strings, we cast the strings to an array. To solve this problem, if necessary, we replace Array#slice and Array#join in old IE.



 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' 


Well, don't forget the ancient truth: never go around an array with a for-in loop . This is not only slow, but also compelling, if IE8- support is needed, to check whether the key is proper - otherwise, bypass not only the array elements, but also the methods of its prototype :)



# Static method Array.isArray belongs to the same category. The method checks whether an object is an array not by a chain of prototypes, but by an internal class. Useful, but not universal. We will talk about the classification of objects in detail in the second, bicycle, part of the article .



 Array.isArray([1, 2, 3]); // => true Array.isArray(Object.create(Array.prototype)); // => false 


# Object API



Full emulation of all methods of ECMAScript 5 object API based on ECMAScript 3 is impossible, partial - possible for many. ES5 adds the following categories of methods to the object API: working with a prototype (creating from / receiving), obtaining object keys, working with descriptors.



# The Object.create method creates an object from the prototype. By passing null , you can create an object without a prototype, which cannot be done on the basis of ECMAScript 3. It is necessary to use fierce thrash based on iframe . Why this will be revealed to us in the second part . Optionally accepts an object # of descriptors , similarly to 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 


# The Object.getPrototypeOf method returns the prototype of the object. In ECMAScript 3, there is no guaranteed way to get a prototype object. If the object contains a constructor property, the constructor will probably be the prototype. For objects created through Object.create , add the # symbol containing the prototype, and we will ignore it when receiving keys . But on the instance of the constructor, the prototype of which was redefined without specifying the “correct” property of the constructor , Object.getPrototypeOf will not work correctly.



 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-    


# The Object.keys method returns an array of the object's own enumerated keys. Object.getOwnPropertyNames returns an array of the object's own keys, incl. and non-listed. With Object.keys , it seems, everything is simple - we Object.keys over the object through for-in and check if the properties are proper. If it were not for the bug with "non-enumerable enumerable" properties in IE. So you have to check the availability of such properties separately. Similarly, with an additional check on the list of available hidden properties, Object.getOwnPropertyNames also 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'] 


# With descriptors everything is bad, ECMAScript 3 does not support them. There is no possibility to set getters / setters. Browsers with Object#__define[GS]etter__ , but Object.defineProperty missing, have long since become extinct. In old IE, it is possible to create an object with getters / setters through perversions with VBScript , but this is a separate topic. enumerable: false properties are enumerable: false , they are not set, but it is possible to check whether it is such through Object # propertyIsEnumerable . In IE8, there are methods for working with descriptors, but it would be better if they did not exist (they work only with DOM objects). So, all we can do for IE8- is a stub. Setting the property value descriptor value in Object.defineProperty and Object.defineProperties and honestly getting the value and enumerable in Object.getOwnPropertyDescriptor .



# What about Object.freeze, Object.preventExtensions, Object.seal ? Not only is their emulation impossible, you can only do stubs, but there is such a point of view:

Object.freeze, Object.preventExtensions, Object.seal, with, eval

Crazy shit Stay away from it.

- Felix Geisendörfer

And I completely agree with her, so we can do without them.



# Other



# ECMAScript 5 adds context binding and basic partial usage through Function # bind . The method is wonderful, but for revealing the potential of partial application and context binding in JavaScript alone there is little of it, the topic is covered in detail in the relevant section .



 var fn = console.log.bind(console, 42); fn(43); // => 42 43 


# The Date.now method returns the current time in numeric representation, the result of execution is similar to +new Date .



 Date.now(); // => 1400263401642 


# String # trim method removes whitespace from the beginning and end of a string.



 '\n  \n'.trim(); // => '' 


As for the JSON module, it is supported by IE8 and, within the framework of this library, I see no point in implementing it. If you need it in absolutely prehistoric IE - no one bothers to use, for example, this polyfill.



# ECMAScript 6




The ECMAScript 5 specification was written hastily instead of the unaccepted ECMAScript 4 specification and did little expand on the ECMAScript 3 adopted in the past millennium. Now the much more seriously expanding language, including and the standard library, ECMAScript 6 specification. Adding new features to it is frozen, all serious changes go to ECMAScript 7 sentences, recently most changes to the draft specification are bug fixes. So in our standard library we will focus mainly on ES6.



What with its support in current engines can be clearly seen from this table .





The most popular ES6 polyfil is es6-shim from paulmillr .



Part of the standard ECMAScript 6 library, for example, Proxy (and this is one of the most delicious features), based on ECMAScript 5 and even more so ECMAScript 3 is impossible to implement, but most can be implemented if not completely, then at least partially and unfairly.



# A little about ECMAScript 6+ preprocessors



As for the syntax, then, within the framework of this library, we cannot add its support.However, preprocessors that convert ECMAScript 6+ syntax to ECMAScript 3 or 5 come to the rescue. A huge amount of them, let's consider only a couple of popular ones.



There is such an old and powerful project - Google Traceur . It generates unreadable code and uses a rather heavy runtime, so I refused to use it.



Another project seems to me much more attractive - 6to5 . The project is fresh and develops amazingly fast. It generates easily readable code and does not use its own runtime, the exception being the runtime regenerator , which it uses to compile the generators. But instead, it actively uses the standard ES6 library - for example, #Symbol.iterator . Default -es6-shim and es6-symbol . Our library easily replaces them, which makes this preprocessor an ideal pair of it. Converts code to ECMAScript 5, but mainly it concerns the standard library - with stub methods like #Object.defineProperties almost everything will work in old IE.



With the use of the preprocessor and polyphyl standard library, you can start using ECMAScript 6 to its fullest now. Except, perhaps, some small things.



Sketched a very simple sandbox with the ability to compile ES6 via 6to5 and the connected core.js library , . , ECMAScript 6, ECMAScript 5.






ECMAScript 6. , # , # , # # , .



# Object.assign



. Object.assign () . :



 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} 


It was also planned to add a method Object.mixinthat also copied non-enumerable properties, took into account descriptors and reassigned the parent obtained via the keyword super. However, it was decided to postpone its addition. Its counterpart is in the # bicycle part of the library.



# Object.is



Comparison operators in JavaScript generally behave quite strangely. Forget even such an operator, as ==with his ghosts, look at ===:



 NaN === NaN // => false 0 === -0 // => true,   : 1/0 === 1/-0 // => false 


Just for this case, the language has an internal comparison algorithm SameValue . For it NaNis equal NaN, and, +0and -0different. In ECMAScript 6, they wanted to bring it out as operators isand isnt, it seems, having understood that comparison operators in the language are already quite a few, and for backward compatibility, they were rendered as an Object.is method . Example :



 Object.is(NaN, NaN); // => true Object.is(0, -0); // => false Object.is(42, 42); // => true,  '===' Object.is(42, '42'); // => false,  '===' 


# Also in ES6 and later, another comparison algorithm is actively used - SameValueZero , for which it NaNis equal to NaN, and, unlike the previous one, it -0is equal to +0. They ensure the uniqueness of the # collections key ; it is used when checking whether an item is in the collection through #Array#includes .



# Object.setPrototypeOf



In ES6, a method appears for installing a prototype of an existing object Object.setPrototypeOf. Example :



 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 


, , , , , — , , — , ECMAScript 5. — __proto__ . , , IE10-, — / Object .



, , Object.setPrototypeOf — __proto__ :



 var setPrototypeOf = Function.call.bind(Object.getOwnPropertyDescriptor(Object.prototype, '__proto__').set); 


However, here another problem appears - in old, but somewhere else actual, versions of v8, the setter __proto__cannot be used as a function. To implement Object.setPrototypeOfthem, it remains only to set the value by key __proto__.



That's how we live:




Also, ECMAScript 6 and our library changes the logic of work Object#toString. The topic is serious, but we'll talk about it in the second part of the article .



# Array methods



Static methods Array.fromand Array.of- generics, if they are running in the context of a function other than Array, they create its instances. If there is a desire to get acquainted with this in more detail, it is well described in this article on new array methods .



# ECMAScript 6 adds a very useful methodArray.from. This is a universal cast to an array of # iterable and array-like objects. In most cases, it will replaceArray.prototype.slice.callwithout specifying the starting and ending positions. Additionally, the method accepts an optional map callback and its execution context. In the case of the transfer of the object being iterated and without a map callback, the result is similar to the use of the # spread operatorin the array literal -[...iterable]. Example :



 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] 


# Unlike the previous one, the Array.of method iscurrently practically useless. It is needed, first of all, for subclassesArray, as an analogue of an array literal[]. Example :



 Array.of(1); // => [1] Array.of(1, 2, 3); // => [1, 2, 3] 


# The Array # find and Array # findIndex methods search the array by calling a callback. Example :



 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 method # fill fill the array with the passed value. Optional arguments are starting and ending positions. Example :



 Array(5).map(function(){ return 42; }); // => [undefined Ă— 5],   .map  ""   Array(5).fill(42); // => [42, 42, 42, 42, 42] 


# String methods



It's simple. String # includes (until recently - String#contains, but #Array#includes drew it along, as long as it is available and by the old name) checks the occurrence of the substring in the string. String # startsWith and String # endsWith check if the string begins or ends for a given substring. These 3 methods take an additional argument - the starting position. Example :



 '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 


The String # repeat method returns a string repeated a specified number of times. Example :



 'string'.repeat(3); // => 'stringstringstring' 


The library does not add ECMAScript 6/7 methods for better support of multibyte characters and an honest #thread iterator; the array iterator is used for strings. Now they are not only because I personally do not need them. It would be nice to add them in the near future.



# Working with numbers



ECMAScript 6 . , : Number.EPSILON , Number.parseFloat , Number.parseInt , Number.isFinite , Number.isInteger , Number.isNaN , Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.isSafeInteger , Math.acosh , Math.asinh , Math.atanh , Math.cbrt , Math.clz32 , Math.cosh , Math.expm1 , Math.hypot , Math.imul , Math.log1p , Math.log10 , Math.log2 , Math.sign , Math.sinh , Math.tanh , Math.trunc .



# ECMAScript 6: Symbols




JavaScript . , , . ECMAScript 5, enumerable: false , for-in Object.keys , — , , , - Object.defineProperty -, .



ECMAScript 6 — Symbol , Name . . :



 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 . , , .



v8, Chrome 38 ( — # ) Firefox, 33. IE, .



# Of course, a full-featured polyfill of characters based on ES5 is not possible, but the basic features — creating uniquekeys thatdo not participate in crawling an object throughfor-innon-returnedObject.keyskeys — are fairly simple, for example:



 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; } })(); 


When Symbolwe call, we generate a unique key string, for example "Symbol(description)_m.y9oth2pcqaypsyvi",, and by this key we Object.prototypeset the setter. When trying to set the value by the key string, to which our “symbol” is brought, the setter sets the enumerable: falseproperty to the current object. However, these "characters" have a huge number of minuses, only a part:





# , ? , 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; }(); 


# As noted above,Object.getOwnPropertySymbolswe do not add themethod. And you want to have a more or less universal, moreover, standardized way to bypass all keys, both strings and characters. ECMAScript 6 adds a moduleReflect - first of all, a set of stubs forProxy. Since we do not have the ability to emulateProxy,Reflectwe don’t really need themodule. However, it has a methodReflect.ownKeysthat returns all the object's own keys — both strings and symbols, i.e.Object.getOwnPropertyNames + Object.getOwnPropertySymbols. Add this method. Example :



 var O = {a: 1}; Object.defineProperty(O, 'b', {value: 2}); O[Symbol('c')] = 3; Reflect.ownKeys(O); // => ['a', 'b', Symbol(c)] 


# Also ES6 adds such a dubious thing as the global case of characters. There are a couple of methods for working with it - Symbol.for and Symbol.keyFor . Symbol.forsearches in a register and returns a character by key-string, does not find it - creates a new one, adds it to the register and returns it. Symbol.keyForreturns the string which in the register corresponds to the passed character. Example :



 var symbol = Symbol.for('key'); symbol === Symbol.for('key'); // true Symbol.keyFor(symbol); // 'key' 


In addition, the library actively uses the symbols #Symbol.iterator and #Symbol.toStringTag .



# ECMAScript 6: Collections




In ECMAScript 6, 4 new types of collections appear: Map, Set, WeakMapand WeakSet. There are also typed arrays , but for now we can do without them.





# So, what are these collections?



# Map - collection key - value, any JavaScript entities can act as keys - both primitives and objects. It is possible to bypass - they have # iterators and method.forEach, the number of elements is available through the property.size. Example :



 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]] 


# Set - collection of unique values. Like uMap, there is the possibility of a detour. Example :



 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'] 


# WeakMap is a key-value collection, only objects can act as keys. It uses a weak link — when the key object is deleted (by the garbage collector), the key-value pair is removed from the collection. There is no way around - there is no iterator and method.forEach, there is no property.size. This is another way to store private data, more “honest”, but also more resource-intensive , compared with the use of # symbols . If in the future JavaScript will add abstract references , a convenient syntax will appear for such private fields. Example :



 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' 


# WeakSet - well, you understand. Appeared in the draft specification relatively recently, so it has a rather weak browser support. Example :



 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 


All of these collections should provide sublinear search time. Key uniqueness is provided by the # SameValueZero comparison algorithm .



# What's with the support of these collections in modern engines js? Not bad at all.





In almost all current implementations, the collection methods .addand .setdo not return this- to fill the collection with a chain of these methods will not work. But it is easily treated.



To initialize the collection with an iterator, it is also sufficient to wrap the constructor, which will create the collection and add elements. About the iterators themselves, let's talk in the next chapter .



Well, the polyphiles of the collections themselves will be considered further. Full implementation of these collections - fast, clean and without memory leaks (for WeakMap), based on ECMAScript 5 is impossible, however, you can find a reasonable compromise.



# Implementation of Map and Set



What do most polyphils represent implementations of Map? In the instance Map- 2 arrays, keys and values. When an item is received, we look for a match in the array of keys and return the element from the array of values ​​at the resulting index. Or, to optimize the deletion, an alternative option is a chain of entry objects. What is wrong in both cases? This is very slow, the complexity of the search element - O (n), the complexity of the operation uniq - O (n 2 ). What does it threaten us with? Here is a small test:



 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); 


We create an array of 200,000 thousand pairs of objects (future key-value), 100,000 of which are unique, and then create a collection from this array Map.



Test native Map, for example, in Firefox:



 Map test:   Map test: 46.25 Map size: 100000 


And now it’s Mapfrom the most popular ECMAScript 6 polyfile:



 Map test:   Map test: 506823.31 Map size: 100000 


About 8.5 minutes. When you try to add each new item you have to go through up to 100,000 already added ones. From this we can conclude that this approach is suitable only for very small collections.



Polyline sublinear speed can be achieved using hash tables. In ECMAScript 5 it is only Objectthat accepts only strings as keys. In the polyfile tested above, there is a small optimization - the search index for a key-string or a number through a simple function, which reduces the average difficulty of searching for a collection item to O (1):



 function fastKey(key){ if(typeof key === 'string')return '$' + key; else if(typeof key === 'number')return key; return null; }; 


Similarly, you can implement quick access for other primitives. That's just why you need Map, as keys which you can effectively use only primitives? Object.create(null)it perfectly copes with this .



Just take and get a unique string identifier for the key object will not work. Therefore, it is necessary to slightly break the rules. Let's add a symbol with an identifier to key objects, if necessary. Like that:



 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 


, , , , . , - — # frozen- , . Set , 1 .



#



Weakly coupled collections are even easier to implement. They have no iterators, methods .forEach, properties .size. In the case of storing keys and values ​​in the collection object, it will no longer be loosely coupled - the keys / values ​​will not be deleted, we will get just a trimmed version Setand Map. The only more or less reasonable solution has long been known - to keep the values ​​on the key, and in the collection object only its identifier. Since values ​​are stored on the key, full privacy of the stored data is lost in the polyfile.



Strongly simplified implementation looks like this:
 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; } }); })(); 


Let's make sure that when deleting a key reference it does not remain in the collection and, accordingly, the memory does not leak:



 // <-   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 






But in some cases, the problem of memory leaks remains. After deleting a collection object, the value will remain associated with the key, which will lead to a memory leak until the object that was the key is deleted. So, in a good way, the system should be designed in such a way that the collections would WeakMaplive longer than their keys. Someone is trying to circumvent the problem of memory leakage, but this is from the category of esotericism - memory leaks exactly in the same cases .



The implementation of WeakSetthis problem remains, but minimized - instead of the value that can be a heavy object, only the presence flag in the collection is stored on the key.



# ECMAScript 6: Iterators




In ECMAScript 6, an iterator protocol appeared - a universal way to bypass collections, and not only. Since it includes syntactic constructions, we will consider them as well. But, first of all, it is not part of the standard library or syntax, but a concept. The protocol iterators include:





Since part of this is syntax, in this chapter we will also consider a module $forthat implements some of the capabilities of these syntactic constructions. If you are going to use the library with the ES6 + # preprocessor , you can safely build it without this module.



# Iterator - an object that has a method.nextthat should return an object with fields.done- whether the iterator traversal is completed, and.valueis the value of the current step. An example is a method that creates an iterator for a positive number, allowing you to bypass all integers from 0 to the given ( sandbox ):



 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} 


# Iterated object (Iterable) - an object that has a keySymbol.iteratorcontaining a method that returns an iterator for it. Accordingly, in order for an iterator to be iterable,Symbol.iteratorit should have a method that returns akeythis. For example, let's make numbers iterable ( sandbox ):



 Number.prototype[Symbol.iterator] = function(){ return NumberIterator(this); } Array.from(10); // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 


Pay attention to Symbol.iterator. Firefox supports the iterator protocol, but in it, in stable assemblies, there are no # characters and the Symbol.iteratorstring is used instead "@@iterator". Symbols and even appeared in the nightly assemblies Symbol.iterator, but the string is still used in the iterator protocol "@@iterator". In order not to break the protocol of iterators in Firefox, in it in our library we will duplicate methods for getting an iterator both by key Symbol.iterator(create, if absent), and by key "@@iterator". In v8, full support for the iterator protocol appeared with Chrome 38.



# A generator is a function that can be suspended. Returns an object with an extended iterator interface. We will not discuss them in detail - syntax - see, for example, in this article. For the preprocessor, perhaps the most terrible part of ECMAScript 6. The example with iterated numbers when using the generator looks quite simple ( sandbox ):



 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] 


# The loop isfor-of designed to bypass the iterated objects. In our example with iterated numbers, it works like this ( sandbox ):



 for(var num of 5)console.log(num); // => 0, 1, 2, 3, 4 


# In ECMAScript 6 distortions are iterableString, Array, Map, SetandArguments. In addition,Array, MapandSethave methods.keys, .valuesand.entries, which return iterators, respectively, by key, value, and key-value pair. Core.js adds data iterators and methods. Together with the cyclefor-ofit looks like this ( sandbox ):



 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 




# , , ES6+, , , 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} 


# In ECMAScript 6, iterators are used to initialize # collections Map, Set, WeakMap, WeakSet , arrays # throughArray.from , they expect methodsPromise.all, Promise.raceto work with # promises .



# The spread operator, applicable when calling functions, constructors, and in an array literal, also expects an object to be iterated, but this is already a topic quite distant from the standard library. There is a desire to use already now - preprocessors in the teeth. Example :



 [...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] 


# Everything else, the array / generator comprehensions protocol is also an iterator protocol( array / generator abstraction? ). Previously, they were present in the draft ECMAScript 6, but postponed to ECMAScript 7, although they have long been supported in Firefox . This is the ability to generate an array or iterator from an iterated object with filtering and conversion. Those. syntax filterand mapfor any iterated objects . Examples, except for the latter - the array destruction is not supported, they work in FF . In 6to5 and Traceur everything works. Example :



 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} 


As for me - amazing thing. That's just the syntax too.



# For those who do not intend to use the ES6 preprocessor, we will add this, quite a bicycle, within the module$for. The call$forreturns an iterator, extended by methodsof, about which,filter, mapandarray. Methodsfilterandmapreturn an iterator that, respectively, filters or converts the values ​​of the previous iterator. This iterator is extended by the same methods as the iterator$for. The methodarrayconverts the current iterator into an array; it accepts an optional map callback. For all these methods, the second, optional, argument is the execution context. If it$foraccepts the flagentries, all the chain callbacks are started with a couple of arguments.



Example :



 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} 


With the literal function from ES5 comes out pretty cumbersome, but with the arrow functions will be almost the same as using the syntax comprehensions.



It would be possible to add other operations on iterators to the module $for, which would give a universal and lazy (such is the iterator protocol) method of traversing and transforming iterable objects. But postpone it for the future. And maybe to hell.



The library adds a couple of iterators ( # once , # two ), the constructor #Dict expects the object to be iterated, but we will not drag all the bicycles associated with the iterators into this chapter.



# ECMAScript 6: Promises




Asynchrony and JavaScript for many are almost synonyms. That's just about asynchrony in the ECMAScript 5 standard there is nothing at all. Even such basic methods, as setTimeoutwell as setInterval, are provided by the W3C and WHATWG web standards, # let 's talk about them a little further . Unless functions as objects of the first class - convenient transfer of callbacks. This generates a callback ad . One way to simplify parallel and sequential execution of asynchronous functions is libraries like async.js .



Another approach to simplifying asynchronous programming is the Promise template. With the help of the promising object that an asynchronous function can return, you can subscribe to its result. Methods by which you can subscribe to the result, return new promises, which helps to better structure the code, building promises in chains. Also, promises solve the problem of error handling: in asynchronous code it try-catchdoes not work, you have to pass errors with callback arguments, which sometimes confuses the code even more. At the end of the chain of promises, you can subscribe to any mistake that may arise in it - either abandoned by the method rejector through throw.



Promise libraries such as Q or RSVP are popular . Over time, the standard appearedPromises / A + , according to which all promises are resolved asynchronously, and the result can be subscribed using the method .then(the first argument is a function that will be executed upon successful completion, the second - if an error occurs).



And so, in order to finally standardize work with asynchronous code, in ECMAScript 6 was added the implementation of promises , compatible with the standard Promises / A + and with minimal, but covering most of the needs, functionality. I will not paint them in particular, I can get acquainted with them in detail here ( translation, but slightly outdated ), here or here . ES6 Promise is already available in v8 and Firefox, there are polyfiles - es6-promise andnative-promise-only .



# The implementation of promises from ECMAScript 6 is a constructorPromisethat accepts a function to which 2 callbacks are passed - the first one resolves the promise, the second one completes with an error. In additionthen, the promises of ES6 contain a methodcatch- an abbreviation forthenwith the first argument omitted; with it, you can subscribe to an error. Example :



 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: '!' 


Since I was too lazy to completely adapt my promise engine to the standard, the core core of core.js promises is based on the native-promise-only library, from the code of which there is not much left. To ensure asynchrony is used process.nextTickand the methods of the # setImmediate polyfil .



# A pair of helpersPromise.resolveandPromise.rejectreturn a promise completed, respectively, successfully or with an error with the value passed to them. If hePromise.resolveaccepts a promise, he returns it. It can also be used to convert other thenables (for example, jQuery Deferred) into promises, in the old version of the standard there was a separate method for thisPromise.cast. Example :



 Promise.resolve(42).then(log); // => 42 Promise.reject(42).catch(log); // => 42 Promise.resolve($.getJSON('/data.json')); // => ES6 promise 


# HelperPromise.allreturns a promise that will be resolved when all promises from the # iterated collectiontransferred to it are resolved(in v8 now it works only with arrays, I haven’t yet fixed it within the library - I don’t see much point in using it with something else). Elements of the collection that are not promises are given to promises throughPromise.resolve. Example :



 Promise.all([ 'foo', sleepRandom(5), sleepRandom(15), sleepRandom(10) //  15   -  ]).then(log); // => ['foo', 956, 85, 382] 


# HelperPromise.raceis similar to the previous one, but returns a promise that will be resolved when at least one promise from the collection given to it is resolved. In my humble opinion, unlikePromise.all, a little less than completely, useless. Is that with its help it becomes a little easier to hang the limit on the time of the resolution of the promise. Example :



 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 


# , JavaScript , ECMAScript 7 , 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 . } })(); 


Over time, promises will change much of the standard asynchronous JavaScript API. Already standardized ( polyfil ) global function fetch- a simple and convenient wrapper over XMLHttpRequest, returning a promise. Offer to add, and I think that will add a simple, but often necessary, a function delaysimilar to the function in the previous example, returns a promise that is resolved after a specified time - goodbye setTimeout. Perhaps delayit would be nice to add to this library.



# Mozilla JavaScript: Static Versions of Array Methods




Above, we looked at the iterator protocol. That's just not the only standard for crawling collections in JavaScript. There is much more simple, fast and ancient. These are array-like objects.



In addition to arrays, there are many entities in JavaScript that are similar to them, but they are not arrays. These objects, as well as arrays, contain the length .lengthand elements by the key [from 0 to .length), which can be skipped - then “holes” appear. They do not contain under themselves Array.prototype, respectively, they do not have array methods. These are objects arguments, strings (formally, IE8 +), typed arrays (arrays, but not containing themselves Array.prototype), collections of DOM elements, jQuery objects, etc.



Almost all methods of the prototype array are generics (as reported by the specification translation- “deliberately generic” functions). They do not require that the object from the context of which they were launched be an array. Is that .concatnot quite. I think many are familiar with these designs:



 Array.prototype.slice.call(arguments, 1); //  [].slice.call(arguments, 1); 


Bulky and unintelligible.



ECMAScript 6 adds the method #Array.from , with its help you can lead to an array of iterable and array-like objects.



 Array.from(arguments).slice(1); 


It seems to be convenient, but far from cheap - even for the simplest operation, we are forced to bring the entire object to an array, moreover, in most cases, through a rather heavy protocol of iterators.



In Mozilla's JavaScript, in the 1.6 version of the language and, accordingly, in Firefox, back in 2005, along with the array methods later included in ECMAScript 5, static versions of the array methods were added . They did not get into either the 5th or the 6th edition of ECMAScript, although they have been present in the Strawman development branch for a long time, but I personally hope for their appearance in one of the future versions of ECMAScript. Add to the library and they are implemented elementarily, and since they already exist in Ognelis, we put them in the category of crutches, not bicycles.



 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'} 


# Deferred execution: setTimeout, setInterval, setImmediate




# setTimeout, setInterval



Perhaps start with all the usual setTimeoutand setInterval. Many do not know that according to the standard ( W3C , WHATWG ), these functions, in addition to the callback and the delay time, take additional arguments with which the transferred callback is started. But here, as usual, the problem is in IE. In IE9- setTimeoutand setIntervaltake only 2 arguments, it is treated with a wrapper in several lines.



 // : setTimeout(log.bind(null, 42), 1000); // : setTimeout(log, 1000, 42); 


# setImmediate



JavaScript is single-threaded and sometimes it is rather unpleasant. Any lengthy heavy calculations on the client will hang up the user interface, and on the server - request processing. In this case, the partitioning of the heavy task into easy subtasks, performed asynchronously, between which I / O can pass, saves.



Also in JavaScript (for now) tail recursion is not optimized. When a function is called recursively a certain number of times, an error will be generated RangeError: Maximum call stack size exceeded. This number, depending on the platform, varies from several hundred to several tens of thousands. Asynchronous recursive call saves from stack overflow. True, a recursive call is usually easily rewritten into a normal cycle, which is preferable.



To solve such problems you can usesetTimeoutwith minimal delay, but it will turn out very slowly. The minimum setTimeoutspecification delay is 4 ms, and on some platforms even more. Total, a maximum, ~ 250 recursive calls per second in modern browsers and, for example, ~ 64 in IE8. It was necessary, and it is necessary, to cycle, since there are ways to make effective pending execution.



If there was a Node.js process.nextTick, then the client came to help from where it was not expected. In IE10, Microsoft added the setImmediate method , which sets the task to be executed immediately after the I / O has completed, and proposed it for W3C standardization . He later appeared on Node.js . FF and Chromium do not hasten to add it. Popular such polyfil.



There are a huge number of ways to implement effective deferred execution of functions. To achieve maximum performance on different platforms, we will have to use many (similar to the methods of the above mentioned polyfil). It:





 setImmediate(function(arg1, arg2){ console.log(arg1, arg2); // =>        }, '   ', '  '); clearImmediate(setImmediate(function(){ console.log('   '); })); 


In addition setImmediate, there is a faster alternative - the concept asap(as soon as possible, the library ) - if possible, create a microtask that will be executed before any input / output. Add such a global method in the standard language think in tc39 . Maybe it should be added to the library?



# Console




The console is the only universal output and debugging tool both in the browser and on the server. At the same time, the console is not part of the ECMAScript specification and is not standardized at all. There are abandoned sketches of the specification , the actual implementation on all platforms varies.



# In IE7, the console is completely missing. In some browsers, the “Heisenberg Console” isconsoledefined only when the user is watching it. And, of course, not all methods are available on all platforms. We will not invent our Firebug Lite style console,we’ll just make stubs for methods so that you can use them without checking availability.



 // : if(window.console && console.log)console.log(42); // : console.log(42); 


# In Firefox and Chromium, the console methods must be run from the contextconsole, so if you need to pass them as a callback you will have to bind - for example, #console.log.bind(console) . In IE, Firebug and Node.js can be transferred without binding, which is much more convenient. Accordingly, we tie the methods to the objectconsole.



 // : 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); 


But there is one problem here: on some platforms, when calling the console methods, the string from which the method was called is also displayed. When overridden methods console, this line will be a line in sore.js . If this is critical for you - you can build a library without a console module.



Since the console is not standardized, add a little ad-lib:



# Ability to turn off the output to the console. Of course, in production, it is better not to leave calls to the console methods in code, either deleting them with pens or using more advanced tools , but this is not always necessary or necessary.



 console.disable(); console.warn(' ,     .'); console.enable(); console.warn('  .'); 


The context binding and the ability to disable the console is also present, for example, in this library from TheShock .



# Part Two: Bicycles


Options zero, no options.

I wanted half the world - enough for a bike.

- Loop of addiction


Bicycles, in the context of this article / library - all non-standard functionality. Actually, that which, in my opinion, is lacking in the standard library of the language, even if it is implemented according to all the standards currently available. We also include here what is offered in ES7 +, since this may be far from being revised or rejected altogether.



# Classification of data




# Here we start quite with the triviality. In JavaScript, according to ECMAScript 5 specification, there are 6 data types :Undefined, Null, Boolean, String, NumberandObject. ECMAScript 6 adds another data type - #Symbol . To determine the type of data there is an operatortypeof. Here it only works specifically. So historically , thattypeof nullreturns'object'and an attempt to fix it in Harmony was not crowned with success. For a type,Object typeofreturns either'object'or'function'depending on the presence of an internal method[[Call]]. Total we get that the operatortypeofreturns exactly the data type only for primitives, and then not all.



If you check whether the variablenulljust enough to compare it with him, then you Objecthave to either write hundreds of code each time, or a helper. You can, of course, do this - Object(foo) === foobut this solution is far from the fastest - it leads the primitive to the object. In the early drafts of ES6, a method was present Object.isObject, but apparently, due to an attempt to correct typeof null, it was deleted. And it is constantly necessary to check whether a variable is an object. So let's add a helperObject.isObject , which is easier to implement ( sandbox ):



 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 


# But with the classification of objects more interesting. The operatorinstanceofchecks the prototype chain. If you create an object from a prototype or set an object as a prototypeFunction.prototype, it will not make it a function. The instance propertyconstructorcannot guarantee anything, andconstructor.namenot onlydoesit lose any meaning when compressing code, IE is also not supported. An internal property can help with the classification of an object[[Class]]. The only way to tear it out is a terrible construction well known to manyObject.prototype.toString.call(foo).slice(8, -1). Example :



 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' 


On the basis of getting an inner class, most libraries add a set of utilities by type #Array.isArray : Object.is Type in Sugar , _.is Type in Undescore , etc.



We will proceed differently - one universal method Object.classoffor classifying data , similar to the operator typeof!from LiveScript ( example ).



Here are just Object.prototype.toString.call(foo).slice(8, -1)a couple of problems:



Suddenly, but in ECMAScript 6 there is no such internal property of objects as [[Class]]. Object#toStringES6, through checking special internal properties, returns the variable to Undefined, Null, Array, String, Arguments, Function, Error, Boolean, Number, Dateor RegExp, while for the rest it searches for a hint on the # symbol Symbol.toStringTag . If a method finds a hint and it is not the name of one of the built-in “classes”, it returns it, it does not find it Object.



We will correct the logic of the work Object#toString, thanks to # one nasty, but fun bug, we can do it in IE8- without breaking it for-in. And, of course, we implement this approach in the method Object.classof. As a bonus, we get the ability to classify instances of custom designers. Example :



 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' 


# Dictionaries




JavaScript ( ) — . — , JavaScript JSON , .



, , ( , — , Object.prototype ), . Object.prototype .



ECMAScript 6 # - — Map . , ( , ). . Map, unlike object dictionaries, there is no simple literal notation; access to properties occurs through methods — not so concise. MapIt's far from being loved by everyone JSONand not so versatile. Yes, and usually in the keys of the dictionary does not require anything except the lines.





# Problem: Object.prototype and dictionaries



B Object.prototype, as suggested by the Mozilla Developer Network , depending on the implementation, can be:



 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(); 


How can this threaten us?



Suppose we have a primitive telephone directory and the user has access to his API:



 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]; } }; })(); 


We get :



 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] } 


The property, in the absence of, is taken from a chain of prototypes, insimilarly checks its presence. Let's add / replace inwith a method hasOwnPropertythat checks the presence of a property in an object without taking into account the prototype chain. We get :



 // ... 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 


Already quite seriously, especially if this "telephone directory" is on the server side. Prototype methods can be overridden. Accordingly, you need to use the method hasOwnPropertyuntied from the object. We get the need to use for every sneeze cumbersome checks. Something like this trash :



 // ... has: function(name){ return Object.prototype.hasOwnProperty.call(db, name); }, get: function(name){ if(Object.prototype.hasOwnProperty.call(db, name))return db[name]; }, // ... 


To solve this problem in the language would be useful to check if the property is proper, similar to in.



Have you decided that the problems are over? Nothing like this :



 phone.set('__proto__', '+7666666'); //   "" console.log(phone.get('__proto__')); // => undefined 


In Object.prototypeaddition, there is also a “magic” getter / setter __proto__, the installation of a primitive by this key will be ignored, and the object will be damaged, for example, when traversing properties. In the old engines were other "magical" properties. Here helps except that Object.defineProperty( pesochnitsa ):



 // ... set: function(name, phone){ Object.defineProperty(db, name, { enumerable : true, configurable: true, writable : true, value : phone }); }, // ... 


We’re not going to talk about dictionary crawling too much - if everything’s not really bad, the dictionary doesn’t contain properties to be recalled in the prototype, you for-incan do without checking when crawling through the dictionary hasOwnProperty. Here are just a bug with # "Non-enumerable enumerable" properties still makes a detour through the for-indictionaries under which is found Object.prototypedefective in old IE.



In ECMAScript 5, a method for creating an object without a prototype has emerged - Object.create(null)with it you can use the implementation of the methods originally proposed ( sandbox ):



 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]; } }; })(); 


Everything is great, only its creation and initialization, even with Object.assign, are not particularly compact.



Since such dictionaries have no prototype, there are no methods toStringand valueOf. What does it threaten us with?

For whom how, but for me it is rather a plus than a minus.



# ConstructorDict



So, creating a dictionary through Object.create(null)and filling it out is much more cumbersome than when creating a dictionary through {}. Of course, the most beautiful solution would be to add a literal dictionary to the language , but this, at least in the short term, is unlikely. There is no possibility of initialization by the literal. There is a record {__proto__: null, foo: 'bar'}, but it is not supported everywhere, at the moment it leads to de-optimization of the code , and it is still rather cumbersome. One rather interesting decision



was discussed - to make “constructor” as an abbreviationDictObject.create(null). How is it now and is it doing at all, I do not know. But why not take it, slightly expanding? At the same time we will receive a name space for methods for work with objects as dictionaries. Add the ability to initialize with an iterator entriesor an object without an iterator, a sort of version #Array.from for dictionaries.



Since it Dict() instanceof Dictwill not work and #Object.classof(Dict()) will return 'Object', we will add a method to identify dictionaries Dict.isDict.



Like this :



 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 


# Methods for safe work with a dictionary that has a prototype



In case you still have to work with an object that has a prototype under it, as with a dictionary, add methods for working safely with your own properties.



Dict.has- trite, static version hasOwnProperty. In drafts of ECMAScript 6, in the module Reflect- a set of stubs for Proxy, until recently there was a static version of the method hasOwnProperty- the method Reflect.hasOwn. However, in recent versions of the draft specification, this method has been removed.



Dict.get- getting the value by key with checking whether the property is own. Not is - return undefined.



Dict.set- a method for absolutely paranoids. Allows you to set the property of the dictionary, ignore setters, such as __proto__. Uses defineProperty.



Well, the operator deleteand so it works as it should.



Example :



 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 


# Methods for working with a dictionary



The methods added by ECMAScript 5 to the prototype array to bypass it ( forEach, map, someand the like) are very convenient. Their static counterparts for dictionaries are present in almost all general purpose frameworks / libraries. But there is no progress with adding them to the standard.



Add them within our module Dict. Everything is simple, the methods are similar to the static versions of the array methods. This is: Dict.forEach, Dict.map, Dict.filter, Dict.some, Dict.every, Dict.find, Dict.findKey, Dict.keyOf, # Dict.includes , Dict.reduce, #Dict.turn . Keyin the name it corresponds indexto the array methods. The “right” versions and the optional argument-index (for the time being?) Are absent, since the order of traversing the keys of objects is not always the same everywhere. Only the enumerated elements of the object are searched. These methods are generic on the same plane as Array.fromor Array.of. For example, Dict.map(dict, fn)will return a newDict, and Dict.map.call(Object, dict, fn)- new Object. But in general, everything is primitive, boring and like everywhere ( sandbox ):



 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'] 


# As for the chains of methods, # for an obvious reason ,Dictthey are notin the framework of the module, and are not foreseen. Salvation here can be abstract references . But within the framework of #$for and #Map it is quite possible and will appear.



# Iteration through the dictionary



The bright future of ES6 is nearing with # iterators and # cyclesfor-of . But for objects like dictionaries, this is neither warm nor cold - for them in ES6 iterators are not provided. Accordingly, there is no easy way to iterate through them for-of, initialize # with aMap dictionary, etc. Adding methods .keys, .valuesand .entriesin Object.prototypeunlikely - there is already enough garbage, see the description of the previous problem. But two other scenarios are quite probable:



The first is the addition of static methods that return an iterator to the namespace Dict- Dict.{keys, values, entries}. But as I already wrote that with the prospect of adding this module to the standard I do not know.



The second is adding methodsObject.{values, entries} by typeObject.keysreturning an array, not an iterator, and already through the array iterator to bypass the object.



I don’t know what will come out of this and I'm afraid to guess. To obtain an array of dictionary values, it is not rational to use a rather heavy iterator protocol, just as using an intermediate array to iterate over an object. So, although it is partially and duplicating each other functional, we implement both sets of methods in our library. Examples :



 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} 


# Possible perspectives



One could go a little further, making Dictnot just an abbreviation for Object.create(null)with the possibility of initialization by an iterator and an object, but a full-fledged constructor with a prototype that does not contain key strings, only # symbols . Like this :



 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); }; 


What would it give us?





However, there are reasons, at a minimum, to postpone the implementation of this approach until the moment when IE8 will die out completely, and Firefox will completely switch to the ECMAScript 6 iterator protocol. Or maybe it’s not worth it at all - we risk permanently losing compatibility with the standard when / if Dictget there.



# Partial application




Perhaps one of the most useful innovations in ECMAScript 5 was the #Function#bind method . Here are the possibilities of partial application of this method is far from fully disclosed. In this chapter, we will look at things like:





One could add currying , but in JavaScript, currying, rather than partial application, is rarely required. Like the "right" versions of the methods. I will add a link to a valid (and so, perhaps, well-known) article on the topic.



# Partial application without context binding



Function#bindcombines partial application and context binding this. The latter is needed far from always, and in this case the binding is thisnot only an “extra” argument for what to write. If the context in which the partially applied function is to be launched is unknown in advance, the method is Function#bindnot applicable. For example, if it is a prototype method ( sandbox ):



 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]; 


Add a partial application method without binding this- Function#part( sandbox ):



 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 


# Partial application of arbitrary arguments



Often, when partially applied, arbitrary arguments need to be passed — not, for example, the first 2, but only the second and fourth, or second and third. Here Function#bindwe will not be able to help - we will have to write a wrapper manually for each specific case.



 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 


Also add a method Function#bysimilar Function#bind, but with the possibility of using a placeholder for arguments. It would be possible to wrap up Function#bind, forcing to work with a placeholder, but this is a violation of the specification, and this method is already rather inhibited in almost all engines.



 var fn = console.log.by(console, _, 2, _, 4); fn(1, 3, 5); // => 1, 2, 3, 4, 5 


# Extract method from object



In most cases, for example, when passing a callback to a function, we need to bind the method precisely to the object from which we get it. And here there is a problem - fn(foo.bar.baz.bind(foo.bar)). We have to write foo.bar2 times, this is a clear violation of the principle of DRY. I hope in the future abstract references will save from this problem , but the proposed implementation does not solve the problem . Perhaps the most delicious and beautiful solution would be to add an access operator to the language while preserving the context, similar to ~LiveScript - fn(foo.bar~baz)( sandbox ).



There are not many solutions to the problem based on the library, except for extracting a method from an object by key. This is either a static method, for example, _.bindKeyfromLoDash (but with early binding), but it is also quite cumbersome and further worsens the readability, or similar in functionality to the method Object.prototype, for example, Object#boundTofrom Eddy.js .



No matter how scary it sounds, we will add a method to Object.prototype. To risk, expanding the Object.prototypemethod with a short key-string, we, at least, for the time being, we will not - it is difficult to avoid conflicts, and we will break it for-inin IE8-. Earlier in this chapter, we already used a global variable _. In order not to produce extra essences and for short, let's apply it here. Replace the _method with the object toString(respectively, if used in conjunction with Undescore.js or LoDash - you need to connect core.jsafter them). It will return a unique key string, similar to the # key of a poly-symbol of a character . Let's add a method using this key Object.prototype. Due to the use of a dirty hack with a fun bug , we add this method to IE8- without breaking it for-in.



In total, from the example from which we started, we get fn(foo.bar[_]('baz'))- far from the ideal, but at least got rid of the second mention of the object. The returned method is cached. Examples :



 ['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]; 


-, IE8- , , - :) tie, boundTo, bindKey - , .



Proxy ES6 — — fn(foo.bar[_].baz) , Proxy (, ), , .



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]; 


#



The problem of optional arguments is discussed in this article . The example in it is - parseInt- rather unintelligible, no one bothers to bring the lines to a number, for example, using Numberthat it does not expect additional arguments. The point here is not their “danger”, but the need to write an extra wrapper.



For example, we want to output all elements of the array to the console, only the elements themselves and nothing else:



 [1, 2, 3].forEach(console.log); // => 1 0 [1, 2, 3] // => 2 1 [1, 2, 3] // => 3 2 [1, 2, 3] 


The method .forEach, like many others, passes the optional arguments to the callback — the index and the array itself. And we do not need them. So each time the callback will have to be wrapped into another function:



 [1, 2, 3].forEach(function(it){ console.log(it); }); // => 1, 2, 3 


In the article mentioned above, a method was proposed to limit the function arguments Function#only. We realize its option. The first argument is the maximum number of arguments, the second, optional is the context. Example :



 [1, 2, 3].forEach(console.log.only(1)); // => 1, 2, 3 


Of course, if the maximum number of arguments is 1, it is simpler, if they are available, to do with the switch functions from ES6 or coffee-like languages, but if more is already problematic.



# Date formatting




It would seem that the simple task of formatting dates is not so simple in JavaScript. What if we need to get the format string "11/18/2014 6:07:25 AM" ? Everything is pretty scary :



 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 ' 


And what if you need to get, for example, a format string "Tuesday, November 18, 2014, 6:07:25 AM" ?



At the beginning of the article, the internationalization standard ECMA402 , specification , was mentioned . The standard adds an Intl object to JavaScript that contains tools for localized formatting of dates, numbers, string comparisons. At a basic level, Intl is covered in this article . In addition, this standard overloads the methods Date#toLocaleString, Date#toLocaleDateString, Date#toLocaleTimeStringby adding 2 arguments to them: localization and format options. Using them, a string close to the format mentioned above can be obtained as follows :



 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' 


It is cumbersome, of course, but better than that. Regarding the support of the standard - in general, not bad. Chrome, Opera, IE11, more recently, and Firefox is supported. But usually you need support for IE10-, Safari, mobile platforms and the devil still knows what. In this case there is a polyfill . But here's a bad luck - the implementation of this functionality will weigh too much, even without taking into account the locales. For this reason, in core.js and no Polyphemus ECMA402 .



# Add a simple date formatting.



What does “simple” mean? How often do you need full localization or some other advanced date handling tools? I - not really, usually I want a simple convenient date formatting with a format string. Well, if needed - no one bothers to connect Moment.js or polyfil Intl. Here the whole module of working with the date is a few dozen lines.



Add a method Date#formatand its UTC version Date#formatUTC( sandbox ):



 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' 


For the sake of simplicity and easy readability of the format string, we will not bother with escaping notation. While their minimum is available:



 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 


The library already includes Russian ( ru) and English ( en) locales. A locale is specified either by the method core.localeor by the second argument of the methods Date#formatand Date#formatUTC( sandbox ):



 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' 


The locale format is shown below. In your own code, you can limit yourself core.addLocale, but due to the possibility of building the library without extending native objects, the universal locale module will look like this:



 (typeof core != 'undefined' ? core : require('core-js/library')).addLocale('ru', { weekdays: ',,,,,,', months: ':|,:|,:|,:|,:|,:|,:|,:|,:|,:|,:|,:|' }); 


Some examples :



 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 API




# : ECMAScript 5 . / Object.defineProperty / Object.defineProperties , ( , ) , .



# Object.assign , ECMAScript 6 Object.mixin , - . , -, super . , .



Add a method Object.definethat works as described Object.mixin— copying the properties of the source object to the target with descriptors, but not overriding the parent, in the absence of a keyword superin 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; } }); 


# Second problem : ECMAScript 5 also added the ability to create an object without using a constructor, viaObject.create. It would be nice to add your own properties of the object during creation, but the second argumentObject.create, as well asObject.defineProperties, accepts an object that contains objects of property descriptors, which is terribly cumbersome.



Let's add a methodObject.make- an analogueObject.create, the second argument is a waiting object, not an object of descriptors, but a simple object from which own properties are copied into the object being created taking into account descriptors.



 //        : 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 


In ECMAScript 7, we propose adding the Object.getOwnPropertyDescriptors method , which returns, as its name indicates, an object containing all the descriptors of the object's own properties. The perfect match for creating a second argument Object.definePropertiesand, Object.createand to some extent, an alternative to ours Object.makeand Object.define. That's just too bulky.



# Arrays




# Array#includes ( — 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 


# But the methodArray#turnis the fruit of my sick fantasy. Although, as it turned out, not unique - it is similar to the method_.transformfrom LoDash . This is an alternative to the methodArray#reducefor convolving an array into an arbitrary battery object (by default, a new array) without having to return the battery from the callback. The signature of the method and callback is similarArray#reduce. You can interrupt the collection by returning from the callbackfalse. Examples :



 //   : [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] 


# Numbers




# Remember the example with the iterated numbers from the chapter on iterators? Too tasty and versatile opportunity to abandon the similar in the standard library. A very short loop executed a specified number of times, at the basefor-of, simple generation of an array of a given length through #Array.from (and maybe # spread ), etc. So let's add an iterator of numbers, though not in such a primitive implementation. Examples :



 //  : 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} 


# Mathematical functions inNumber.prototype- from the category of pleasant things. Just like in Sugar and MooTools, we move the methods from objectMathtoNumber.prototype. There is nothing special to say here - the context becomes the first argument of a mathematical function. Maybe it duplicates the existing, standardized functionality, but this is quite convenient :)



We mention the method in a separate lineNumber#random. It returns a random number between the context number and the argument passed (the default is 0).



Examples :



 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 


# Escaping special characters




# If I suddenly, at the end of the article, say that JavaScript is used, first of all, for working with HTML, I will not reveal a great secret. To work with HTML, both on the client and on the server, we need to escape it. Someone may say that this is the task of the framework or template engine. But is it worth it for such a primitive task to pull them? Methods for escaping HTML are in all standard libraries. In Sugar, Prototype, MooTools are the methodsescapeHTMLandunescapeHTMLthe prototype line. We will not break this tradition:



 '<script>doSomething();</script>'.escapeHTML(); // => '&lt;script&gt;doSomething();&lt;/script&gt;' '&lt;script&gt;doSomething();&lt;/script&gt;'.unescapeHTML(); // => '<script>doSomething();</script>' 


# Often there is a need to create a regular expression from user data, and for correct / safe operation, you need to screen them as well. Methods for this are in Sugar, Prototype, MooTools, somewhere as a static methodRegExp, somewhere a methodString.prototype. It has long been discussed the addition of such a method in ECMAScript. I hope we will wait for this, but for now we will implement the proposed option in our library:



 RegExp.escape(' -[]{}()*+?.,\\^$|'); // => ' \-\[\]\{\}\(\)\*\+\?\.\,\\\^\$\|' 


# Conclusion




Somehow like this.



I foresee the appearance of well-known xkcd pictures about standards in the comments, only almost everything in the library is as close as possible, and I don’t see any alternatives to it except for a hodgepodge of libraries from hundreds of kilobytes.



As for plans for the future of the library, they are mainly scattered throughout the text of the article. Of course, you also need to optimize performance and better cover the code with tests - with this, I haven't really bothered about it yet.



Interested in your opinion that I may have missed and what can be implemented better.



Yes. And yet, once I started suffering like this, it’s boring to me. I am looking for an interesting project with a decent s / n.

Source: https://habr.com/ru/post/216997/



All Articles