preface

background

  • Quote question asked
    • 1 a meeting asked the question of reference type, students want to use lazy loading, want to fetch in the web page to save the data, if some parameters do not change, will not initiate the request, provided that the data returned by the FETCH will be modified, but also use the previous fetchdata
    if(window.fetchData&&window.fetchDataSomeKeyLength=== window.fetchData.length) return const data = fetch(`${url}` Window. FetchData = data window. FetchDataSomeKeyLength = data. SomeKey. Length processData (data) / / data. SomeKey length do the processingCopy the code

  • To get the point
    • The difference between js basic types and reference types
    • 2 The difference between stack storage and stockpiling
    • 3 js garbage collection mechanism
    • 4 Active object, execution context, this
    • 5. Closure formation
    • 6 Deep copy implementation

The difference between basic types and reference types

There are six basic types

  • string
  • number
  • bool
  • null
  • undefined
  • symbol

In plain English, the basic types of JS are used to store value, they are allocated to a limited size and their memory is allocated when the basic type variable is defined,

  • Numbers have a maximum and a minimum
  • Null undefined is a fixed value
  • The bool values are true and false

The four types, string, Number, Boolean, and symbol, are collectively called Primitive and represent basic types that cannot be subdivided any further; A new call to symbol is not allowed because the generated symbol is of a primitive type. Null and undefined are usually considered special values. The only value of these two types is the value itself.

Reference types

  • object

  • An array of

  • function

Distinguish it from the basic types. An object is logically an unordered or ordered collection of properties and a container for various values. Object values store reference addresses, so unlike basic type values, which are immutable, object values are mutable.

Packaging object

In fact, a reference to a String property or method is converted to an object by calling new String(). The object inherits the String method to handle the reference to the property. Once the reference ends, the temporary object is destroyed.

It’s not just strings that have the concept of wrapping objects, numbers and Boolean values also have corresponding new Number() and new Boolean() wrapping objects. Null and undefined have no wrapped objects, and accessing their properties will result in a type error.

Strings, numbers, and Boolean values are wrapped objects that are explicitly generated by constructors. Since they are objects, they must be distinct from values of basic types, which can be detected by Typeof.

typeof 'seymoe'                 // 'string'
typeof new String('seymoe')     // 'object'

Copy the code

Data type judgment

  • typeof

  • instanceof

  • Object.prototype.toString()

    typeof

    The ‘typeof’ operator determines which basic type a value belongs to. The return value is a string. Null is incorrectly considered to be a null pointer

    typeof 'seymoe' // 'string' typeof true // 'boolean' typeof 10 // 'number' typeof Symbol() // 'symbol' typeof null // Null typeof undefined // 'undefined'Copy the code

    If you use the typeof operator to determine the typeof an object and its subtypes, such as functions (callable objects), arrays (ordered indexed objects), and so on, you get the object result except for the function.

    typeof {} // 'object'
    typeof [] // 'object'
    typeof(() => {})// 'function'
    Copy the code

    Since there is no way to know whether a value is an array or a normal object, it is clear that typeof is not enough to determine the specific subtype of the object.

    instanceof

    The ‘instanceof’ operator can also be used to determine whether the constructor’s ‘prototype’ is present in the chain of the object being tested. “` [] instanceof Array // true ({}) instanceof Object // true (()=>{}) instanceof Function // true “`

    Note:instanceofIt’s not a panacea. The idea is to test the constructor

     var a={}
     a.__proto__=[]
     a instanceof Array //true
     a instanceof Object //true
    
    Copy the code
    Object.prototype.toString()

    ` ` Object. The prototype. The toString () can be said to be the judge JavaScript data types in the ultimate solution, specific usage, please see the following code:

    Object.prototype.toString.call({}) // '[object Object]' Object.prototype.toString.call([]) // '[object Array]' Object.prototype.toString.call(() => {}) // '[object Function]' Object.prototype.toString.call('seymoe') // '[object String]' Object.prototype.toString.call(1) // '[object Number]' Object.prototype.toString.call(true) // '[object Boolean]' Object.prototype.toString.call(Symbol()) // '[object Symbol]' Object.prototype.toString.call(null) // '[object  Null]' Object.prototype.toString.call(undefined) // '[object Undefined]' Object.prototype.toString.call(new Date()) // '[object Date]' Object.prototype.toString.call(Math) // '[object Math]' Object.prototype.toString.call(new Set()) // '[object Set]' Object.prototype.toString.call(new WeakSet()) // '[object WeakSet]' Object.prototype.toString.call(new Map()) // '[object Map]' Object.prototype.toString.call(new WeakMap()) // '[object WeakMap]'Copy the code

Data type conversion

ToPrimitive

When the ToPrimitive algorithm is executed, it is passed a hint parameter indicating what type of operation it is (also called the expected value of the operation), and according to this hint parameter, the algorithm determines the internal execution logic.

The hint parameter can be one of the following:

  • string
  • number
  • default
Transformation algorithm
  1. Call obj[symbol.toprimitive](hint)- the method with the Symbol key symbol.toprimitive (system Symbol), if such a method exists,

  2. Otherwise if it says “string”

    • tryobj.toString()andobj.valueOf()Whatever exists.
  3. Otherwise, if the prompt is “number” or “default”

    • tryobj.valueOf()andobj.toString()Whatever exists.
Determine the hint

We have mentioned the hint parameter used in the ToPrimitive algorithm. How do we determine what the hint value is in the context of an operation? —- create a new object and print the hint values for each operation:

let obj = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { console.log(`hint: ${hint}`); }}; alert(obj) // hint: string +obj // hint: number obj + 500 // hint: Var obj1 = {}; var obj1 = {}; var obj1 = {}; var obj1 = {}; console.log(+obj1); // NaN console.log(`${obj1}`); // "[object Object]" console.log(obj1 + ""); // "[object object]" // Declare an object, manually giving Symbol. ToPrimitive attribute, Var obj2 = {[Symbol. ToPrimitive](hint) {if (hint == "number") {return 10; } if (hint == "string") { return "hello"; } return true; }}; console.log(+obj2); // 10 -- hint at "number" console.log(' ${obj2} '); // "hello" -- hint parameter value is "string" console.log(obj2 + ""); // "true" -- hint is "default"Copy the code
ToPrimitive and toString/valueOf methods

