preface

In the “preliminary and intermediate front-end JavaScript self-test Checklist – 1” part, and we have simply gone through the basic knowledge of JavaScript, have not seen friends can review 😁

This article series is my “Modern JavaScript Assault Team” within our team. The first installment is the output of the second part of the modern JavaScript Tutorial series. I hope this self-test list will help you consolidate your knowledge and refresh your knowledge.

This part of the content toJavaScript objectMainly, including the following contents:

An object,

JavaScript has eight data types, seven primitive types whose values contain only one type (string, number, or other), and objects, which are used to hold key-value pairs and more complex entities. We can do this by using curly braces with an optional property list **{… {“key” : “value”}, where the key is a string (or property name), and the value can be any type.

1. Create an object

We can create a new object in two ways:

// 1. Create using the constructor
let user = new Object(a);// 2. Create by literal
let user = {};
Copy the code

2. Object text and properties

When an object is created, some properties of the object can be initialized:

let user = {
	name : 'leo'.age  : 18
}
Copy the code

We can then add, delete, modify, and check the properties of this object:

// Add attributes
user.addr = "China";
// user => {name: "leo", age: 18, addr: "China"}

// Delete the attribute
delete user.addr
// user => {name: "leo", age: 18}

// Modify the properties
user.age  = 20;
// user => {name: "leo", age: 20}

// Find the property
user.age;
/ / 20
Copy the code

3. Use of square brackets

Of course, the key of an object can also be a multi-word attribute, but it must be enclosed in quotation marks. When used, it must be read using square brackets ([]) :

let user = {
	name : 'leo'."my interest" : ["coding"."football"."cycling"]
}
user["my interest"]; // ["coding", "football", "cycling"]
delete user["my interest"];
Copy the code

We can also use variables in square brackets to get the value of the property:

let key = "name";
let user = {
	name : "leo".age  : 18 
}
// ok
user[key]; // "leo"
user[key] = "pingan";

// error
user.key; // undefined
Copy the code

4. Calculate attributes

When you create an object, you can use square brackets in object literals, that is, evaluate properties:

let key = "name";
let inputKey = prompt("Please enter key"."age");
let user = {
	[key] : "leo",
  [inputKey] : 18
}
// When the user enters "age" on the Prompt, the user will look like this:
// {name: "leo", age: 18}
Copy the code

Of course, evaluated properties can also be expressions:

let key = "name";
let user = {
	["my_" + key] : "leo"
}
user["my_" + key]; // "leo"
Copy the code

5. Attribute name shorthand

In real development, the same attribute name and attribute value can be abbreviated to a shorter syntax:

// The original way of writing
let getUser = function(name, age){
  // ...
	return {
		name: name,
    age: age
	}
}

// Short form
let getUser = function(name, age){
  // ...
	return {
		name,
    age
	}
}
Copy the code

It can also be mixed:

// The original way of writing
let getUser = function(name, age){
  // ...
	return {
		name: name,
    age: 18}}// Short form
let getUser = function(name, age){
  // ...
	return {
		name,
    age: 18}}Copy the code

6. Object attribute existence detection

6.1 Using the IN Keyword

This method can determine whether the object’s own properties and inherited properties exist.

let user = {name: "leo"};
"name" in user;            // True, the property exists
"age"  in user;            //false
"toString" in user;     // True is an inherited property
Copy the code

6.2 Use the hasOwnProperty() method of the object.

This method can only determine whether the own property exists, and returns false for inherited properties.

let user = {name: "leo"};
user.hasOwnProperty("name");       //true, there is a name in the property
user.hasOwnProperty("age");        //false, there is no age in the property
user.hasOwnProperty("toString");   //false, this is an inherited property, but not a property
Copy the code

6.3 Using undefined

This method can determine the object’s own properties and inherited properties.

let user = {name: "leo"}; user.name ! = =undefined;        // trueuser.age ! = =undefined;        // falseuser.toString ! = =undefined     // true
Copy the code

The problem with this method is that if the value of the property is undefined, the method cannot return the desired result:

let user = {name: undefined}; user.name ! = =undefined;        // false, attribute exists, but the value is undefineduser.age ! = =undefined;        // falseuser.toString ! = =undefined;    // true
Copy the code

6.4 Direct judgment in conditional statements

let user = {};
if(user.name) user.name = "pingan";
// If the name is undefined, null, false, "", 0, or NaN, it will remain the same

user; / / {}
Copy the code

7. Object loop traversal

When we need to traverse every property in an object, we can use for… In statement

7.1 the for… In circulation

for… The in statement iterates over the enumerable properties of an object other than Symbol in any order. Note: for… In should not be applied to an array where index order is important.

let user = {
	name : "leo".age  : 18
}

for(let k in user){
	console.log(k, user[k]);
}
// name leo
// age 18
Copy the code

7.2 New Method for ES7

The newly added object.values () and object.entries () in ES7 are similar to the previous object.keys () and return the array type.

1. Object.keys()

Returns an array of keys for all traversable properties of the argument object itself (not inherited).

let user = { name: "leo".age: 18};
Object.keys(user); // ["name", "age"]
Copy the code

2. Object.values()

Returns an array of keys for all traversable properties of the argument object itself (not inherited).

let user = { name: "leo".age: 18};
Object.values(user); // ["leo", 18]
Copy the code

If the argument is not an object, return an empty array:

Object.values(10); // [] Object.values(true); / / []Copy the code

3. Object.entries()

Returns an array of key-value pairs for all traversable properties of the argument object itself (not inherited).

let user = { name: "leo".age: 18};
Object.entries(user);
// [["name","leo"],["age",18]]
Copy the code

Manually implement the object.entries () method:

// The Generator function implements:
function* entries(obj){
    for (let k of Object.keys(obj)){
        yield[k ,obj[k]]; }}// Non-generator function implementations:
function entries (obj){
    let arr = [];
    for(let k of Object.keys(obj)){
        arr.push([k, obj[k]]);
    }
    return arr;
}
Copy the code

4. Object.getOwnPropertyNames(Obj)

This method returns an array containing the names of all the properties owned by object Obj, whether or not it is enumerable.

let user = { name: "leo".age: 18};
Object.getOwnPropertyNames(user);
// ["name", "age"]
Copy the code

Object copy

If you don’t know how to assign value in JS, please see here.

1. Assignment operation

Let’s first review the basic data types and reference data types:

  • Basic types of

Concept: Basic type values occupy a fixed size in memory and are held in stack memory (excluding variables in closures). Common include: undefined, null, Boolean, String, Number, Symbol

  • Reference types

Concept: The value of a reference type is an object and is stored in heap memory. The stack memory stores the variable identifier of the object and the address of the object stored in the heap memory (reference). The reference data type stores a pointer in the stack that points to the starting address of the entity in the heap. When the interpreter looks for a reference value, it first retrieves its address in the stack and then retrieves the entity from the heap. Common include: Object, Array, the Date, the Function, the RegExp, etc

1.1 Basic data type assignment

When the data in the stack memory changes, the system will automatically allocate a new value for the new variable in the stack memory. The two variables are independent of each other and do not affect each other.

let user  = "leo";
let user1 = user;
user1 = "pingan";
console.log(user);  // "leo"
console.log(user1); // "pingan" 
Copy the code

1.2 Assigning values to reference data types

In JavaScript, variables do not store the object itself, but rather its “address in memory,” in other words, a “reference” to it. For example, the following Leo variable simply holds a reference to the user object:

let user = { name: "leo".age: 18};
let leo  = user;
Copy the code

Other variables can also refer to the User object:

let leo1 = user;
let leo2 = user;
Copy the code

However, since the variable is a reference, when we change the value of the variable Leo \ leo1 \ leo2, we also change the value of the reference object user, but when user is changed, all other variables that refer to that object will change their value:

leo.name = "pingan";
console.log(leo);   // {name: "pingan", age: 18}
console.log(leo1);  // {name: "pingan", age: 18}
console.log(leo2);  // {name: "pingan", age: 18}
console.log(user);  // {name: "pingan", age: 18}

user.name = "pingan8787";
console.log(leo);   // {name: "pingan8787", age: 18}
console.log(leo1);  // {name: "pingan8787", age: 18}
console.log(leo2);  // {name: "pingan8787", age: 18}
console.log(user);  // {name: "pingan8787", age: 18}
Copy the code

This process involves the variable address pointer pointing to the problem, here temporarily not to discuss, interested friends can look up relevant information online.

2. Object comparison

When two variables refer to the same object, both == and === return true.

let user = { name: "leo".age: 18};
let leo  = user;
let leo1 = user;
leo ==  leo1;   // true
leo === leo1;   // true
leo ==  user;   // true
leo === user;   // true
Copy the code

But if two variables are an empty object {}, they are not equal:

let leo1 = {};
let leo2 = {};
leo1 ==  leo2;  // false
leo1 === leo2;  // false
Copy the code

3. The shallow copy

3.1 concept

Concept: A new object copies the values of non-object properties and references to object properties of an existing object. It can also be understood as: a new object directly copies a reference to the object properties of an existing object, that is, a shallow copy.

The shallow copy only copies the first layer property. If the first layer property value is of a basic data type, the new object and the original object do not affect each other, but if the first layer property value is of a complex data type, the new object and the original object point to the same memory address.

Example code to demonstrate that the shallow copy scenario is not used:

// Example 1: original copy of object
let user = { name: "leo".skill: { JavaScript: 90.CSS: 80}};
let leo = user;
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1"
console.log(user.name);     // "leo1"
console.log(leo.skill.CSS); / / 90
console.log(user.skill.CSS);/ / 90

// Example 2: raw copy of array
let user = ["leo"."pingan", {name: "pingan8787"}];
let leo  = user;
leo[0] = "pingan888";
leo[2] ["name"] = "pingan999";
console.log(leo[0]);          // "pingan888"
console.log(user[0]);         // "pingan888"
console.log(leo[2] ["name"]);  // "pingan999"
console.log(user[2] ["name"]); // "pingan999"
Copy the code

As you can see from the above example code, since the object is copied directly, equivalent to copying the reference data type, whenever any value is changed by the new object, the source data is changed.

Next implement shallow copy, compare the following.

3.2 Implementing shallow copy

1. Object.assign() 

Syntax: object.assign (target,… Sources) is a method of copying objects in ES6. The first argument is the target of the copy, and the remaining argument is the source object (there can be multiple) of the copy. For details, you can read the MDN Object.Assign document.

// Example 1: Object shallow copy
let user = { name: "leo".skill: { JavaScript: 90.CSS: 80}};
let leo = Object.assign({}, user);
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ difference!
console.log(user.name);     // "Leo" ⚠️ difference!
console.log(leo.skill.CSS); / / 90
console.log(user.skill.CSS);/ / 90

// Example 2: Shallow copy of an array
let user = ["leo"."pingan", {name: "pingan8787"}];
let leo  = Object.assign({}, user);
leo[0] = "pingan888";
leo[2] ["name"] = "pingan999";
console.log(leo[0]);          // "pingan888" ⚠️ difference!
console.log(user[0]);         // "Leo" ⚠️ difference!
console.log(leo[2] ["name"]);  // "pingan999"
console.log(user[2] ["name"]); // "pingan999"
Copy the code

As you can see from the print, a shallow copy only creates a new object at the root property (the first level of the object), but only copies the same memory address if the value of the property is an object.

Object.assign() :

  • Copy only the source object’s own properties (not inherited properties).
  • Non-enumerable properties of the object are not copied;
  • Properties,SymbolValue, which can be copied by object.assign;
  • undefinedandnullThey can’t be converted to objects. They can’t be used asObject.assignParameter, but can be used as a source object.
Object.assign(undefined); / / an error
Object.assign(null);      / / an error

Object.assign({}, undefined); / / {}
Object.assign({}, null);      / / {}

let user = {name: "leo"};
Object.assign(user, undefined) === user; // true
Object.assign(user, null)      === user; // true
Copy the code

2. Array.prototype.slice()

Syntax: arr.slice([begin[, end]]) The slice() method returns a new array object that is a shallow copy of the original array determined by begin and end (including begin but not end). The original array will not be changed. For details, read the MDN Array Slice document.

// Sample array shallow copy
let user = ["leo"."pingan", {name: "pingan8787"}];
let leo  = Array.prototype.slice.call(user);
leo[0] = "pingan888";
leo[2] ["name"] = "pingan999";
console.log(leo[0]);          // "pingan888" ⚠️ difference!
console.log(user[0]);         // "Leo" ⚠️ difference!
console.log(leo[2] ["name"]);  // "pingan999"
console.log(user[2] ["name"]); // "pingan999"
Copy the code

3. Array.prototype.concat()

Concat (value1[, value2[,…[, valueN]]]) The concat() method is used to merge two or more arrays. This method does not change the existing array, but returns a new array. For details, you can read the MDN Array Concat document.

let user  = [{name: "leo"},   {age: 18}];
let user1 = [{age: 20}, {addr: "fujian"}];
let user2 = user.concat(user1);
user1[0] ["age"] = 25;
console.log(user);  // [{"name":"leo"},{"age":18}]
console.log(user1); // [{"age":25},{"addr":"fujian"}]
console.log(user2); // [{"name":"leo"},{"age":18},{"age":25},{"addr":"fujian"}]
Copy the code

Array.prototype.concat is also a shallow copy, creating a new object in the root property (the first level of the object), but only copying the same memory address if the property value is an object.

4. Expand the operator (…)

Syntax: var cloneObj = {… obj }; The extension operator is also a shallow copy. It is not possible to copy a property whose value is an object into two different objects, but it is also a convenient advantage if the property is a value of a primitive type.

let user = { name: "leo".skill: { JavaScript: 90.CSS: 80}};
letleo = {... user}; leo.name ="leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ difference!
console.log(user.name);     // "Leo" ⚠️ difference!
console.log(leo.skill.CSS); / / 90
console.log(user.skill.CSS);/ / 90
Copy the code

3.3 Handwritten shallow copy

Implementation principle: A new object copies the values of non-object properties and references to object properties of an existing object, that is, object properties are not copied into memory.

function cloneShallow(source) {
    let target = {};
    for (let key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; }}return target;
}
Copy the code
  • for in

