Introduction to the DEVELOPMENT of the ECMAScript specification

I’m sure you’ve heard of ES6, ES7, ES2015, ES2016, ES2017… All these intricate names. Some people describe it by version number (ES6, ES7). Others by year (ES2015, ES2016).

So what is the relationship between them? Let’s look from the development of JS.

history

ECMAScript is a standardized specification for a scripting language developed by Brandon Acker of Netscape; Originally named Mocha, later renamed to LiveScript, and finally renamed to JavaScript[1]. In December 1995, Sun And Netscape jointly published JavaScript[2]. In November 1996, Netscape submitted JavaScript to the European Computer Manufacturers Association for standardization. The first version of ECMA-262 was adopted by the ECMA organization in June 1997. ECMAScript is the name of the scripting language standardized by ECMA-262.

While JavaScript and JScript are compatible with ECMAScript, they contain features beyond ECMAScript [3].

From wikipedia.

Bottom line: TECHNICALLY, ES6 refers to the ES2015 standard, which was released in June 2015, but many people talk about ES6 with ES2016, ES2017 and other standards in mind. So technically, it’s better to use years when talking about the ECMAScript standard. But it doesn’t matter. There’s not much point in dwelling on it.

ESNext

We have seen all kinds of ES6, 7, ES2015, how can there be an ESNext? What is ESNext?

In fact, ESNext is a general term, it always points to the next version. For example, the current version is ES2020, so ESNext refers to the standard that will be released in June 2021.

Common API parsing added in ES6 and beyond

Let and const

Let’s start with a classic interview question

for(var i=0; i<=3; i++){setTimeout(function() {  
        console.log(i)  
    }, 10);
} 
Copy the code

What are the outputs? Why is that? How can I modify it so that it outputs 0,1,2,3?

for(var i = 0; i <=3; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i);
        }, 10);
    })(i);
}

for(let i=0; i<=3; i++){setTimeout(function() {  
        console.log(i)  
    }, 10);
} 
Copy the code

I. SetTimeout is asynchronous. In the next round of the event loop, wait until the time of execution, to find a reference to the variable I. So the function finds the I at the end of the loop, which has already become 4.

  1. Let introduces the concept of block-level scope. When you create the setTimeout function, the variable I is in scope. For each iteration of the loop, the I referenced is a different instance of I.

  2. There is also the problem of variable enhancement

console.log(i)
var i = 1;

console.log(letI)
let letI = 2;
Copy the code
  1. Const is simple and cannot be modified based on a let.

Arrow function

  1. The biggest difference: this in an arrow function is determined when it is defined; this in a normal function is determined when it is used.
const teacher = {
    name: 'lubai'.getName: function() {
        return `The ${this.name}`}}console.log(teacher.getName());

const teacher = {
    name: 'lubai'.getName: () = > {
        return `The ${this.name}`}}console.log(teacher.getName());
Copy the code
  1. Arrow function – short for arrow function
const arrowFn = (value) = > Number(value);

console.log(arrowFn('aaa'))
Copy the code
  1. Note that arrow functions cannot be used as constructors

What does the constructor do? Change this to point to the object that comes out of the new instance. What does the arrow function do? The point to this is determined when you define it.

class

class Test {
    _name = ' ';
    constructor() {
        this.name = 'lubai';
    }

    static getFormatName() {
        return `The ${this.name} - xixi`;
    }

    get name() {
        return this._name;
    }

    set name(val) {
        console.log('name setter');
        this._name = val; }}console.log(new Test().name)
console.log(Test.getFormatName())
Copy the code

Template string