But note the following two points:

  1. Symbol.toPrimitivetoStringThe return value of the method must be a value of the primitive type.
  2. valueOfMethods can return values of other types than the basic type.

When we create a normal Object({}, new Object(), etc.), there is no [Symbol. ToPrimitive] (method) attribute on the Object. Therefore, the operation of a common object to a value of a basic type generally follows the scenario:

  1. hintA value of"string"Is called firsttoString.toStringIf a value of a primitive type is returned, the operation is returned and terminated. Otherwise it will callvalueOfMethods.
  2. Otherwise, call firstvalueOf.valueOfIf a value of a primitive type is returned, the operation is returned and terminated. Otherwise it will calltoStringMethods.

2 The difference between stack storage and stockpiling

Stack data structure

A stack is a special kind of list. The elements in the stack can only be accessed through one end of the list, called the top of the stack. A stack is said to be a last-in-first-out (LIFO) data structure. Since the stack is last in, first out, any element that is not at the top of the stack is not accessible. To get at the bottom of the stack, you must first remove the top element.

Here, for the convenience of understanding, through the analogy of ping-pong box to analyze the stack access mode.

This ping-pong ball is stored in the same way as the stack access data. Table tennis 5, which is at the top of the box, must be put in last, but can be used first. And if we want to use table tennis 1 on the bottom, we have to take the top four table tennis balls out, so that table tennis 1 is on the top of the box. This is the stack space first in, last in, first out characteristics.

Heap data structure
The relationship between variable types and memory

Basic data types are stored in stack memory because they are small, fixed in size, accessed by value, and are frequently used data. To better understand the basic data type variables and stack memory, we use the following example and diagram to understand:

let num1 = 1; 
let num2 = 1;
Copy the code

Reference data types are stored in the heap memory because they take up a large space and are not fixed in size. If stored on the stack, the performance of the program will be affected. The reference data type stores a pointer on the stack 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

// Basic data type - stack memory let a1 = 0; // Basic datatype - stack memory let a2 = 'this is string'; // Basic datatype - stack memory let a3 = null; // let b = {m: 20}; // Let b = {m: 20}; Let c = [1, 2, 3]; // Let c = [1, 2, 3];Copy the code

So when we want to access a reference data type in the heap, we actually first get the address pointer to that object from the variable and then get the data we need from the heap.

Variable replication from a memory perspective
let a = 20; let b = a; b = 30; console.log(a); // The value of a is 50Copy the code

In this example, a and B are both basic types, and their values are stored in the stack memory. A and B each have their own stack space, so the value of a does not change after changing the value of B.

Replication of reference data types
let m = { a: 10, b: 20 }; let n = m; n.a = 15; Console.log (m.a) // What is the value of m.a, which is 10? Or 15?Copy the code

In this case, m, n is a reference type, stack memory storage address pointing to the object in the heap memory, the replication of a reference type as new variables automatically assigns a new value stored in the variable, but it’s just a reference type address pointer, actually refer to the same object, so after modifying n.a. value, The m.A. changed accordingly.

Advantages and disadvantages of stack memory and heap memory

In JS, basic data type variables are fixed in size and easy to manipulate, so they are stored on the stack. Reference type variables don’t have a fixed size, so allocate them to the heap and let them determine their size as they apply for space. Storing them separately minimizes the amount of memory your program needs to run.

Because of its characteristics, the stack memory system efficiency is higher. Heap memory needs to allocate space and addresses, and store addresses on the stack, so it is less efficient than the stack.

3 js garbage collection mechanism