for… The in statement iterates over an object’s own, inherited, enumerable, non-symbol properties in any order. For each different property, the statement is executed.

  • hasOwnProperty

This function returns a Boolean value. All objects that inherit from Object inherit from the hasOwnProperty method. Unlike the in operator, this function ignores the properties inherited from the prototype chain and its own properties. Syntax: obj. HasOwnProperty (prop) Prop is the string name or Symbol of the property to be checked.

4. A deep copy

4.1 concept

Copy variable values and, for reference data, recurse to the base type before copying. The deep-copy objects are completely isolated from the original ones. The modification to one object does not affect the other.

4.2 Implementing deep Copy

1. JSON.parse(JSON.stringify())

The idea is to serialize an object into a JSON string, convert the contents of the object into a string and save it on disk, and then deserialize the JSON string into a new object using json.parse ().

let user = { name: "leo".skill: { JavaScript: 90.CSS: 80}};
let leo = JSON.parse(JSON.stringify(user));
leo.name = "leo1";
leo.skill.CSS = 90;
console.log(leo.name);      // "leo1" ⚠️ difference!
console.log(user.name);     // "Leo" ⚠️ difference!
console.log(leo.skill.CSS); // 90 ⚠️ difference!
console.log(user.skill.CSS);// 80 ⚠️ difference!
Copy the code

