-
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 functionThe call (the context, arg1, arg2)...
This changes the reference of this inside the function to context and executes the function immediatelyapply(context, args)
And:call
The difference lies in the difference in the transmission of parameters,apply
The second argument accepted is the class array argument, andcall
The 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 givenproto
As 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