Why do we have garbage collection

In C and C++ languages, if we want to open up a heap of memory, we need to calculate the size of the memory, and then manually allocate the memory through the malloc function, and always remember to use the free function to clean up after the use of memory, otherwise the memory will be permanently occupied, causing memory leak.

However, when writing JavaScript, we don’t have this process, because it is already encapsulated for us, V8 will automatically allocate memory based on the size of the object you are currently defining.

There is no need for us to manually manage the memory, so it is natural to have garbage collection. Otherwise, if we only allocate and do not recycle, it will not take long for the memory to be full, causing the application to crash.

The advantage of garbage collection is that we do not need to manage memory, more energy on the implementation of complex applications, but the disadvantage also comes from this, do not need to manage, may not pay attention when writing code, causing circular reference and other conditions, resulting in memory leaks.

Garbage collection mechanism
Mark clear

When a variable enters the environment (for example, when a variable is declared in a function), the variable is marked as “entering the environment.” Logically, the memory occupied by variables entering the environment can never be freed, because they are likely to be used as soon as the execution flow enters the appropriate environment. When a variable leaves the environment, it is marked “out of the environment.”

Variables can be marked in any way. For example, you can record when a variable enters the environment by flipping a particular bit, or you can use a list of variables “in the environment” and a list of variables “out of the environment” to track which variables have changed. It doesn’t matter how you mark your variables, it’s what you do.

  • (1) When the garbage collector is running, it marks all variables stored in memory (of course, it can use any marking method).
  • (2) It then unflags variables in the running environment and variables referenced by variables in the environment
  • (3) After that, variables that are still marked are considered to be ready to be deleted because they are no longer accessible in the runtime environment.
  • (4) Finally, the garbage collector completes the memory cleanup, destroying those marked values and reclaiming the memory space occupied by them.

Currently, the JavaScript implementations of Internet Explorer, Firefox, Opera, Chrome, and Safari all use a tag-clear garbage collection policy (or something similar), but at different intervals.

Reference counting

If the same value is assigned to another variable, the number of references to that value is increased by one. Conversely, if the variable containing the reference to the value changes the reference object, the number of references to the value is reduced by one.

When the number of references to the value goes to 0, it means that the value is no longer accessible, so the memory it occupied can be reclaimed.

This way, the next time the garbage collector runs, it frees the memory occupied by the values that are referenced zero times.

A circular reference is when object A contains A pointer to object B, and object B contains A reference to object A. Here's an example:Copy the code
function foo () {
    var objA = new Object();
    var objB = new Object();
    
    objA.otherObj = objB;
    objB.anotherObj = objA;
}

Copy the code

In this example, objA and objB refer to each other by their properties, that is, they are both referenced 2 times.

In a mark-clear implementation, since both objects are out of scope after the function is executed, this mutual reference is not an issue.

In an implementation with the count of references policy, however, objA and objB will continue to exist after the function is executed because their count will never be zero. Adding that this function is called multiple times can cause a lot of memory not to be reclaimed

Also note that most of us write code for circular references all the time. Take a look at this example, which I’m sure you’ve done:

var el = document.getElementById('#el');
el.onclick = function (event) {
    console.log('element was clicked');
}

Copy the code

We bind an anonymous function to the click event of an element. We use the event parameter to get information about the element el.

Is this a circular reference? El has a property onclick that refers to a function (which is actually an object), and the arguments in the function refer to el, so that el is always referenced by 2 times, so that even if the page is currently closed, it is not garbage collected.

If you do this too many times, you can cause a memory leak. We can do this by clearing the event reference when the page is uninstalled so that it can be reclaimed

var el = document.getElementById('#el'); el.onclick = function (event) { console.log('element was clicked'); } / /... / /... Window. onbeforeUnload = function(){el.onclick = null; }Copy the code
V8 garbage collection policy

Therefore, V8 adopts a generation recycling strategy, dividing memory into two generations: New Generation and old generation.

New generation of object for shorter survival time of the object, the old generation of objects in the live for a long time or for permanent memory object, adopts different recycling of old new generation algorithm to improve efficiency, and the object at the beginning will be assigned to a new generation (if the new generation of memory space is not enough, direct distribution to the old generation), after the object in the new generation will satisfy certain conditions, Being moved to an older generation, a process also known as promotion, which I’ll explain in more detail later.

Generational memory

By default, the new generation memory is 16MB on a 32-bit system and the old generation memory is 700MB. In a 64-bit system, the new generation memory is 32MB and the old generation memory is 1.4GB.

The new generation is divided into two equal chunks of memory, called semispace, each of 8MB (32-bit) or 16MB (64-bit) size.

allocation

The new generation of objects are short-lived, and memory allocation is easy. Only a pointer to the memory space is saved, and the pointer is increased according to the size of the allocated object. When the storage space is nearly full, a garbage collection is performed.

algorithm