Json.stringify () :

  • If the value of the copied object has a function,undefinedsymbolPast theJSON.stringify()‘The key-value pair disappears in the serialized JSON string;
  • Cannot copy non-enumerable properties, cannot copy the prototype chain of the object;
  • copyDateThe reference type becomes a string;
  • copyRegExpThe reference type becomes an empty object;
  • Object containingNaNInfinity-Infinity, the serialized result becomesnull
  • A cyclic application that cannot copy an object (i.eobj[key] = obj).

2. Third-party libraries

4.3 Handwritten Deep Copy

The core idea is recursion, traversing objects and arrays until they are all basic data types, and then copying, deep copying. Implementation code:

const isObject = obj= > typeof obj === 'object'&& obj ! =null;

function cloneDeep(source) {
    if(! isObject(source))return source; // Non-objects return themselves
    const target = Array.isArray(source) ? [] : {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep(source[key]); // Notice here
            } else{ target[key] = source[key]; }}}return target;
}
Copy the code

The drawback of this approach: if you encounter circular references, you get stuck in a circular recursive process that causes the stack to explode. For other tips, read how to Write a Deep Copy of an Amazing Interviewer? .

5. Summary

Shallow copy: Each property of an object is copied sequentially, but when the property value of an object is of a reference type, the reference is essentially copied, and when the value to which the reference points changes.

