• In type conversion, Number(undefined) is NaN and Number(null) is 0

  • ** as the exponentiation operator can also be used as a root, 2 ** 3 is 8,8 ** (1/2) is 3

  • Most operators return the current value (+5, -3), as does the assignment operator = (console.log(x = 5)), where x is assigned to 5 and the value of the returned x is printed out. This is common in some judgments, such as:

    where(x = y - 1){... }Copy the code

    Chain assignment is also often used, such as:

    let a, b, c;
    a = b = c = 2 + 2;
    Copy the code

    It’s not recommended to do this in code because it’s not very readable

  • The comma operator, which is a lower priority than the assignment operator, const a = (1 + 2, 3 + 4); The result of a is 7, 1 + 2 and 3 + 4 are both executed, but only the result of the last statement is returned. 1 + 2 is run but the result is discarded. Such operators are useful for complex operations where several actions are placed on a line, such as:

    for (a = 1, b = 3, c = a * b; a < 10; a++) {
        ... 
    }
    Copy the code
  • Null-value merge operator?? And logical operators | | are similar, but???? Only if the first value is null or undefined, if the first value is undefined or null, the result is the second value

    const a = null;
    const b = undefined;
    const c = 0;
    const d = ' ';
    const v = 'value';
    console.log(a ?? v , a || v); // Result is 'value' 'value'
    console.log(c ?? v , c || v); // Result is 0 'value'
    console.log(d ?? v , d || v); // The result is "value".
    Copy the code
  • Object has two methods of querying and changing attributes, such as obj. Key and obj[key]. When complex attributes (variables, expressions, multi-word attributes, etc.) need to be used, most of them are [], such as:

    const key1 = 'key1';
    const key2 = 'key2';
    const key3 = 'key3 key3';
    const obj = {
        key1: 'value1'.key2: 'value2'.'key3 key3': 'value3',
        [key1+key2]: 'value12'};console.log(obj.key1,obj.key2); // 'value1' 'value2'
    console.log(obj[key1+key2],obj[key3]); // 'value12' 'value3'
    Copy the code
  • Optional chain? . Is a secure way to access properties of nested objects. If the street in the address of a User object is retrieved, the presence of user and address must be checked, otherwise TypeError will be raised when either user or address is null/undefined. Optional chain? . If the preceding part is undefined or null, it stops and returns

    const getUserStreet1 = user= > {
        if(user && user.address) {
            returnuser.address.street; }}const getUserStreet2 = user= > {
        returnuser? .address? .street; }Copy the code

    Can you also use optional chains for functions and arrays? . (),? [].

    const print1 = undefined;
    const print2 = x= > x;
    console.log(print1? . ('test')); // undefined
    console.log(print2? . ('test')); // test
    
    const getArrValue = (arr, index) = >arr? .[index];console.log(getArrValue(undefined.1)); // undefined
    console.log(getArrValue([1.2].1)); / / 2
    Copy the code
  • The Symbol([desc]) function creates a unique Symbol type value. The same description text creates different values

    const symbol1 = Symbol('test');
    const symbol2 = Symbol('test');
    console.log(symbol1 === symbol2); // false
    Copy the code

    Can be used as a private property of an object, not open to the outside world. For (key) if the same key will return the previous value, otherwise a new value will be created and returned

    const symbol1 = Symbol.for('test');
    const symbol2 = Symbol.for('test');
    console.log(symbol1 === symbol2); // true
    Copy the code

    Iterator, toPrimitive, etc. You can use Symbol. ToPrimitive (must return an original value) to set the conversion of the object’s original value. Precedence over toString() and valueOf()

    const obj = {};
    // hint = one of "string", "number", and "default"
    obj[Symbol.toPrimitive] = function(hint) { 
        // Must return a primitive value (string, number, Boolean, etc.)
    }
    Copy the code

    The hint parameter is one of “string”, “number”, or “default”, and is “number” when performing mathematical calculations, comparisons, or displays for number () conversions. Hint is “string” for obvious string operations, such as alert() printing and object attribute assignment; The hint is “default” when the operator “does not determine” the type of expected value, as binary + can be used for numbers or characters without determining the result

    const obj = {};
    obj[Symbol.toPrimitive] = function(hint) {
       return hint === "number" ? 8 : hint; 
    }
    console.log(+obj, Number(obj)); / / 8 8
    const test = {
        [obj]: 'test',}console.log(String(obj), Object.keys(test)); // 'string' ['string']
    console.log(obj + 5); // 'default5'
    Copy the code

    If you want to meet a = = 1 & & & & a = = a = = 2 or 3 + a + a = = = = 1 & 2 && + a = = = = = 3 can use type conversion

    (() = > {
        const a = { v: 0 };
        a[Symbol.toPrimitive] = function(hint) {
            this.v += 1;
            return hint === "number" ? this.v : hint;
        }
        console.log(+a === 1 && +a === 2 && +a === 3); // true
        console.log(+a === 1); // false, because the value of v in object A increases each time}) ();Copy the code

    To solve the sum (1) (2) (3) (4)… The result is 1 + 2 + 3 + 4 +…

    const sum = arg= > {
        const result = arg2= > sum(arg+arg2);
        result[Symbol.toPrimitive] = () = > arg;
        return result;
    } // Use closures, recursion, symbol.toprimitive
    console.log(+sum(1) (2) (3) (4)); / / 10
    console.log(+sum(1) (2) (3) (4) (5) = = =15); // true
    Copy the code

    Symbol.iterator requires that this method must return an iterator (an object with a next() method). The result returned by next() must be {done: Boolean, value: Any}, done=true, indicates the end of the iteration, otherwise value is the next value.

    const obj = {};
    obj[Symbol.iterator] = function() { 
        // Must return an iterator
        return {
            next:() = >{...// The data returned must be in the format '{done: Boolean, value: any}'}}}Copy the code

    Const range = {from: 1, to: 5};

    const range = { from: 1.to: 5 };
    range[Symbol.iterator] = function() {
        let indexItem = this.from;
        return {
            next: () = > {
                if(indexItem <= this.to) {
                    return {done: false.value: indexItem++}
                }else{
                    return {done: true}
                }
            }
        }
    }
    for(let item of range) {
        console.log(item); // 1, 2, 3, 4, 5
    }
    Copy the code

    Or rewrite with generator as follows:

    const range = { from: 1.to: 5 };
    range[Symbol.iterator] = function* () {
        let indexItem = this.from;
        for(let indexItem = this.from; indexItem <= this.to; indexItem++){
            yieldindexItem; }}for(let item of range) {
        console.log(item); // 1, 2, 3, 4, 5
    }
    Copy the code

    Can use the Object. The prototype. ToString or instanceof access type, Symbol. ToStringTag properties can be custom Object type toString method was obtained

    console.log(Object.prototype.toString.call([])); // [object Array]
    const user = {
        [Symbol.toStringTag]: "User",}console.log(Object.prototype.toString.call(user)); // [object User]
    console.log(window[Symbol.toStringTag]); // Window
    Copy the code
  • In addition to using wrappers (e.g., Number(3).tofixed (1)), we can call numeric methods using.. Direct calls, such as 3.. ToFixed (1), (3). The toString (2). 3. ToFixed (1) will return an error because js syntax implies that the part after the first point is a decimal part. If you add a dot, then js knows that the decimal part is empty, so it cannot use the decimal part if the preceding number is itself a decimal. , such as 3.5.. ToFixed (2) will throw an error, and have a way to have a decimal point can be directly call number 3.5 toFixed (2), you can also use parentheses (3.5). The toFixed (2), (3) toFixed (2)

  • Array.from() converts an iterable (containing symbol.iterator attributes) or an array-like object (with index and length attributes) to the Array in progress

    const arrayLike = { 0: "Hello".1: "World".length: 2 };
    const arr1 = Array.from(arrayLike);
    console.log(arr1); // ['Hello', 'World']
    
    const iteratorObj = { from: 1.to: 5 };
    iteratorObj[Symbol.iterator] = function() {
        let indexItem = this.from;
        return {
            next: () = > {
                if(indexItem <= this.to) {
                    return {done: false.value: indexItem++}
                }else{
                    return {done: true}
                }
            }
        }
    }
    const arr2 = Array.from(iteratorObj);
    console.log(arr2); // [1, 2, 3, 4, 5]
    Copy the code
  • Map is an iterable data structure similar to an object. Its key can be any value, and values can be obtained and modified through get(), set() methods, and object entries can be converted to each other through the data format of the object

    const obj = {
        key1: 'a'.key2: 'b'};const objEntries = Object.entries(obj);
    const map = new Map(objEntries);
    map.set('key3'.'c');
    const mapEntries = map.entries();
    const resObj = Object.fromEntries(mapEntries);
    console.log(resObj); // {key1: 'a', key2: 'b', key3: 'c'}
    Copy the code
  • A set is a “collection of values” (without keys), which is also an iterable, each of which can occur only once. The add() and delete() methods are used to manipulate the collection, and the has() method is used to determine whether the value exists

    const set = new Set(a);const user1 = {name: 'zhangsan'.age: 18};
    const user2 = {name: 'lisi'.age: 28};
    const user3 = {name: 'wangwu'.age: 38};
    set.add(user1);
    set.add(user1);
    set.add(user2);
    set.add(user2);
    set.add(user3);
    set.add(user3);
    console.log(set.size); / / 3
    console.log(set.has(user1)); // true
    console.log(set.has({name: 'zhangsan'.age: 18})); // false
    Copy the code
  • The nesting of setTimeout can be used to realize setInterval and control the frequency of each execution. For example, the frequency of polling requests can be reduced based on the number of system visits

    (() = > {
        let mockVisitNum = 0;
        const request = () = > console.log('mock request');
        let delay = 1000;
        const loopReq = () = > {
            setTimeout(function fun() {
                request();
                const nextDelay = delay + 500 * Math.trunc(mockVisitNum/10);
                console.log(`mockVisitNum:${mockVisitNum},delay:${nextDelay}`);
                setTimeout(fun, nextDelay);
            }, delay);
        }
        loopReq();
        setInterval(() = > { mockVisitNum += 1 }, 500); }) ()Copy the code
  • Bind, apply, and call can all change the direction of this in a function

    • Bind (context, arg1, arg2)...The new function does not have the attributes of the original function
    • The call (the context, arg1, arg2)...This changes the reference of this inside the function to context and executes the function immediately
    • apply(context, args)And:callThe difference lies in the difference in the transmission of parameters,applyThe second argument accepted is the class array argument, andcallThe following parameters are separated by commas

    Partial (func, [partials]); partial(func, [partials]); partial(func, [partials]);

    const greet = function(greeting, name) { 
        return greeting + ' ' + name;
    }; 
    const sayHelloTo = _.partial(greet, 'hello');
    sayHelloTo('fred'); // => 'hello fred'
    Copy the code

    Const partial = (fn,… args) => fn.bind(null,… args); , note that the arrow function does not have this problem, as follows:

    const user1 = {
        name: 'userTest'.print: function() {
            console.log(this.name); }}const user2 = {
        name: 'userTest'.print: () = > {
            console.log(this.name);
        }
    }
    user1.print(); // 'userTest'
    user2.print(); // undefined, arrow function this refers to the global context of execution (globalThis)
    Copy the code

    Const partial = (fn, const partial = (fn,… args) => fn.bind(null,… args); This method causes the internal this to be lost

    const partial = (fn, ... args) = > fn.bind(null. args);const user = {
        name: 'userTest'.print: function(job, age) {
            console.log(job, this.name,age);
        }
    }
    user.print = partial(user.print, 'teacher');
    user.print('18'); // teacher undefined 18, this.name failed to get
    Copy the code

    So the implementation takes into account the possibility that the function may call this, where null can’t be directly changed to this, where this is the global object, and once this is inside the binding function FN, it will all be persisted to the global object, not the execution context. You can return a function with a runtime context (a non-arrow function), which is guaranteed to be correct by assigning the inner this to the execution context when called

    const partial = (fn, ... args) = > {
        // returns a function, so the internal return value needs to be executed immediately
        return function(. fnArgs) {
            // The output of this depends on when called
            return fn.call(this. args, ... fnArgs); }}const greet = function(greeting, name) { 
        return greeting + ' ' + name;
    }; 
    const sayHelloTo = partial(greet, 'hello');
    sayHelloTo('fred'); // 'hello fred'
    
    function print(job, age) {
        console.log(job, this.name, age);
    }
    const teachers = [{name: 't1'.print: partial(print, 'teacher')}];
    const students = [{name: 's1'.print: partial(print, 'student')}];
    teachers[0].print('38'); // teacher t1 38
    students[0].print('18'); // student s1 18
    Copy the code
  • Objects have two types of properties, ordinary data properties and getters/setters. Accessor properties are essentially functions used to get and set values (you can intercept, filter, process, etc.), but look like regular properties from external code

    const user = { 
        name: "John".surname: "Smith".get fullName() {
            return `The ${this.name} The ${this.surname}`; 
        },
        set fullName(value) {[this.name, this.surname] = value.split(' '); }};console.log(user.fullName); // John Smith
    user.fullName = 'test fullName';
    console.log(user.fullName); // test fullName
    Copy the code
  • There are methods for manipulating stereotypes in objects

    • Object.getPrototypeOf(obj)Returns the objectobj 的 [[Prototype]].
    • Object.setPrototypeOf(obj, proto)The objectobj 的 [[Prototype]]Set toproto.
    • Object.create(proto, [descriptors])Using a givenprotoAs a[[Prototype]]And optional property descriptions to create an empty object.

    This method should be used instead of obj.__proto__.__proto__ itself is only the accessor property of [[Prototype]], which sets /get [[Prototype]]. For this reason, if you have an object implementation that accepts all attributes it will find that __proto__ as a string is not properly assigned to the object, because __proto__ as the object’s protoobject accessor attribute is only allowed to be NULL or an object

    const obj = {};
    obj.__proto__ = 1234;
    console.log(obj.__proto__); // a prototype object
    console.log(obj.toString); // [object Object]
    Copy the code

    Create (null) creates an empty Object. This Object has no Prototype ([[Prototype]] is null). This Object is very clean and simpler than {}. It doesn’t have methods like toString on the object itself, and __proto__ is only an object property and can’t operate on the object’s prototype any more. GetPrototypeOf (obj) and setPrototypeOf(obj, proto) are required to operate.

    const obj = Object.create(null);
    obj.__proto__ = 1234;
    console.log(obj.__proto__); / / 1234
    console.log(obj.toString); // undefined
    Copy the code
  • The class keyword is a variant of the constructor, but not quite syntactic sugar, with some extra identifiers and so on

    class User {
        constructor(name){
            this.name = name;
        }
    
        getName(){
            return this.name; }}const user = new User('zhangsan');
    console.log(user.getName()); // zhangsan
    Copy the code

    This code in class is equivalent to the following code

    function User(name) {
        this.name = name;
    }
    User.prototype = {
        constructor:User,
        getName() {
            return this.name; }}Copy the code

    But you can see some differences in their [[Prototype]]

    ____________________

    Class methods are not enumerable. The class definition sets enumerable to false for all methods in “Prototype”.

  • There are several common methods of inheritance in JS, including the extends keyword for class classes. The principle is similar, and they are all implemented through a chain of prototypes

    class Animal {
        constructor(name) {
            this.name = name;
        }
        
        getName() {
            return this.name; }}class Rabbit extends Animal {
        constructor(name, age) {
            super(name); // The super keyword must be used before assignment
            this.age = age;
        }
        
        getInfo() {
            return {
                name: super.getName(),
                age: this.age,
            }
        }
    }
    
    const rabbit = new Rabbit('xiaobai'.3);
    console.log(rabbit.getInfo()); // {name: "xiaobai", age: 3}
    Copy the code

    The constructor function of a subclass must use super before assigning this object properties (e.g. This.age = age) for the following reasons

    class Animal {
        name = 'animal'; 
        constructor() { 
            console.log(this.name); }}class Rabbit extends Animal {
        name = 'rabbit';
    }
    const animal = new Animal(); // animal 
    const rabbit = new Rabbit(); // animal
    console.log(rabbit.name); // rabbit
    Copy the code

    For the base class (Animal) the initialization will happen before the constructor call, so the constructor call will get this.name (new calls Animal() print “Animal”), otherwise this.name will be undefined; The Rabbit class initializes its Animal property first, then calls its constructor, and then initializes its own property. If you want to insert super at this point without affecting the original initialization order, you need to call super before the attribute of this object is assigned. In multilevel inheritance of classes

    class A {
        name = 'A';
        speak() {
            console.log(this.name);
            console.log('A speak'); }}class B extends A {
        name = 'B'
        speak() {
            super.speak();
            console.log('B speak'); }}class C extends B {
        name = 'C'
        speak() {
            super.speak();
            console.log('C speak'); }} (new C()).speak();
    Copy the code

    The following results are obtained after execution:



    If the object’s prototype chain is used to implement this multiple inheritance:

    const a = {
        name: 'A'.speak() {
            console.log(this.name);
            console.log('A speak'); }}const b = {
        name: 'B'.speak() {
            a.speak();
            console.log('B speak'); }}const c = {
        name: 'C'.speak() {
            b.speak();
            console.log('C speak');
        }
    }
    
    c.speak();
    Copy the code

    As long as the output from a c. peak() call is the same as that from the extends implementation, there should be no other objects in a, B, or C method calls (such as A.peak () in B’s speak). Bind, call, and apply can be used. After adding the prototype and modifying the this pointer, the code looks like this:

    const a = {
        name: 'A'.speak() {
            console.log(this.name);
            console.log('A speak'); }}const b = {
        name: 'B'.speak() {
            Object.getPrototypeOf(this).speak.call(this);
            console.log('B speak'); }}const c = {
        name: 'C'.speak() {
            Object.getPrototypeOf(this).speak.call(this);
            console.log('C speak'); }}Object.setPrototypeOf(c, b); // The prototype of c is B
    Object.setPrototypeOf(b, a); // The prototype of b is A
    c.speak();
    Copy the code

    Execution found that the above code was in an infinite loop, but there was “no logical problem”; In fact, object.getPrototypeof (this).speak.call(this) passes itself to its base class in c’s speak function, so this in base CLASS B’s speak function is actually C, Therefore, object.getPrototypeof (c).speak.call(c) is called in the speak function of Object B, which forms a loop with the speak function of c. What the speak function in the B object wants to do is call its base class’s Speak function and pass it this. Add a variable to each speak function to record its environment (this should be this, but call, bind, apply will change this).

    const a = {
        name: 'A'.speak() {
            const environmentRecord = a;
            console.log(this.name);
            console.log('A speak'); }}const b = {
        name: 'B'.speak() {
            const environmentRecord = b;
            Object.getPrototypeOf(environmentRecord).speak.call(this);
            console.log('B speak'); }}const c = {
        name: 'C'.speak() {
            const environmentRecord = c;
            Object.getPrototypeOf(environmentRecord).speak.call(this);
            console.log('C speak'); }}Object.setPrototypeOf(c, b); // The prototype of c is B
    Object.setPrototypeOf(b, a); // The prototype of b is A
    c.speak();
    Copy the code

    The above code implements the function of super, but each object’s function has a piece of code that records its environment. To provide a solution, JavaScript adds a special internal property to the function: [[HomeObject]]. Our implementation of environmentRecord is similar, while calling super.speak() is equivalent to Object.getPrototypeof (environmentRecord).speak.call(this). ECMAScript® 2015 Language Specification [[HomeObject]]

  • Classes in JS have static properties and methods that do not belong to any object but to the class itself (not on the prototype of the function).

    class A {
        constructor(name) {
            this.name = name;
        }
        static create(name) {
            return new this(name);
        }
        getName() {
            return this.name; }}const a1 = new A('a1');
    const a2 = A.create('a2');
    console.log(a1.getName(), a2.getName()); // "a1" "a2"
    Copy the code

    Not only regular properties and methods can be inherited, but also static properties and methods

    class A {
        constructor(name) {
            this.name = name;
        }
        static create(name) {
            return new this(name);
        }
        static type = 'static property';
        getName() {
            return this.name; }}class B extends A {}
    const b1 = new B('b1');
    const b2 = B.create('b2');
    console.log(B.type, b1.getName(), b2.getName()); // "static property" "b1" "b2"
    Copy the code

    During object creation, normal methods are created in the function’s Prototype property. When an object is created using new, the object’s prototype points to the function’s Prototype property. In the inheritance process, the prototype of the prototype property of a subclass (itself an object) points to the prototype property of the base class, which implements the inheritance of ordinary functions. Subclasses and base classes themselves are functions and objects, so static functions and static properties are mounted in the object. When inheriting a subclass, its prototype points to the base class, and static properties and static methods are inherited

    class A {
        constructor(name) {
            this.name = name;
        }
        static create(name) {
            return new this(name);
        }
        static type = 'static property';
        getName() {
            return this.name; }}const a = new A('a');
    console.log(Object.getPrototypeOf(a) === A.prototype); // true
    
    class B extends A {}
    console.log(Object.getPrototypeOf(B.prototype) === A.prototype); // true
    console.log(Object.getPrototypeOf(B) === A); // true
    Copy the code