Scavenge is used in the new generation. The application of the Scavenge garbage collection algorithm is mainly based on the Cheney algorithm. The Cheney algorithm splits memory in two, called Semispace, with one in use and one idle.

The semispace that is in use is called the From space, and the semispace that is idle is called the To space.

Next, I’ll elaborate on how The Cheney algorithm works, in conjunction with a flow chart. Garbage Collection is referred to as Garbage Collection. Step1. Three objects A, B, C are allocated in the From space

Step2. GC comes in and determines that object B has no other reference and can be reclaimed. Objects A and C are still active objects

Step3. Copy active objects A and C From the From space To the To space

Step4. Clear all the memory in the From space

Step5. Swap From space and To space

Step6. Add two objects D and E in the From space

Step7. The next GC comes in and finds that there is no reference to object D, mark it

Step8. Copy active objects A, C, and E From the From space To the To space

Step9. Clear all the memory in the From space

Step10. Continue swapping From space and To space To start the next round

From the flowchart above, it is clear that the exchange between From and To is intended To keep the active objects in one semispace while the other semispace remains idle.

Scavenge is excellent in terms of time efficiency because it only replicates surviving objects, and takes a small proportion of surviving objects in scenarios with short life cycle. Scavenge has the disadvantage of using only half of the heap memory, due to the partitioning and replication mechanism.

Scavenge is a typical spatial-for-time algorithm, so it cannot be applied to all garbage collections on a large scale. Be insane. However, we can see that Scavenge is very suitable for application in the Cenozoic era, because the life cycle of the object in the Cenozoic is short, it is exactly suitable for this algorithm.

promotion

The process of an object moving from the new generation to the old generation is called promotion.

There are two main conditions for an object to be promoted:

  1. When an object is copied From the From space To the To space, it checks its memory address To determine if the object has undergone a Scavenge. If already experienced, the object is moved From the From space To the old generation space, or copied To the To space if not. In summary, if an object is copied From the From space To the To space for the second time, then the object is moved To the old generation.
  2. When an object is copied From the From space To the To space, if the To space has been used by more than 25%, the object is directly promoted To the old generation. Be insane. The reason for setting the 25% threshold is that when the Scavenge is completed, the To space will be changed To the From space, and the next memory allocation will be made in this space. If the proportion is too high, subsequent memory allocation will be affected
The old generation
  1. Because there are many living objects, the efficiency of copying living objects is very low.
  2. Use Scavenge algorithm will waste half of the memory, because the old generation heap memory is much greater than the new generation, so the waste can be serious.

Therefore, V8 mainly used mark-sweep and Mark-sweep combined method for garbage collection in the old generation.

Mark-Sweep

Unlike Scavenge, Mark-Sweep does not split memory in two, so there is no waste of half the space. Mark-sweep traverses all objects in the heap memory in the marking phase and marks the living objects, and in the subsequent cleaning phase only unmarked objects are cleared.

Scavenge copies only living objects, while Mark-Sweep cleans only dead objects. The number of living objects in the new generation is small, and the number of dead objects in the old generation is small. This is why both recycling methods are efficient.

step1There are objects A, B, C, D, E, F in the old generation

Step2. GC enters the marking stage and marks A, C, and E as the live objects

step3. GC enters the cleanup phase and reclaims the memory space occupied by dead OBJECTS B, D, and F

As you can see, the biggest problem with Mark-Sweep is that the memory space is discontinuous after a clean collection. This fragmentation can cause problems with subsequent memory allocation.

If there is a situation where a large amount of memory needs to be allocated, garbage collection is triggered early because the remaining debris space is not sufficient to complete the allocation, and the collection is not necessary.

Mark-Compact

** Mark-compact means Mark Sweep, ** is an evolution of Mark-sweep. Mark-compact moves the living object to one end of the memory space after marking it. When the move is complete, all memory outside the boundary is cleaned up. Step1. There are objects A, B, C, D, E, F in the old generation (same as Mark — Sweep).

step2. GC enters the marking phase and marks A, C and E as live objects (same as Mark — Sweep).

step3. GC enters the collation stage and moves all the living objects to one side of the memory space. The gray part is the space left after moving

step4. GC enters the cleanup phase and reclaims all the memory on the other side of the border at one time

A combination of these

In V8’s recycling strategy, Mark-Sweep and Mark-Conpact are used in combination.

Since Mark-Conpact requires objects to be moved, it is unlikely to perform very fast. For trade-off, V8 uses mark-Sweep mainly, and mark-Compact only when there is not enough space to allocate objects promoted from the new generation.

conclusion

V8’s garbage collection mechanism is divided into the new generation and the old generation.

The cenogeneration is mainly used To Scavenge. The primary implementation is The Cheney algorithm, which divides the memory evenly into two pieces, the use space is called From, and the idle space is called To. New objects are allocated To the From space first. Swap the From and To Spaces and continue allocating memory. When those two conditions are met, the object is promoted From the new generation To the old generation.