Deep copy: Copy variable values and, for reference data, recurse to basic types before copying. The deep-copy objects are completely isolated from the original ones. The modification to one object does not affect the other.

Deep copy and shallow copy are for complex data types. The shallow copy copies only one layer, while the deep copy copies multiple layers.

3. Garbage Collection Mechanism (GC)

Garbage Collection (GC) is an automatic memory management mechanism. When some memory occupied by a program is no longer accessible by the program, the program returns that memory to the operating system using garbage collection algorithms. A garbage collector can reduce the burden on the programmer and reduce errors in the program. Garbage collection originated in LISP. Many languages such as Smalltalk, Java, C#, and D support garbage collectors, and JavaScript is known to have an automatic garbage collection mechanism.

In JavaScript, raw type data is allocated in stack space, and reference type data is allocated in heap space.

1. Garbage collection in the stack space

When the functionshowNameAfter the call is complete, pass downExtended Stack Pointer (ESP)Pointer to destroyshowNameFunction, and later when other functions are called, the old memory is overwritten, the execution context of another function is stored, and garbage collection is implemented.Image from How Browsers Work in Practice

2. Garbage collection in heap space

The foundation of The data-garbage collection strategy in The heap is: The Generational Hypothesis. That is:

  1. Most objects live in memory for a very short time, and many become inaccessible very quickly.
  2. Immortal objects will live longer.

These two features apply not only to JavaScript, but also to most dynamic languages such as Java, Python, and so on. V8 divides the heap space into new generation (for short-lived objects) and old generation (for long-lived objects) and uses different garbage collectors.

  • Vice garbage collector, mainly responsible for the new generation of garbage collection.
  • The main garbage collector, mainly responsible for the old generation of garbage collection.

Regardless of the type of garbage collector, the same garbage collection process is used: marking the active and inactive objects, reclaiming the memory of the inactive objects, and finally sorting the memory. 支那

1.1 A garbage collector

Apply the Scavenge algorithm to divide the Cenozoic space into two regions, an object region and an idle region.Image from How Browsers Work in Practice

Execution process:

  • New objects exist in the object area. When the object area is about to be full, a garbage collection is performed.
  • In the process of garbage collection, the garbage in the object area is marked first, and then the auxiliary garbage collector copies the living objects and arranges them into the free area in order, which is equivalent to the completion of memory sorting.
  • When the replication is complete, the object area and the free area are reversed to complete the garbage collection operation, which also allows the two areas in the new generation to be reused indefinitely.

Of course, there are some problems with this: if the data of the replication operation is large, the cleaning efficiency will be affected. The solution of the JavaScript engine is to make the new generation area small and use the object promotion strategy (objects that survive two recollections will be moved to the old area) to avoid the problem that the new generation area is small and the remaining objects will fill the whole area.

1.2 Main garbage collector

Mark-sweep algorithm and Mark-compact algorithm are divided into three categories: mark-sweep algorithm and Mark-compact algorithm.

A) Mark-sweep algorithm process:

  • Marking process: starting from a set of root elements to traverse the entire element, the elements that can be reached are active objects, and the other is garbage data;
  • Cleanup process: clean up the marked data, and generate a large number of fragments of memory. (Disadvantages: large objects cannot be allocated enough contiguous memory)

Image from How Browsers Work in Practice

B) Mark-compact algorithm process:

  • Marking process: starting from a set of root elements to traverse the entire element, the elements that can be reached are active objects, and the other is garbage data;
  • Sorting process: All the living objects are moved to a segment, and then the contents beyond the end boundary are cleared.

Image from How Browsers Work in Practice

3. Expand your reading

2. “MDN Memory Management”

Object methods and this

1. Object methods

For details, read the MDN Method Definition. Call methods that are properties of objects “object methods,” such as the following user object’s say method:

let user = {};
let say = function(){console.log("hello!")};

user.say = say;  // Assign a value to an object
user.say(); // "hello!"
Copy the code

You can also use a more concise approach:

let user = {
	say: function(){}
  
  / / for short
	say (){console.log("hello!")}

	// ES8 async method
	async say (){/... /}
}
user.say();
Copy the code

The name of the object method, of course, also supports the name of the computed property as the method name:

const hello = "Hello";
let user = {
	['say' + hello](){console.log("hello!")}
}
user['say' + hello](); // "hello!"
Copy the code

Also note that all method definitions are not constructors, and TypeError will be raised if you try to instantiate them.

let user = {
	say(){};
}
new user.say; // TypeError: user.say is not a constructor
Copy the code

2. this

2.1 this profile

When an object method needs to use an object property, use this keyword:

let user = {
	name : 'leo'.say(){ console.log(`hello The ${this.name}`)}
}

user.say(); // "hello leo"
Copy the code

When the code user.say() is executed, this refers to the user object. You can also refer to the say() method using the name user:

let user = {
	name : 'leo'.say(){ console.log(`hello ${user.name}`)}
}

user.say(); // "hello leo"
Copy the code

This is not safe, however, because the user object might assign to another variable and assign other values to the user object, which might cause an error:

let user = {
	name : 'leo'.say(){ console.log(`hello ${user.name}`)}}let leo = user;
user = null;

leo.say(); // Uncaught TypeError: Cannot read property 'name' of null
Copy the code

But change user.name to this.name and the code works.

2.2 this value

The value of this is computed at runtime, and its value depends on the code context:

let user = { name: "leo"};
let admin = {name: "pingan"};
let say = function (){
	console.log(`hello The ${this.name}`)}; user.fun = say; admin.fun = say;// Inside the function this refers to the object "before the dot"
user.fun();     // "hello leo"
admin.fun();    // "hello pingan"
admin['fun'] ();// "hello pingan"
Copy the code

Rule: if obj.fun() is called, this is obj during the fun call, so this above is first user, then admin.