const b = 'lubai'
const a  = `${b} - xxxx`;
const c = 'I'm a line feed I'm a line feed! I've changed lines again! `;
Copy the code

Write a function to render, implement template render function.

const year = '2021'; 
const month = '10'; 
const day = '01'; 

let template = '${year}-${month}-${day}';
let context = { year, month, day };

const str = render(template)({year,month,day}); 

console.log(str) / / 2021-10-01

function render(template) {
    return function(context) {
        return template.replace(/ \ $\ {(. *?) \}/g.(match, key) = >context[key]); }}Copy the code

deconstruction

  1. Deconstruction of arrays
// Base type destruct
let [a, b, c] = [1.2.3]
console.log(a, b, c) / / 1, 2, 3

// Object array destruct
let [a, b, c] = [{name: '1'}, {name: '2'}, {name: '3'}]
console.log(a, b, c) // {name: '1'}, {name: '2'}, {name: '3'}

/ /... deconstruction
let [head, ...tail] = [1.2.3.4]
console.log(head, tail) // 1, [2, 3, 4]

// Nested deconstruction
let [a, [b], d] = [1[2.3].4]
console.log(a, b, d) / / 1, 2, 4

// Destruct failed to use undefined
let [a, b, c] = [1]
console.log(a, b, c) // 1, undefined, undefined

// Deconstruct the default assignment
let [a = 1, b = 2] = [3]
console.log(a, b) / / 3 and 2
Copy the code
  1. Object structure
// Object property destruct
let { f1, f2 } = { f1: 'test1'.f2: 'test2' }
console.log(f1, f2) // test1, test2

// This is one of the differences between array destructuring and object destructuring
let { f2, f1 } = { f1: 'test1'.f2: 'test2' }
console.log(f1, f2) // test1, test2

// Rename the destruct object
let { f1: rename, f2 } = { f1: 'test1'.f2: 'test2' }
console.log(rename, f2) // test1, test2

// Nested deconstruction
let { f1: {f11}} = { f1: { f11: 'test11'.f12: 'test12'}}console.log(f11) // test11

/ / the default value
let { f1 = 'test1'.f2: rename = 'test2' } = { f1: 'current1'.f2: 'current2'}
console.log(f1, rename) // current1, current2
Copy the code
  1. What is the principle of deconstruction?

For the Iterator interface of the iterable object, the Iterator obtains the corresponding value in order to assign the value.

3.1 What is an Iterator?

Iterator is an interface that provides a uniform access mechanism for different data destructions. Any data destructor that has an Iterator interface can process all the members of the data structure in sequence by iterating over them. The syntax for of in ES6 is equivalent to an Iterator, which automatically searches for the Iterator interface as it traverses the data structure.

3.2 What is the use of an Iterator?

  • Provide a unified access interface for various data destructions
  • Enables data destructuring to be sorted
  • You can use the latest ES6 command for of for traversal
function generateIterator(array) {
    let nextIndex = 0
    return {
        next: () = > nextIndex < array.length ? {
            value: array[nextIndex++],
            done: false}, {value: undefined.done: true}}; }const iterator = generateIterator([0.1.2])

console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
Copy the code

3.3 What is an Iterable?

An iterable is an implementation of the Iterator interface. This is a complement to ECMAScript 2015, it’s not a built-in or syntax, it’s just a protocol. Any object that follows this protocol point can be iterable. An iterable must have two protocols: the iterable protocol and the iterator protocol.

  • Iterable protocol: Objects must implement the iterator method. The object or its prototype chain must have a property called symbol.iterator. The value of this property is a parameterless function that returns an iterator protocol.

  • Iterator protocol: Defines a standard way to produce a finite or infinite sequence of values. It requires that you implement a next() method that returns an object with done(Boolean) and value properties.

3.4 Can we implement a for of traversable object?

As we can see from the above, a custom data structure that has an Iterator interface and deploys it on its Symbol. Iterator property can be iterable and can be traversed by a for of loop.

const obj = {
    count: 0[Symbol.iterator]: () = > {
        return {
            next: () = > {
                obj.count++;
                if (obj.count <= 10) {
                    return {
                        value: obj.count,
                        done: false}}else {
                    return {
                        value: undefined.done: true
                    }
                }
            }
        }
    }
}

for (const item of obj) {
    console.log(item)
}
 
Copy the code

or

const iterable = {
    0: 'a'.1: 'b'.2: 'c'.length: 3[Symbol.iterator]: Array.prototype[Symbol.iterator],
};

for (const item of iterable) {
    console.log(item);
}
Copy the code

traverse

  1. for in

When traversing the array, the key is the array subscript string; Traversing the object, key is the name of the object field.

let obj = {a: 'test1'.b: 'test2'}
for (let key in obj) {
    console.log(key, obj[key])
}
Copy the code

Disadvantages:

  • For in not only traverses the current object, but also includes enumerable properties on the prototype chain
  • For in is not suitable for traversing groups; its main application is objects
  1. for of

Create an iteration loop on an iterable (including Array, Map, Set, String, TypedArray, Arguments objects, and NodeList objects), call a custom iteration hook, and execute a statement for each of the different property values.

let arr = [{age: 1}, {age: 5}, {age: 100}, {age: 34}]
for(let {age} of arr) {
    if (age > 10) {
        break // To allow interrupts
    }
    console.log(age)
}
Copy the code

Advantages:

  • For of only traverses the current object

Object

  1. Object.keys

This method returns an array of self-enumerable properties of a given object.

const obj = { a: 1.b: 2 };
const keys = Object.keys(obj); // [a, b]
Copy the code

Hand-written implementation of a function simulating object. keys?

function getObjectKeys(obj) {
    const result = [];
    for (const prop in obj) {
        if(obj.hasOwnProperty(prop)) { result.push(prop); }}return result;
}

console.log(getObjectKeys({
    a: 1.b: 2
}))
Copy the code
  1. Object.values

This method returns an array of all enumerable property values for a given object itself.

const obj = { a: 1.b: 2 };
const keys = Object.keys(obj); / / [1, 2]
Copy the code

Hand-written implementation of a function simulating object. values?

function getObjectValues(obj) {
    const result = [];
    for (const prop in obj) {
        if(obj.hasOwnProperty(prop)) { result.push(obj[prop]); }}return result;
}

console.log(getObjectValues({
    a: 1.b: 2
}))
Copy the code
  1. Object.entries

This method returns an array of key-value pairs for a given object’s own enumerable properties.

const obj = { a: 1.b: 2 };
const keys = Object.entries(obj); // [ [ 'a', 1 ], [ 'b', 2 ] ]
Copy the code

Hand-written implementation of a function simulating object.entries?

function getObjectEntries(obj) {
    const result = [];
    for (const prop in obj) {
        if(obj.hasOwnProperty(prop)) { result.push([prop, obj[prop]]); }}return result;
}

console.log(getObjectEntries({
    a: 1.b: 2
}))
Copy the code
  1. Object.getOwnPropertyNames

This method returns an array whose elements are enumerated or non-enumerable attribute name strings owned by OBj itself.

What does this code say?

Object.prototype.aa = '1111';

const testData = {
    a: 1.b: 2
}

for (const key in testData) {
    console.log(key);
}

console.log(Object.getOwnPropertyNames(testData))
Copy the code
  1. Object.getOwnPropertyDescriptor

What is a descriptor? The property descriptor corresponding to an object is an object. Contains the following attributes:

  • Configurable. If false, any attempts to remove the target property or modify the property property (writable, signals, Or Enumerable) will be invalidated. Therefore, you can set the information without additional information to true if the properties of the information already have features.
  • Writable Whether writable is enabled. If set to false, any overwriting of this property is invalid (but does not report an error, as in strict mode). The default is false.
  • Enumerable. Whether it can be iterated in a for-in loop or enumerated in object. keys.
const object1 = {};
Object.defineProperty(object1, 'p1', {
  value: 'lubai'.writable: false
});

object1.p1 = 'not lubai';

console.log(object1.p1);
Copy the code

So if you’re talking about defineProperty, you can’t do without Proxy.

// const obj = {};
// let val = undefined;
// Object.defineProperty(obj, 'a', {
// set: function (value) {
// console.log(`${value} - xxxx`);
// val = value;
/ /},
// get: function () {
// return val;
/ /},
// configurable: true,
// })

// obj.a = 111;

// console.log(obj.a)

const obj = new Proxy({}, {
    get: function (target, propKey, receiver) {
        console.log(`getting ${propKey}`);
        return target[propKey];
    },
    set: function (target, propKey, value, receiver) {
        console.log(`setting ${propKey}`);
        return Reflect.set(target, propKey, value, receiver); }}); obj.something =1;
console.log(obj.something);
Copy the code

And Reflect is what?

  • Put some methods that are clearly internal to the language (such as object.defineProperty) on the Reflect Object. Currently, some methods are deployed on both Object and Reflect, and new methods in the future will be deployed only on Reflect. In other words, the Reflect object gives you access to methods inside the language
  • Make all Object operations function behavior. Some Object operations are imperative, such as name in obj and delete obj[name], while reflect.has (obj, name) and reflect.deleteProperty (obj, name) make them functional behavior.
  • The methods on the Reflect object correspond to the methods on the Proxy object. As long as the methods on the Proxy object correspond to the methods on the Reflect object. This makes it easy for the Proxy object to call the corresponding Reflect method, completing the default behavior as a basis for modifying the behavior. That is, no matter how Proxy changes the default behavior, you can always get the default behavior on Reflect.

Note, however, that an object with writable set to false by defineProperty will not be able to use Proxy

const target = Object.defineProperties({}, {
    foo: {
        value: 123.writable: false.configurable: false}});const proxy = new Proxy(target, {
    get(target, propKey) {
        return 'abc'; }}); proxy.fooCopy the code
  1. Object.create()

The object.create () method creates a new Object and takes the first argument of the method as the value of the __proto__ attribute of the new Object. The object.create () method also takes a second optional argument, which is an Object. Each property of the Object is treated as a property of the new Object itself. Object attribute value to descriptor (Object. GetOwnPropertyDescriptor (obj, ‘key’)) in the form of and enumerable to false by default

const person = {
    isHuman: false.printIntroduction: function () {
        console.log(`My name is The ${this.name}. Am I human? The ${this.isHuman}`); }};const me = Object.create(person);

me.name = "lubai";
me.isHuman = true;
me.printIntroduction();

console.log(person);

const myObject = Object.create(null)
Copy the code

What does passing in the second argument do?

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}

const b = Object.create(Person.prototype, {
    name: {
        value: 'coco'.writable: true.configurable: true.enumerable: true,},sex: {
        enumerable: true.get: function () {
            return 'hello sex'
        },
        set: function (val) {
            console.log('set value:' + val)
        }
    }
})

console.log(b.name)
console.log(b.sex)
Copy the code

So what is the meaning of object.create (null)? Create ({}) or declare a {} Object.

Object.create(null) Creates an Object whose prototype chain is null, i.e. Fn. Prototype = NULL

const b = Object.create(null) // Return a pure {} object with no prototype

b / / {}
b.__proto__ // undefined
b.toString() // throw error
Copy the code

So when you want to create a very clean Object with no properties on the stereotype chain, then use object.create (null). For in to iterate without considering the stereotype chain properties.

  1. Object.assign

Shallow copy, similar to {… a, … b };

function shallowClone(source) {
    const target = {};
    for (const i in source) {
        if(source.hasOwnProperty(i)) { target[i] = source[i]; }}return target;
}

const a = {
    b: 1.c: {
        d: 111}}const b = shallowClone(a);


b.b = 2222;

b.c.d = 333;

console.log(b)
console.log(a)
Copy the code
  1. Object.is
const a = {
    name: 1
};
const b = a;
console.log(Object.is(a, b))

console.log(Object.is({}, {}))
Copy the code

Promise

I’ve talked about most of my promises, so maybe I’ll review promise.all

function PromiseAll(promiseArray) {
    return new Promise(function (resolve, reject) {
        // Determine the parameter type
        if (!Array.isArray(promiseArray)) {
            return reject(new TypeError('arguments muse be an array'))}let counter = 0;
        let promiseNum = promiseArray.length;
        let resolvedArray = [];
        for (let i = 0; i < promiseNum; i++) {
            // 3. Why do you use promise.resolve?
            Promise.resolve(promiseArray[i]).then((value) = > {
                counter++;
                resolvedArray[i] = value; // 2. Push instead of index
                if (counter == promiseNum) { Resolvedarr. length === = promiseNum (); // 2.
                    // 4. If not in. Then, but in the outer judgment, ok?
                    resolve(resolvedArray)
                }
            }).catch(e= >reject(e)); }})}/ / test
const pro1 = new Promise((res, rej) = > {
    setTimeout(() = > {
        res('1')},1000)})const pro2 = new Promise((res, rej) = > {
    setTimeout(() = > {
        res('2')},2000)})const pro3 = new Promise((res, rej) = > {
    setTimeout(() = > {
        res('3')},3000)})const proAll = PromiseAll([pro1, pro2, pro3])
    .then(res= >
        console.log(res) // Print after 3 seconds ["1", "2", "3"]
    )
    .catch((e) = > {
        console.log(e)
    })
Copy the code

Then write a promise.allseettled.Return the status and results of all the promises

function PromiseAllSettled(promiseArray) {
    return new Promise(function (resolve, reject) {
        // Determine the parameter type
        if (!Array.isArray(promiseArray)) {
            return reject(new TypeError('arguments muse be an array'))}let counter = 0;
        const promiseNum = promiseArray.length;
        const resolvedArray = [];
        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promiseArray[i])
                .then((value) = > {
                    resolvedArray[i] = {
                        status: 'fulfilled',
                        value
                    };

                })
                .catch(reason= > {
                    resolvedArray[i] = {
                        status: 'rejected',
                        reason
                    };
                })
                .finally(() = > {
                    counter++;
                    if (counter == promiseNum) {
                        resolve(resolvedArray)
                    }
                })
        }
    })
}

Copy the code

An array of

  1. Array.flat

The flat() method recurses through the array at a specified depth and returns a new array combining all the elements with the elements in the subarray it traverses

const arr1 = [1.2[3.4]];
arr1.flat();
// [1, 2, 3, 4]

const arr2 = [1.2[3.4[5.6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

const arr3 = [1.2[3.4[5.6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

// Use Infinity to expand nested arrays of any depth
const arr4 = [1.2[3.4[5.6[7.8[9.10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Copy the code

How to simulate array. flat?

// Use Reduce, concat, and recursion to expand an infinite number of nested arrays
const arr1 = [1.2.3[1.2.3.4[2.3.4]]];

function flatDeep(arr, d = 1) {
    if (d > 0) {
        return arr.reduce((res, val) = > {
            if (Array.isArray(val)) {
                res = res.concat(flatDeep(val, d - 1))}else {
                res = res.concat(val);
            }
            returnres; }}, [])else {
        return arr.slice()
    }
};

console.log(flatDeep(arr1, Infinity))
// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
Copy the code

If we don’t care about depth, let’s just give him an infinite tie

function flatten(arr) {
    let res = [];
    let length = arr.length;
    for (let i = 0; i < length; i++) {
        if (Object.prototype.toString.call(arr[i]) === '[object Array]') {
            res = res.concat(flatten(arr[i]))
        } else {
            res.push(arr[i])
        }
    }
    return res
}

// If the array elements are of type Number
function flatten(arr) {
	return arr.toString().split(', ').map(item= > +item)
}

function flatten(arr){
    while(arr.some(item= >Array.isArray(item))){ arr = [].concat(... arr); }return arr;
}
Copy the code
  1. Array.includes

The includes() method determines whether an array contains a specified value, returning true if it does, or false otherwise.

const array1 = [1.2.3];

console.log(array1.includes(2));

const pets = ['cat'.'dog'.'bat'];

console.log(pets.includes('cat'));
Copy the code

It actually has two parameters, but we only use one.

  • valueToFind

The value of the element to look for.

  • FromIndex optional

Look for valueToFind from the fromIndex index. If the value is negative, the search starts with the index of array. Length + fromIndex in ascending order (even if the search starts with the absolute value of fromIndex, and then searches backwards). The default is 0.

[1.2.3].includes(2);     // true
[1.2.3].includes(4);     // false
[1.2.3].includes(3.3);  // false
[1.2.3].includes(3, -1); // true
[1.2.NaN].includes(NaN); // true

// fromIndex is greater than or equal to the array length
var arr = ['a'.'b'.'c'];

arr.includes('c'.3);   // false
arr.includes('c'.100); // false

// The calculated index is less than 0
var arr = ['a'.'b'.'c'];

arr.includes('a', -100); // true
arr.includes('b', -100); // true
arr.includes('c', -100); // true
Copy the code
  1. Array.find

The find() method returns the value of the first element in the array that satisfies the provided test function. Otherwise, return undefined.

Callback is a function that executes on each item in the array, taking three arguments:

  • element

The element currently traversed.

  • The index of the optional

Index currently traversed.

  • An array of optional

The array itself.

const test = [
    {name: 'lubai'.age: 11 },
    {name: 'xxx'.age: 100 },
    {name: 'nnn'.age: 50}];function findLubai(teacher) {
    return teacher.name === 'lubai';
}

console.log(test.find(findLubai));
Copy the code
  1. Array.from

4.1 The array.from () method creates a new, shallow copy Array instance from an array-like or iterable.

  • arrayLike

A pseudo-array object or iterable that you want to convert to an array.

  • MapFn optional

If specified, the callback is performed for each element in the new array.

4.2 Array.from() You can create an Array object by:

  • Pseudo-array object (any object that has a length property and several indexed properties)
  • Iterable objects (can get the elements of an object, such as maps and sets)
console.log(Array.from('foo'));

console.log(Array.from([1.2.3].x= > x + x));

const set = new Set(['foo'.'bar'.'baz'.'foo']);
Array.from(set);
// [ "foo", "bar", "baz" ]

const map = new Map([[1.2], [2.4], [4.8]]);
Array.from(map);
// [[1, 2], [2, 4], [4, 8]]

const mapper = new Map([['1'.'a'], ['2'.'b']]);
Array.from(mapper.values());
// ['a', 'b'];

Array.from(mapper.keys());
/ / (' 1 ', '2');
Copy the code

So what can we do to de-iterate an array?

function unique (arr) {
  return Array.from(new Set(arr))
  // return [...new Set(arr)]
}
const test = [1.1.'true'.'true'.true.true.15.15.false.false.undefined.undefined.null.null.NaN.NaN.'NaN'.0.0.'a'.'a'];
console.log(unique(test));


function unique(arr) {
    const map = new Map(a);const array = []; // The array is used to return the result
    for (let i = 0; i < arr.length; i++) {
        if(! map.has(arr[i])) {// If there is a key value
            array.push(arr[i]);
            map.set(arr[i], true); }}return array;
}

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error! ')
        return
    }
    const array = [];
    for (let i = 0; i < arr.length; i++) {
        if(! array.includes(arr[i])) {// Includes checks whether the array has a valuearray.push(arr[i]); }}return array
}
Copy the code
  1. Array.of

The array.of () method creates a new Array instance with a variable number of arguments, regardless of the number or type of the arguments.

Array.of(7);       / / [7]
Array.of(1.2.3); / / [1, 2, 3]
Copy the code

So how do you simulate it?

Array.of = function() {
    return Array.prototype.slice.call(arguments);
};
Copy the code

async await yeild