The old generation mainly adopts mark-Sweep and Mark-compact algorithms, one is Mark Sweep, the other is Mark cleaning. The difference between Mark-Sweep and Mark-Compact is that mark-Sweep generates fragmented memory after garbage collection, while Mark-Compact cleans up memory by moving the living objects to one side and then emptying the memory on the other side of the boundary. The free memory is continuous, but the problem is that it is slower. In V8, old age is managed by both Mark-Sweep and Mark-Compact.

That’s all for this article, which was written with reference to a number of Chinese and foreign articles, including NodeJS in Simple Terms by Park Dada and JavaScript Advanced Programming. We have not discussed the specific algorithm implementation here, interested friends can continue to study.

Finally, thank you for reading this, and if there is any ambiguity or error in this article, please leave me a comment ~~

Execution environment, execution context, active object, this

The execution environment

The variable object

Global execution environment

The scope chain

Identifier resolution (variable lookup) is a level – level operation in the scope chain, starting from the current variable object until it is found. If it is not found, there will usually be an exception

var color = "blue"; function changeColor() { var otherColor = "red"; function swapColor() { var tempColor = otherColor; otherColor = color; color = tempColor; // Here you can access tempColor otherColor color} swapColor(); // Here you can access otherColor color swapColor} changeColor(); // Here you can access the changeColor colorCopy the code

Look at the picture

What is this

  • 1 This refers to the current function itself
  • 2 this refers to the scope of the current function

This refers to the current function itself

In the following code you need to understand the multifaceted, multiple identities of functions

  • Ordinary function
  • Ordinary object
  • The constructor

So the next thing we’re going to do is we’re going to use two identities: normal function, normal object, look at the code ()

function foo(){ this.count++ } var count=0; foo.count=0; for(var i=0; i<5; i++){ foo() } console.log(foo.count)//0 console.log(count)//5Copy the code

It’s obvious from the print that this is not referring to the function itself, and of course when we see problems like this we usually go around and look at the code

function foo(){ this.count++ } var bar={ count:0 } foo.count=0; for(var i=0; i<5; i++){ foo.call(bar) } console.log(bar.count)//5 console.log(count)//0Copy the code

Although this solution is good, and there will be other solutions, we still don’t understand the problem of this, and we still feel uneasy

This refers to the scope of the current function

So the next thing we’re going to do is we’re going to use two identities: normal function, normal object, look at the code ()

 function foo(){
     var num=2;
     console.log(this.num)
 }
 var num=0;
 foo()//0

Copy the code

When we look at the result of the code execution, we see that this does not refer to the scope of the function.

What exactly is this

In the figure we see that this is created when the function is executed.


This


In the previous steps we have identified the errors in creating this and pointing to this. Now we will look at the rules for binding this, which are divided into four rules.

  • The default binding
  • Implicit binding (context binding)
  • Explicitly bound
  • The new binding


The default binding


 function foo(){
     var num=2;
     this.num++
     console.log(this.num)
 }
 var num=0;
 foo()//1
Copy the code

The default binding is implemented in the above code, which operates window.num++ in the foo method block.


Implicit binding (context binding)




function foo(){
    console.log(this.name)
}
var bar={
    name:'shiny',
    foo:foo
}
bar.foo()//shiny
Copy the code

To add, no matter how deep your object is nested, this will only bind to objects that refer directly to the address property of the function. See the code

function foo(){
    console.log(this.name)
}
var shiny={
    name:'shiny',
    foo:foo
}
var red={
    name:'red',
    obj:shiny
    
}
red.obj.foo()//shiny
Copy the code


Loss of implicit binding


function foo(){
    console.log(this.name)
}
var shiny={
    name:'shiny',
    foo:foo
}
function doFoo(fn){
    fn()
}
doFoo(shiny.foo)//undefind
Copy the code

Known function parameters in function, actually have an assignment operation, let me explain the above, when function doFoo execution will create a new and pushed into the global stack, in the process of execution object will create an activity, the activity object will be assigned the incoming parameters and variables defined in the function function, Variables and functions used during function execution are taken directly from the live object. See the doFoo execution stack

Fn execution stack

So let’s see how this works the same way that we did up here, we lost the implicit binding by assigning values. Look at the code

function foo(){
    console.log(this.name)
}
var shiny={
    name:'shiny',
    foo:foo
}
var bar = shiny.foo
bar()//undefined
Copy the code

So what shiny properties of foo are doing, and what shiny properties of foo are doing is referring to the memory address of the function foo, so if you assign the address of foo to bar, then now bar’s reference address and shiny. Foo’s reference address is, When bar is executed, the default binding rule will also be triggered because there are no other rules to match. When bar is executed, this inside the function is bound to the global variable.

See if the full reference address assignment is present, the exotic implicit binding is missing, see the code

function foo(){
    console.log(this.name)
}
var shiny={
    name:'shiny',
    foo:foo
}
var red={
    name:'red'
}
(red.foo=shiny.foo)()//undefined
Copy the code

The return value of the assignment expression p.foo = o.foo is a reference to the target function, so the call location is foo() instead of p.foo() or o.foo(). As we said before, default bindings will apply here.


Explicitly bound


Call and apply bindings


Look at the code

function foo(){ console.log(this.age) } var shiny={ age:20 } foo.call(shiny)//20 function bar(){ console.log(this.age) }  var red={ age:18 } bar.apply(red)//18Copy the code

Both methods are explicitly tiHS bound

Hard binding:


function foo(b){
  return this.a+b
}
var obj={
  a:2
}
function bind(fn,obj){
  return function(){
     return fn.apply(obj,arguments)
  }
}
bind(foo,obj)(3)//5
Copy the code

The bind method is implemented by the apply + closure mechanism to enforce binding rules

The “context” of the API call to a third-party library or parasite environment, as well as some built-in js methods, provide the content context argument, which, like bind, ensures that the this callback function is bound

function foo (el){ console.log(el,this.id) } var obj ={ id:'some one' }; [1,2,4]. ForEach (foo,obj) // 1 some one 2 some one 4 some oneCopy the code


The new binding


  • Constructors in traditional class-oriented languages call special methods (constructors) of the class when the class is instantiated using the new operator

  • Many people think that the new operator in JS is the same as the constructor in traditional class-oriented languages, but it is quite different

  • When a constructor in JS is called by the new operator, this constructor does not belong to every class and does not create a class. It is just a function that is called by the new operator.

  • When the constructor is called using the new operator, four steps are performed

    • Create an entirely new object
    • Change the address of the __proto__ property of the new object to the reference address of the constructor’s prototype
    • The this constructor is bound to this brand new object
    • If the constructor has a return value and the return value is an object, return that object, otherwise return the current new object

Now that we know what the js new operator does when it calls the constructor, we know who this is in the constructor

Code implementation

function Foo(a){
  this.a=a
}
var F = new Foo(2)
console.log(F.a)//2
Copy the code


The order of binding rules




The default binding


Explicit versus implicit binding


Look at the code

function foo(){
    console.log(this.name)
}
var  shiny={
    name:'shiny',
    foo:foo
}
var red={
    name:'red'
}

shiny.foo()//shiny
shiny.foo.call(red)// red
shiny.foo.apply(red)// red
shiny.foo.bind(red)()//red
Copy the code

Clearly, explicit binding won the implicit binding in the binding this contest


Implicit binding VS New operator binding


function  foo(name){
    this.name=name
}
var shiny={
    foo:foo
}
shiny.foo('shiny')
console.log(shiny.name)//shiny

var red = new shiny.foo('red')
console.log(red.name)//red
Copy the code

Obviously the new operator binding won the implicit binding in the binding this contest


Explicit binding (hard binding) VS new operator binding


When you use the call and apply methods in combination with the new operator, an error is reported

But we can compare the priority of the explicit binding with the this binding of the new operator by using bind this. Look at the code

function foo(){
    console.log(this.name)
}
var shiny={
    name:'shiny'
}

var bar = foo.bind(shiny)
var obj = new bar();
console.log(obj.name)// undefind
Copy the code

Obviously, the new operator binding trumps the explicit binding


This judgment


  • Var f = new foo() var f = new foo()
  • Call, apply, bind (); if yes, this is the first argument to the call, apply, or bind function

foo.call(window)

  • This function has a context (implicit binding) and is triggered by a reference to the object’s property. This function’s “this” is the address of the current object.

obj.foo();

  • 4 If none of the above is available, this is the default binding, and this is either global or undefined (in strict mode).


Bind the exception


function foo(){
    console.log(name)
}
var name ='shiny'
foo.call(null)//shiny
foo.call(undefined)//shiny
var bar = foo.bind(null)
var baz = foo.bind(undefined)
bar()//siny
baz()//siny
Copy the code

If null and undefined are explicitly bound via apply, call, and bind, it is recommended that they be bound by default. However, it is recommended that they be bound to global objects in non-strict mode, which sometimes causes unnecessary bugs.


More secure this


function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // Our empty Object var ø = object.create (null); // Expand the array to the argument foo.apply(ø, [2, 3]); // a:2, b:3 // Use bind(..) Var bar = foo.bind(ø, 2); bar( 3 ); // a:2, b:3Copy the code


Es6 in this


 function foo(){
     return ()=>{
          console.log(this.name)
     }
 }
 var obj ={
     name:'obj'
 }
  var shiny ={
     name:'shiny'
 }
 var bar = foo.call(obj);
 bar.call(shiny)// foo
 

Copy the code

We see that the this of the arrow function is bound to the scope in which the function executes.

Let’s look inside js to provide built-in functions using the arrow function

Function foo() {setTimeout() => {// this inherits from foo() console.log(this.a); }, 100); } var obj = { a:2 }; foo.call( obj ); / / 2Copy the code

Arrow functions can be used like bind(..) It also ensures that the function’s this is bound to the specified object, and is important now that it replaces the traditional this mechanism with a more common lexical scope. In fact, we were already using a pattern almost identical to the arrow function before ES6.

function foo() {
var self = this; // lexical capture of this
setTimeout( function(){
    console.log( self.a );
    }, 100 );
}
var obj = {
    a: 2
};
foo.call( obj ); // 2
Copy the code

Although both self = this and the arrow function seem to replace bind(..) But essentially, they want to replace the this mechanism. If you’re writing a lot of this style code, but most of the time you’re going to use self = this or the arrow function. If you use the this style entirely, use bind(..) if necessary. , try to avoid self = this and arrow functions.

5. Closure formation

closure

During function execution, variables need to be looked up in the scope chain in order to read and write their values. Let’s look at the following example.

function compare(value1, value2) {
if (value1 < value2) {
 return -1;
} else if (value1 > value2) {
 return 1;
} else {
 return 0;
}
}
var result = compare(5, 10);
Copy the code

The above code defines the compare() function and then calls it at the global scope. When compare() is called, an active object containing Arguments, value1, and value2 is created. The variable objects of the global execution environment (including result and compare) are second in the scope chain of the compare() execution environment. The image shows the chain of scopes in which the compare() function containing the above relationship is executed.

Each execution environment in the background has an object that represents a variable — a variable object. The global environment variable object always exists, while the local environment variable object, such as the compare() function, exists only for the duration of the function’s execution. When compare() is created, a Scope chain is created that precontains the global variable object, which is stored in the internal [[Scope]] property. When compare() is called, an execution environment is created for the function, which is then scoped by copying the objects in the function’s [[Scope]] property. After that, another live object (used here as a variable object) is created and pushed to the front of the execution environment scope chain. For the context in which compare() is executed in this example, the scope chain contains two variables: a local live object and a global variable object. Obviously, a scope chain is essentially a list of Pointers to a variable object that only references but does not actually contain the variable object. Whenever a variable is accessed in a function, the scope chain is searched for the variable with the corresponding name. In general, when the function is executed, the local active object is destroyed, and only the global scope (the variable object of the global execution environment) is kept in memory. Closures, however, are different.

I’m looking at a case

function createComparisonFunction(propertyName) { return function (object1, object2) { var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; }}; }Copy the code

In this example, the object1[propertyName] object2[propertyName] lines are code in the inner function (an anonymous function) that accesses the variable propertyName in the outer function. Even if this inner function is returned and called elsewhere, it still has access to the propertyName variable. This variable is still accessible because the scope of createComparisonFunction() is contained in the scope chain of the inner function. To fully understand the details, you must start by understanding what happens when a function is called.

When a function is called, an execution context and the corresponding scope chain are created. The activation Object of the function is then initialized using arguments and the values of other named parameters. But in the scope chain, the active object of the external function is always second, and the active object of the external function is third,…… The global execution environment up to the end of the scope chain

Look at the picture

Closures and variables

function createFunctions() {
  var result = new Array();
  for (var i = 0; i < 10; i++) {
    result[i] = function () {
      return i;
    };
  }
  return result;
}

Copy the code

This function returns an array of functions. On the surface, it would seem that each function should return its own index, i.e. a function at position 0 returns 0, a function at position 1 returns 1, and so on. But in fact, every function returns 10. Because each function holds the active object of the createFunctions() function in the scope chain, they all refer to the same variable I. When createFunctions() returns, variable I has a value of 10. Each function refers to the same variable object that holds variable I, so I has a value of 10 inside each function. However, we can force the closure to behave as expected by creating another anonymous function, as shown below.

function createFunctions() {
  var result = new Array();
  for (var i = 0; i < 10; i++) {
    result[i] = (function (num) {
      return function () {
        return num;
      };
    })(i);
  }
  return result;
}
Copy the code

After overwriting the previous createFunctions() function, each function returns a different index. In this version, instead of assigning the closure directly to the array, we define an anonymous function and assign the result of the immediate execution of that anonymous function to the array. The anonymous function here takes one argument, num, which is the value that the final function will return. When we call each anonymous function, we pass in the variable I. Since function arguments are passed by value, the current value of the variable I is copied to the argument num. Inside this anonymous function, a closure that accesses num is created and returned. This way, each function in the result array has its own copy of the num variable, so it can return a different number.

About this

var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { return function () { return this.name; }; }}; alert(object.getNameFunc()()); //"The Window"Copy the code

Each function automatically takes two special variables when it is called: this and arguments. The internal function searches for the two variables only as far as their live object, so it is never possible to directly access the two variables in the external function. However, by storing the this object in the outer scope in a variable that the closure can access, the closure can access the object.

var name = "The Window"; var object = { name: "My Object", getNameFunc: function () { var that = this; return function () { return that.name; }; }}; alert(object.getNameFunc()()); //"My Object"Copy the code

In several special cases, the value of this can change unexpectedly. For example, the following code is the result of modifying the previous example.

var name = "The Window"; var object = { name: "My Object", getName: function () { return this.name; }};Copy the code

The first line of code calls object.getName() as usual, returning “My object “because this.name is object.name. The second line of code parentheses the method before calling it. Even though the parentheses make it look like you’re just referring to a function, the value of this is maintained because the definitions of object.getName and (object.getName) are the same. The third line executes an assignment statement and then calls the result of the assignment. Since The value of The assignment expression is The function itself, The value of this cannot be maintained, and The result is “The Window”. Of course, you are unlikely to call this method as you would in lines 2 and 3. However, this example helps to show that even slight changes in syntax can accidentally change the value of this.

6 Deep copy implementation

Definition of deep copy and shallow copy

Shallow copy:

Creates a new object that has an exact copy of the property values of the original object. If the property is of a primitive type, the value of the primitive type is copied, and if the property is of a reference type, the memory address is copied, so if one object changes the address, the other object is affected.

Deep copy:

A complete copy of an object from memory, from the heap memory to open a new area to store the new object, and the new object changes will not affect the original object

The beggar version

JSON.parse(JSON.stringify());
Copy the code

This is very simple and works well for most application scenarios, but it has some major drawbacks, such as copying other reference types, copying functions, circular references, and so on.

Basic version

function clone(target) { if (typeof target === 'object') { let cloneTarget = {}; for (const key in target) { cloneTarget[key] = clone(target[key]); } return cloneTarget; } else { return target; }};Copy the code

This is a deep copy of the most basic version. This code will allow you to show the interviewer that you can solve a problem with recursion, but obviously it has a lot of drawbacks, such as not considering arrays.

Consider the array

In the previous version, we only initialized normal objects. Now we only need to change the initialization code to be compatible with arrays:

module.exports = function clone(target) { if (typeof target === 'object') { let cloneTarget = Array.isArray(target) ? [] : {}; for (const key in target) { cloneTarget[key] = clone(target[key]); } return cloneTarget; } else { return target; }}; const target = { field1: 1, field2: undefined, field3: { child: 'child' }, field4: [2, 4, 8] };Copy the code

OK, no problem, your code is one small step closer to qualifying.

A circular reference

const target = {
   field1: 1,
   field2: undefined,
   field3: {
       child: 'child'
   },
   field4: [2, 4, 8]
};
target.target = target;
Copy the code

Obviously, the stack is out of memory because of recursion into an infinite loop.

The reason for this is that the above objects have circular references, that is, objects whose properties indirectly or directly refer to themselves:

Solve the problem of circular reference, we can create a extra storage space, to store the current objects and copy the object correspondence, when need to copy the current object, go to the storage space, find ever copy this object, if any direct return, if not to copy, so clever resolve problem of circular references.

This storage space needs to be able to store data in the form of key-value, and the key can be a reference type. We can choose the data structure Map:

  • checkmapIs there any cloned object in the
  • Yes. – Straight back
  • None – treats the current object askey, clone objects asvalueFor storage
  • Continue to clone
function clone(target, map = new Map()) { if (typeof target === 'object') { let cloneTarget = Array.isArray(target) ? [] : {}; if (map.get(target)) { return map.get(target); } map.set(target, cloneTarget); for (const key in target) { cloneTarget[key] = clone(target[key], map); } return cloneTarget; } else { return target; }};Copy the code

Next, we can use WeakMap to make the code achieve the finishing point.

function clone(target, map = new WeakMap()) {
    // ...
};

Copy the code

Why do we do that? Let’s look at the function of WeakMap first:

A WeakMap object is a set of key/value pairs where the keys are weakly referenced. The key must be an object, and the value can be arbitrary.

What is a weak reference?

In computer programming, a weak reference, as opposed to a strong reference, is a reference that does not guarantee that the object it references will not be reclaimed by the garbage collector. An object that is referenced only by weak references is considered inaccessible (or weakly accessible) and may therefore be reclaimed at any time.

Here’s an example:

let obj = { name : 'ConardLi'} const target = new Map(); Target. set(obj,'code secret garden '); obj = null;Copy the code

Although we manually release OBj, the target still has a strong reference to OBj, so this part of memory still cannot be freed.

Let’s look at WeakMap:

let obj = { name : 'ConardLi'} const target = new WeakMap(); Target. set(obj,'code secret garden '); obj = null;Copy the code

If it is a WeakMap, the target and OBj have a weak reference relationship. When the next garbage collection mechanism is executed, the memory will be freed.

Imagine that if the object we want to copy is very large, the use of Map will cause very large extra consumption on memory, and we need to manually clear the attributes of the Map to release the memory, and WeakMap will help us cleverly resolve this problem.

  • Front-end advanced stack memory heap memory in JS stack memory
  • Talk about V8 garbage collection
  • How to write a deep copy of an amazing interviewer?