But in the global environment, this points to the global object whether strict mode is turned on or not

console.log(this= =window); // true

let a = 10;
this.b = 10;
a === this.b; // true
Copy the code

2.3 Arrow functions do not have their own this

The arrow function is special in that it does not have its own “this”. If there is a reference to “this”, it refers to an external normal function. In the following example, this refers to the user.say() method:

let user = {
	name : 'leo'.say : () = > {
  	console.log(`hello The ${this.name}`);
  },
  hello(){
		let fun = () = > console.log(`hello The ${this.name}`);
    fun();
	}
}

user.say();   // hello => say() The external function is window
user.hello(); // hello Leo => fun() the external function is hello
Copy the code

2.4 Call/apply/bind

Detailed can read “JS Basics – all about call,apply,bind”. When we want to bind this value to another environment, we can do so using the call/apply/bind method:

var user = { name: 'leo' };
var name = 'pingan';
function fun(){
	return console.log(this.name); // The value of this depends on how the function is called
}

fun();           // "pingan"
fun.call(user);  // "leo"
fun.apply(user); // "leo"
Copy the code

Var name = ‘pingan’; You need to use var to declare it. If you use let, there will be no name variable on the window.

The syntax of the three is as follows:

fun.call(thisArg, param1, param2, ...) fun.apply(thisArg, [param1,param2,...] ) fun.bind(thisArg, param1, param2, ...)Copy the code

Constructors and new operators

1. Constructor

The purpose of the constructor is to implement reusable object creation code. In general, there are two conventions for constructors:

  • Capitalize the first letter when naming;
  • Can only usenewOperator execution.

The new operator creates an instance of a user-defined object type or an instance of a built-in object with a constructor. The syntax is as follows:

new constructor[([arguments])]
Copy the code

The parameters are as follows:

  • constructorA class or function that specifies the type of an object instance.
  • argumentsOne is used byconstructorThe argument list of the call.

2. Simple example

Here’s a simple example:

function User (name){
	this.name = name;
  this.isAdmin = false; 
}
const leo = new User('leo');
console.log(leo.name, leo.isAdmin); // "leo" false
Copy the code

3. How the new operator operates

When a function is executed using the new operator, it follows these steps:

  1. A new empty object is created and assigned tothis.
  2. Function body execution. Usually it changesthisTo add new attributes to it.
  3. returnthisThe value of the.

Take the previous User method as an example:

function User(name) {
  // this = {}; (Implicitly created)

  // Add attributes to this
  this.name = name;
  this.isAdmin = false;

  // return this; (Implicit return)
}
const leo = new User('leo');
console.log(leo.name, leo.isAdmin); // "leo" false
Copy the code

When we execute new User(‘ Leo ‘), the following happens:

  1. An inheritance fromUser.prototypeA new object is created for;
  2. Calls the constructor with the specified argumentsUserAnd willthisBind to the newly created object;
  3. The object returned by the constructor isnewThe result of the expression. If the constructor does not explicitly return an object, the object created in Step 1 is used.

Note:

  1. Normally, constructors do not return values, but developers can choose to override the normal object creation step by actively returning objects;
  2. new UserIs equivalent tonew User()The parameter list is not specified, that isUserWith no parameters;
let user = new User; // <-- no arguments
/ / is equivalent to
let user = new User();
Copy the code
  1. Any function can be used as a constructor, that is, any function can be usednewOperator.

4. Methods in constructors

In the constructor, we can also bind methods to this:

function User (name){
	this.name = name;
  this.isAdmin = false; 
	this.sayHello = function(){
		console.log("hello " + this.name); }}const leo = new User('leo');
console.log(leo.name, leo.isAdmin); // "leo" false
leo.sayHello(); // "hello leo"
Copy the code

Six, optional chain?” .”

For details, see MDN Optional Chain Operators.

1. Background

In actual development, the following error cases often occur:

// 1. The specified attribute does not exist in the object
const leo = {};
console.log(leo.name.toString()); 
// Uncaught TypeError: Cannot read property 'toString' of undefined

// 2. Use a nonexistent DOM node attribute
const dom = document.getElementById("dom").innerHTML; 
// Uncaught TypeError: Cannot read property 'innerHTML' of null
Copy the code

In the optional chain? Before., we would have solved this problem by short-circuiting the && operator:

const leo = {};
console.log(leo && leo.name && leo.name.toString()); // undefined
Copy the code

The downside of this is that it’s a lot of trouble.

2. Optional chains

Optional chain? Is an error-proof way to access the properties of nested objects. Even if the intermediate property does not exist, there will be no error. What if the chain is optional? If the first part is undefined or null, it will stop the operation and return undefined.

Grammar:

obj? .prop obj? .[expr] arr? .[index] func? .(args)Copy the code

** We modify the previous example code:

// 1. The specified attribute does not exist in the object
const leo = {};
console.log(leo? .name?.toString());// undefined

// 2. Use a nonexistent DOM node attribute
const dom = document? .getElementById("dom")? .innerHTML;// undefined
Copy the code

3. Use attention

Optional chains are useful, but the following points need to be noted:

  1. Do not overuse optional chains;

Should we just? . Used where properties or methods may not exist. Take the example code above:

const leo = {};
console.log(leo.name?.toString()); 
Copy the code

This is better because the Leo object must exist and the name attribute may not exist.

  1. Optional chain? . Previous variables must be declared;

In the optional chain? Let /const/var ();

leo? .name;// Uncaught ReferenceError: leo is not defined
Copy the code
  1. Optional chains cannot be used for assignment;
letobject = {}; object? .property =1; 
// Uncaught SyntaxError: Invalid left-hand side in assignment
Copy the code
  1. Optional chain access to array elements;
letarrayItem = arr? .42];
Copy the code

4.? . (), and? [].

One caveat? .is a special syntax construct, not an operator, and can also be used with () and [] :

4.1 Optional Chains and Function calls? . ()

? .() is used to call a function that may not exist, such as:

let user1 = {
  admin() {
    alert("I am admin"); }}letuser2 = {}; user1.admin? . ();// I am adminuser2.admin? . ();Copy the code

? .() checks the left side of it: if the admin function exists, then it is called to run (for user1). Otherwise (for user2) the operation stops without error.

4.2 Optional Chains and Expressions? [].

? .[] Allows properties to be safely read from an object that may not exist.

let user1 = {
  firstName: "John"
};

let user2 = null; // Assume that we cannot authorize this user

let key = "firstName"; alert( user1? .[key] );// Johnalert( user2? .[key] );// undefinedalert( user1? .[key]? .something? .not? .existing);// undefined
Copy the code

5. Optional chain? .Syntax summary

Optional chain? There are three forms of grammar:

  1. obj? .prop– ifobjReturns if it existsobj.propOtherwise returnundefined.
  2. obj? .[prop]– ifobjReturns if it existsobj[prop]Otherwise returnundefined.
  3. obj? .method()– ifobjIf there isobj.method()Otherwise returnundefined.

As we can see, these grammatical forms are straightforward to use. ? Check whether the left part is null/undefined. If not, continue. ? .chains give us secure access to nested properties.

Seven, Symbol

The specification states that attributes of objects in JavaScript can only be of type string or Symbol, since we’ve only seen these two types.

1. Concept introduction

ES6 introduced Symbol as a new primitive data type that represents unique values, primarily to prevent attribute name conflicts. After ES6, JavaScript has a total of data types: Symbol, undefined, NULL, Boolean, String, Number, Object. Simple use:

let leo = Symbol(a);typeof leo; // "symbol"
Copy the code

Symbol supports passing parameters as Symbol names for code debugging: **

let leo = Symbol("leo");
Copy the code

2. Precautions **

  • SymbolThe function doesn’t worknew, will report an error.

Since Symbol is a primitive type, not an object, no attributes can be added; it is a data type similar to a string.

let leo = new Symbol(a)// Uncaught TypeError: Symbol is not leo constructor
Copy the code
  • SymbolAre not equal,Even if the parameters are the same.
// No arguments
let leo1 = Symbol(a);let leo2 = Symbol(a); leo1 === leo2;// false 

/ / a parameter
let leo1 = Symbol('leo');
let leo2 = Symbol('leo');
leo1 === leo2; // false
Copy the code
  • SymbolCannot be calculated with another type of value, an error will be reported.
let leo = Symbol('hello');
leo + " world!";  / / an error
`${leo}world! `;  / / an error
Copy the code
  • SymbolCannot be automatically converted to a string, only explicitly converted.
let leo = Symbol('hello');
alert(leo); 
// Uncaught TypeError: Cannot convert a Symbol value to a string

String(leo);    // "Symbol(hello)"
leo.toString(); // "Symbol(hello)"
Copy the code
  • SymbolCan be converted to a Boolean value, but not to a numeric value:
let a1 = Symbol(a);Boolean(a1); ! a1;// false
Number(a1); // TypeError
a1 + 1 ;    // TypeError
Copy the code
  • SymbolAttribute non-participationfor... in/ofCycle.
let id = Symbol("id");
let user = {
  name: "Leo".age: 30,
  [id]: 123
};

for (let key in user) console.log(key); // name, age (no symbols)

// Use the Symbol task to access directly
console.log( "Direct: " + user[id] );
Copy the code

3. Use Symbol as the attribute name in the literal

When using Symbol as a property name in object literals, use square brackets ([]), such as [Leo]: “Leo”. Benefits: Prevents properties of the same name, and also prevents keys from being overwritten or overwritten.

let leo = Symbol(a);/ / write 1
let user = {};
user[leo] = 'leo';

/ / write 2
let user = {
    [leo] : 'leo'
} 

/ / writing 3
let user = {};
Object.defineProperty(user, leo, {value : 'leo' });

// The result is the same
user[leo]; // 'leo'
Copy the code

Note: When Symbol is an object property name, it cannot use the dot operator and must be enclosed in square brackets.

let leo = Symbol(a);let user = {};
// Can't use dot operation
user.leo = 'leo';
user[leo] ; // undefined
user['leo'];// 'leo'

// Must be in square brackets
let user = {
    [leo] : function (text){
        console.log(text);
    }
}
user[leo]('leo'); // 'leo'

// The above is equivalent to more concise
let user = {
    [leo](text){
        console.log(text); }}Copy the code

It is also often used to create a set of constants to ensure that all values are not equal:

let user = {};
user.list = {
    AAA: Symbol('Leo'),
    BBB: Symbol('Robin'),
    CCC: Symbol('Pingan')}Copy the code

4. Application: Eliminate magic strings

Magic string: a string or number that occurs repeatedly and is strongly coupled in code and should be avoided in favor of well-defined variables.

function fun(name){
    if(name == 'leo') {
        console.log('hello');
    }
}
fun('leo');   'hello' is the magic string
Copy the code

Often use variables to eliminate magic strings:

let obj = {
    name: 'leo'
};
function fun(name){
    if(name == obj.name){
        console.log('hello');
    }
}
fun(obj.name); // 'hello'
Copy the code

Use Symbol to eliminate strong coupling so that no relation-specific values are required:

let obj = {
    name: Symbol()
};
function fun (name){
    if(name == obj.name){
        console.log('hello');
    }
}
fun(obj.name); // 'hello'
Copy the code

5. Attribute name traversal

Symbol is traversed as the property name and does not appear in the for… In, for… Of circulation, nor would it be the Object. The keys (), Object, getOwnPropertyNames (), JSON. The stringify () returns.

let leo = Symbol('leo'), robin = Symbol('robin');
let user = {
    [leo]:'18', [robin]:'28'
}
for(let k of Object.values(user)){console.log(k)}
/ / no output

let user = {};
let leo = Symbol('leo');
Object.defineProperty(user, leo, {value: 'hi'});
for(let k in user){
    console.log(k); / / no output
}
Object.getOwnPropertyNames(user);   / / []
Object.getOwnPropertySymbols(user); // [Symbol(leo)]
Copy the code

Object. GetOwnPropertySymbols method returns an array containing all the current Object used as Symbol value of the property name.

let user = {};
let leo = Symbol('leo');
let pingan = Symbol('pingan');
user[leo] = 'hi leo';
user[pingan] = 'hi pingan';
let obj = Object.getOwnPropertySymbols(user);
obj; // [Symbol(leo), Symbol(pingan)]
Copy the code

You can also use the reflect. ownKeys method to return all types of key names, including regular and Symbol keys.

let user = {
    [Symbol('leo')]: 1.age : 2.address : 3,}Reflect.ownKeys(user); // ['age', 'address',Symbol('leo')]
Copy the code

Because the attribute with the Symbol value as the name is not traversed by the normal method, it is often used to define non-private, internally used methods of the object.

6. Symbol. For (), Symbol. KeyFor ()

For () with a 6.1 Symbol.

Use to reuse a Symbol value. Take a string as a parameter. If there is a Symbol value named by this parameter, return this Symbol. Otherwise create a new Symbol and return the value named by this parameter.

let leo = Symbol.for('leo');
let pingan = Symbol.for('leo');
leo === pingan;  // true
Copy the code

Difference between Symbol() and symbol.for () :

Symbol.for('leo') = = =Symbol.for('leo'); // true
Symbol('leo') = = =Symbol('leo');         // false
Copy the code

6.2 Symbol. KeyFor ()

Return a key of type Symbol used:

let leo = Symbol.for('leo');
Symbol.keyFor(leo);   // 'leo'

let leo = Symbol('leo');
Symbol.keyFor(leo);   // undefined
Copy the code

7. Built-in Symbol value

ES6 provides 11 built-in Symbol values that point to methods used within the language:

7.1 Symbol. HasInstance

This method is called when another object uses the instanceof operator to determine whether it is an instanceof the object. For example, foo instanceof foo is called foo [Symbol. HasInstance](foo) within the language.

class P {[Symbol.hasInstance](a){
        return a instanceof Array; }} [1.2.3] instanceof new P(); // true
Copy the code

P is a class, and new P() returns an instance whose Symbol. HasInstance method is automatically called during the instanceof operation to determine whether the operator on the left is an instanceof Array.

7.2 Symbol. IsConcatSpreadable

Value is a Boolean value indicating whether the object can be expanded when used with array.prototype.concat ().

let a = ['aa'.'bb'];
['cc'.'dd'].concat(a, 'ee'); 
// ['cc', 'dd', 'aa', 'bb', 'ee']
a[Symbol.isConcatSpreadable]; // undefined
let b = ['aa'.'bb']; 
b[Symbol.isConcatSpreadable] = false; 
['cc'.'dd'].concat(b, 'ee'); 
// ['cc', 'dd',[ 'aa', 'bb'], 'ee']
Copy the code

7.3 Symbol. Species

Refers to a constructor that is used when creating a derived object, which requires the get parser.

class P extends Array {
    static get [Symbol.species](){
        return this; }}Copy the code

Solve the following problems:

// Problem: b should be an instance of Array, but is actually an instance of P
class P extends Array{}
let a = new P(1.2.3);
let b = a.map(x= > x);
b instanceof Array; // true
b instanceof P; // true
// Fix: by using Symbol
class P extends Array {
  static get [Symbol.species]() { return Array; }}let a = new P();
let b = a.map(x= > x);
b instanceof P;     // false
b instanceof Array; // true
Copy the code

7.4 Symbol. The match

When str.match(myObject) is executed, it is called when the passed property exists and returns the return value of this method.

class P {[Symbol.match](string){
        return 'hello world'.indexOf(string); }}'h'.match(new P());   / / 0
Copy the code

7.5 Symbol. The replace

When this object is called by the string.prototype. replace method, the return value of the method is returned.

let a = {};
a[Symbol.replace] = (. s) = > console.log(s);
'Hello'.replace(a , 'World') // ["Hello", "World"]
Copy the code

7.6 Symbol. HasInstance

When this object is called by the string.prototype. search method, it returns the value of that method.

class P {
    constructor(val) {
        this.val = val;
    }
    [Symbol.search](s){
        return s.indexOf(this.val); }}'hileo'.search(new P('leo')); / / 2
Copy the code

7.7 Symbol. The split

When this object is called by the string.prototype. split method, the return value of this method is returned.

// Redefines the behavior of the split method on string objects
class P {
    constructor(val) {
        this.val = val;
    }
    [Symbol.split](s) {
        let i = s.indexOf(this.val);
        if(i == -1) return s;
        return [
            s.substr(0, i),
            s.substr(i + this.val.length)
        ]
    }
}
'helloworld'.split(new P('hello')); // ["hello", ""]
'helloworld'.split(new P('world')); // ["", "world"] 
'helloworld'.split(new P('leo'));   // "helloworld"
Copy the code

7.8 Symbol. The iterator

Object for… Of loop, the Symbol. Iterator method is called, returning the default iterator for the object.

class P {
    *[Symbol.interator]() {
        let i = 0;
        while(this[i] ! = =undefined ) {
            yield this[i]; ++i; }}}let a = new P();
a[0] = 1;
a[1] = 2;
for (let k of a){
    console.log(k);
}
Copy the code

7.9. The Symbol. ToPrimitive

This method is called when the object is converted to a value of the primitive type, returning the corresponding primitive type value for the object. When called, it needs to receive a string parameter indicating the current operation mode. The operation mode is:

  • Number: You need to convert it to a Number
  • String: you need to convert it to a String
  • Default: The value can be converted to a value or a string
let obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return 123;
      case 'string':
        return 'str';
      case 'default':
        return 'default';
      default:
        throw new Error(a); }}};2 * obj / / 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
Copy the code

7.10 Symbol. ToStringTag

Call on the Object Object. The prototype. The toString method, if the property exists, it returns a value will appear in the toString () method returns the string, the type of the Object. That is, this property can be used to customize the string after object in [object Object] or [object Array].

/ / a
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"
/ / two cases
class Collection {
  get [Symbol.toStringTag]() {
    return 'xxx'; }}let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"
Copy the code

7.11 Symbol. Unscopables

This object specifies which properties are excluded from the with environment when the with keyword is used.

// There are no unscopables
class MyClass {
  foo() { return 1; }}var foo = function () { return 2; };
with (MyClass.prototype) {
  foo(); / / 1
}
/ / unscopables
class MyClass {
  foo() { return 1; }
  get [Symbol.unscopables]() {
    return { foo: true}; }}var foo = function () { return 2; };
with (MyClass.prototype) {
  foo(); / / 2
}
Copy the code

By specifying the Symbol. Unscopables attribute, the with syntax block does not look for the foo attribute in the current scope. That is, foo will point to variables in the outer scope.

8. Raw value conversion

We have reviewed the conversion of strings, numbers, booleans, etc., but we have not covered the conversion rules of objects, so let’s take a look at this section: There are a few rules to remember:

  1. All objects are in the Boolean contexttrueAnd there is no conversion to a Boolean value, only string and numeric conversions.
  2. Numerical transformations occur when objects are subtracted or mathematical functions are applied. Such asDateObjects can be subtracted, such asdate1 - date2The result is the difference between the two times.
  3. In string conversions, usually occurs in such asalert(obj)In this form.

Of course we can use special object methods to fine-tune string and numeric conversions. Here are three types of hints:

1. object to string

Object to string conversion, when we perform an operation, such as “alert”, on an object that expects a string:

/ / output
alert(obj);
// Set the object as the property key
anotherObj[obj] = 123;
Copy the code

2. object to number

Object to number conversion, such as when we perform a mathematical operation:

// Explicit conversion
let num = Number(obj);
// Mathematical operations (except binary addition)
let n = +obj; // Unary addition
let delta = date1 - date2;
// Less than/greater than comparison
let greater = user1 > user2;
Copy the code

3. object to default

In rare cases, when the operator “does not determine” the expected value type. For example, binary addition + can be used for strings (concatenation) or numbers (addition), so both types work. Therefore, when a binary addition gets an argument of object type, it will convert it to “default”. In addition, if the object is being used for a == comparison with a string, number, or symbol, it is not clear which conversion should be performed, so “default” is used.

// Binary addition uses the default hint
let total = obj1 + obj2;
// obj == number
if (user == 1) {... };Copy the code

4. Type conversion algorithm

To make the transformation, JavaScript tries to find and call three object methods:

  1. callobj[Symbol.toPrimitive](hint)— with symbol keySymbol.toPrimitive(system symbol) method, if it exists,
  2. Otherwise, if hint is"string"– try toobj.toString()obj.valueOf(), whichever exists.
  3. Otherwise, if hint is"number""default"– try toobj.valueOf()obj.toString(), whichever exists.

5. Symbol.toPrimitive

Detailed introduction to reading the MDN | Symbol. ToPrimitive “. ToPrimitive is a built-in Symbol value that exists as a function value attribute of an object. This function is called when an object is converted to its original value. Brief examples:

let user = {
  name: "Leo".money: 9999[Symbol.toPrimitive](hint) {
    console.log(`hint: ${hint}`);
    return hint == "string" ? `{name: "The ${this.name}"} ` : this.money; }}; alert(user);// console: hint: string hint: {name: "John"}
alert(+user);    // console: hint: number 9999
alert(user + 1); // Console: hint: default hint: 10000
Copy the code

6. toString/valueOf

ToString/valueOf are two relatively early ways to implement the transformation. When there is no Symbol. ToPrimitive, JavaScript will try to find them, and it will try in the following order:

  • Hint for “string” hint,toString -> valueOf.
  • In other cases,valueOf -> toString.

Both methods must return a primitive value. If toString or valueOf returns an object, the return value is ignored. By default, normal objects have toString and valueOf methods:

  • toStringMethod returns a string"[object Object]".
  • valueOfMethod returns the object itself.

Brief examples:

const user = {name: "Leo"};

alert(user); // [object Object]
alert(user.valueOf() === user); // true
Copy the code

We can also combine toString/valueOf to implement the user object described in point 5:

let user = {
  name: "Leo".money: 9999./ / to hint = "string"
  toString() {
    return `{name: "The ${this.name}"} `;
  },

  Hint ="number" or "default"
  valueOf() {
    return this.money; }}; alert(user);// console: hint: string hint: {name: "John"}
alert(+user);    // console: hint: number 9999
alert(user + 1); // Console: hint: default hint: 10000
Copy the code

conclusion

This article as the “preliminary intermediate front end JavaScript self-test list” the second part, the content to introduce JavaScript objects, which let me see the knowledge, such as Symbol. ToPrimitive method. I also hope this list will help you test your JavaScript skills and catch up on the old and new.