JavaScript based

1.1 Execution context/scope chain/closure

1.1.1 Understand execution context and execution stack in JavaScript
What is an execution context?Copy the code

In short, an execution context is an abstraction of the context in which JavaScript code is evaluated and executed. Whenever JavaScript code is running, it is in an execution context.

The type of execution contextCopy the code

There are three types of execution context in JavaScript

  • Global execution context – This is the default or base context, and any code that is not inside a function is in the global context. It does two things: create a global Window object (in the case of the browser) and set this to equal the global object. There is only one global execution context in a program.
  • Function execution Context – Each time a function is called, a new context is created for that function. Each function has its own execution context, but is created when the function is called. There can be any number of function contexts. Whenever a new execution context is created, it performs a series of steps in a defined order.
  • Eval function execution context – Code executed inside the Eval function also has its own execution context, but Eval is not often used by JavaScript developers.
Execution stack
What is an execution stack?Copy the code

An execution stack, also known as a “call stack” in other programming languages, is a stack of LIFO (LIFO) data structures that are used to store all execution contexts created while the code is running.

When the JavaScript engine first encounters your script, it creates a global execution context and pushes it onto the current execution stack. Every time the engine encounters a function call, it creates a new execution context for that function and pushes it to the top of the stack.

The engine executes functions whose execution context is at the top of the stack. When the function completes execution, the execution context pops out of the stack and the control flow moves to the next context in the current stack.

For example:

let a = 'Hello World! ';

function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}

function second() {
  console.log('Inside second function');
}

first();
console.log('Inside Global Execution Context');
Copy the code

The execution context stack of the above code

When the above code loads in the browser, the JavaScript engine creates a global execution context and pushes it onto the current execution stack. When the first() function call is encountered, the JavaScript engine creates a new execution context for the function and pushes it to the top of the current execution stack.

When second() is called from within the first() function, the JavaScript engine creates a new execution context for the second() function and pushes it to the top of the current execution stack. When second() completes, its execution context is popped off the current stack and the control flow moves to the next execution context, that of the first() function.

When first() completes, its execution context pops off the stack and the control flow reaches the global execution context. Once all code has been executed, the JavaScript engine removes the global execution context from the current stack.

How do I create an execution context?

There are two phases to creating an execution context: 1) the creation phase and 2) the execution phase

The Creation Phase

Before the JavaScript code executes, the execution context goes through the creation phase. Three things happen during the creation phase:

  1. The determination of the this value, known as the This binding.
  2. Create the lexical environment component
  3. Create the variable environment component.

So the execution context is conceptually expressed as follows:

ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }},Copy the code

This binding: In the context of global execution, the value of This points to the global object. (In browsers, this refers to the Window object.)

In the context of function execution, the value of this depends on how the function is called. If it is called by a reference object, this will be set to that object, otherwise this will be set to global or undefined.

let foo = { baz: function() { console.log(this); } } foo.baz(); // 'this' refers to 'foo' because 'baz' is called by // object 'foo' let bar = foo.baz; bar(); // 'this' points to the global window object because // no reference object is specifiedCopy the code

Lexical environment

Simply put, a lexical environment is a structure that holds identity-variable mappings. (The identifier here refers to the name of the variable/function, and the variable is a reference to the actual object [containing the object of the function type] or the original data).

Now, inside the lexical environment there are two components :(1) the environment logger and (2) a reference to the external environment.

  1. The environment logger is the actual location where variable and function declarations are stored.
  2. A reference to an external environment means that it has access to its parent lexical environment (scope).

There are two types of lexical environments:

  • Global environment in global execution context) is a lexical environment that has no references to the external environment. The external reference to the global environment is NULL. It has built-in Object/Array/ and so on, prototype functions in the environment logger (associated with global objects, such as the window Object) and any user-defined global variables, and the value of this refers to the global Object.
  • In a function environment, user-defined variables within a function are stored in the environment logger. And the referenced external environment may be the global environment, or any external function that contains this internal function.

The variable environment

It is also a lexical environment whose environment logger holds bindings created in the execution context of variable declaration statements.

As mentioned above, the variable environment is also a lexical environment, so it has all the attributes of the lexical environment defined above.

In ES6, one difference between a lexical environment component and a variable environment is that the former is used to store function declarations and variable (let and const) bindings, while the latter is used only to store var variable bindings.

Let’s look at sample code to understand the above concepts:

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
 var g = 20;
 return e * f * g;
}

c = multiply(20.30);
Copy the code

The execution context looks like this:

GlobalExectionContext = {

  ThisBinding: <Global Object>, LexicalEnvironment: {EnvironmentRecord: {Type: "Object", < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: } outer: <null>}} FunctionExectionContext = {ThisBinding: <Global Object>, LexicalEnvironment: {EnvironmentRecord: {Type: "Declarative", // 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: G: undefined, outer: <GlobalLexicalEnvironment>}}Copy the code

Note – The function execution context is created only if the multiply function is called.

You may have noticed that the let and const variables are not associated with any value, but the var variable is set to undefined.

This is because during the creation phase, the engine examines the code for variable and function declarations, which are stored entirely in the environment but are initially set to undefined (in the case of var) or uninitialized (in the case of let and const).

This is why you can access variables defined by var (albeit undefined) before declaration, but access let and const variables before declaration will get a reference error.

This is what we call variable declaration enhancement.

Execution phase

This is the easiest part of the whole article. At this stage, all of these variables are allocated and the code is executed.

Note – at execution time, if the JavaScript engine cannot find the value of the let variable at the actual location declared in the source code, it will be assigned the value of undefined.

One last thing to remember, especially important:
Lexical scope is defined when an internal function determines its outer scope. This is especially important when it comes to closures.Copy the code

1.2 this/call/apply/bind

To determine a function’s this binding, we need to find where the function is called directly. Then we can determine this binding objects by following four rules:

  1. Called by new: binds to the newly created object
  2. Called by call or apply or bind: binds to the specified object
  3. Called by context object: Binds to context object
  4. Default: global object

Note: The arrow function does not use the above binding rules and determines this based on the scope of the outer layer, inherits the this binding of the outer function call.

This refers to three cases:

  1. Obj.fun () this->obj in fun, automatically pointing to. In front of the object

  2. New Fun() This -> is the new object being created in Fun. New changes the this pointer inside the function, causing this to point to the object instantiating new

  3. Fun () and anonymous functions call this by default ->window. This inside the function refers to window by default

  4. Normal function calls: bind to undefined in strict mode, otherwise bind to global objects.

Js does not bind this or use the arrow function, so the callback defaults to the global window object.

Summary of articles on nuggets:

Note: a basis for judging js code execution, remember

  1. When a variable is accessed from a function scope, it looks up from where the function was declared and then to the upper scope, not from where it was called

2. The this pointer is determined at execution time, not at definition. The arrow function this points to the value of this in the function to which its closest binding this points

3. Criteria for this in non-arrow functions: This always refers to the execution context of the current function

Implement the Call method by hand

Function.prototype.call2 = function(context) {
    var context = context || window;
    // Get the function that calls call
    context.fn = this;
    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + '] ');
    }

    // eval executes the internal string as js code
    var result = eval('context.fn(' + args +') ');

    delete context.fn
    return result;
}
Copy the code

A simulated implementation of Apply

Function.prototype.apply = function (context, arr) { var context = Object(context) || window; context.fn = this; var result; if (! arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push('arr[' + i + ']'); } result = eval('context.fn(' + args + ')') } delete context.fn return result; }Copy the code

Bind’s constructor emulates the effect

Function.prototype.bind2 = function (context) { if (typeof this ! == "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); } var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fbound = function () { self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments))); } fNOP.prototype = this.prototype; fbound.prototype = new fNOP(); return fbound; }Copy the code

Bind is implemented using apply

Function.prototype.bind2 = function (context) { var self = this; / / get bind2 function from the second parameter to the last parameter var args = Array. The prototype. Slice. The call (the arguments, 1); Return the function () {/ / the arguments this time refers to bind returns the function of the incoming parameter var bindArgs = Array. Prototype. Slice. The call (the arguments); self.apply(context, args.concat(bindArgs)); }}Copy the code

About the orientation of this

  • If a function has this in it, but it is not called by a higher level object, then this refers to window. It is important to note that in the strict version of JS this does not refer to Window, but we are not going to discuss the strict version here, you can look it up on the Internet
  • If a function has this, and the function is called by a higher-level object, then this refers to the higher-level object.
  • If a function has this, it contains multiple objects, and even though the function is called by the outermost object, this refers only to the object above it
  • Special case: This always refers to the object that last called it, depending on who called it when it was executed
var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
}
var j = o.b.fn;
j();
Copy the code
  • Constructor this
Function Fn(){this.user = this.user; } var a = new Fn(); console.log(a.user); / / the commitmentsCopy the code

This is normal because the new operator does three things

var obj  = {};

obj.__proto__ = Fn.prototype;

Fn.call(obj);
Copy the code

If there is a return value:

Function fn() {this.user = 'this.user '; return {}; } var a = new fn; console.log(a.user); //undefinedCopy the code
Function fn() {this.user = 'this.user '; return function(){}; } var a = new fn; console.log(a.user); //undefinedCopy the code
Function fn() {this.user = 'this.user '; return 1; } var a = new fn; console.log(a.user); / / the commitmentsCopy the code
Function fn() {this.user = 'this.user '; return undefined; } var a = new fn; console.log(a.user); / / the commitmentsCopy the code

Conclusion: If the return value is an object, then this refers to the returned object. If the return value is not an object, then this still refers to the function instance. Null is also an object, but in this case this still refers to the function instance, because null is more specific.

1.3 Prototype/Inheritance

We get a law

Every object has a __proto__ attribute, but only function objects have a Prototype attributeCopy the code

Prototype object definition:

As the name suggests, it's just a normal object. From now on keep in mind that the prototype object is Person.prototype,Copy the code
function Person() {} Person.prototype.name = 'Zaxlct'; Person.prototype.age = 28; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function() { alert(this.name); } Person. Prototype = {name: 'zs', age: 28, job: sayName: function(){console.log(this.name); }}Copy the code

By default, all prototype objects automatically get a constructor property that points to the function (Person) on which the Prototype property belongs.

A has A default constructor property, which is A pointer to the Person(i.e. constructor). That is:

Person.prototype.constructor == Person
Copy the code

Conclusion: The prototype object (Person.prototype) is an instance of the constructor (Person).

The proto of an instance refers to the prototype object of the constructor that created the instance, for example:

Person.prototype.constructor == Person;
person1._proto_  == Person.prototype;
person1.constructor == Person;
Copy the code

Conclusion:

  • Stereotypes and prototype chains are a pattern of JS implementation inheritance
  • The prototype chain really depends onprotoRather than the prototype

1.3.1 Six Types of Handwriting Inheritance (to be added)

1.4 Promise

Manual writing:

class Promise{
	constructor(executor){
		this.state = 'pending';
		this.value = undefined;
		this.reason = undefined;
		this.onResolvedCallbacks = [];
		this.onRejectedCallbacks = [];
		let resolve = value= > {
			if (this.state === 'pending') {
				this.state = 'fulfilled';
				this.value = value;
				this.onResolvedCallbacks.forEach(fn= >fn()); }};let reject = reason= > {
			if (this.state  === 'pending') {
				this.state = 'rejected';
				this.reason = reason;
				this.onRejectedCallbacks.forEach(fn= >fn()); }};try{
			executor(resolve, reject);
		} catch(err) { reject(err); }}then(onFulfilled, onRejected) {
		// onFulfilled if this is not a function, ignore onFulfilled and return value directly
		onFulfilled = typeof  onFulfilled === 'function' ? onFulfilled : value= > value;
		// onRejected if it is not a function, just ignore onRejected and throw an error
               onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };
               
		 let promise2 =  new Promise((resolve, reject) = > {
		 	if (this.state === 'fulfilled') {
		 	    setTimeout(() = > {
		 		    try {
		 			    let x = onFulfilled(this.value);
		 			    resolvePromise(promise2, x, resove, reject);
		 		    } catch(e) { reject(e); }},0);
		 	};
		 	if (this.state === 'rejected') {
		 		try {
		 			ley x = onRejected(this.reason);
		 			resolvePromise(promise2, x, resolve, reject);
		 		} catch (e) {
		 			reject(e)
		 		}
		 	};
		 	if (this.state === 'pending') {
		 		this.onResolvedCallbacks.push(() = > {
		 		    / / asynchronous
                                setTimeout(() = > {
                                    try {
                                    let x = onFulfilled(this.value);
                                    resolvePromise(promise2, x, resolve, reject);
                                    } catch(e) { reject(e); }},0);
		 		});
		 		this.onRejectedCallbacks.push(() = > {
                                    / / asynchronous
                                    setTimeout(() = > {
                                        try {
                                        let x = onRejected(this.reason);
                                        resolvePromise(promise2, x, resolve, reject);
                                        } catch(e) { reject(e); }},0)}); }; });// Return promise, complete the chain
		 return promise2
	}
	
	catch(fn)	{
		return this.then(null, fn); }}function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if(x ! =null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y= > {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err= > {
          if(called)return;
          called = true; reject(err); })}else{ resolve(x); }}catch (e) {
      if(called)return;
      called = true; reject(e); }}else{ resolve(x); }}Copy the code

Understand (call order)

let myPromise = new Promise1((resolve, reject) = > {
    console.log('000');
    setTimeout(() = > {
    resolve('111');
    }, 1000);
});
myPromise.then((res) = > {
    console.log(res + The '-' + new Date().valueOf());
});
Copy the code

The above code is called in this order when executed:

  1. The Promise’s constructor is first called and the code in it is executed
  2. Executes the console. The log (‘ 000 ‘)… The code in this function
  3. When calling myPromise. Then (…). This. State === ‘pending’ in the case of asynchronous requests, and add the respective callback functions to the corresponding collection
  4. The result of the asynchronous callback is returned, then resolve() or reject() is called and the corresponding method in constructor is called
  5. Then call the method that adjusts the price to the set before in then, and execute let x = ondepressing (this.value); resolvePromise(promise2, x, resolve, reject);
  6. The resolvePromise method is primarily for chain-calling, or returning a new Promise object in then. And this function is a recursive call.

The other functions in Promise, All and Race

/ / resolve method
Promise.resolve = function(val){
  return new Promise((resolve,reject) = >{
    resolve(val)
  });
}
/ / reject method
Promise.reject = function(val){
  return new Promise((resolve,reject) = >{
    reject(val)
  });
}
/ / race method
Promise.race = function(promises){
  return new Promise((resolve,reject) = >{
    for(let i=0; i<promises.length; i++){ promises[i].then(resolve,reject) }; })}//all (get all promises, execute then, place the results in an array, and return them together)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject) = >{
    for(let i=0; i<promises.length; i++){ promises[i].then(data= >{
        processData(i,data);
      },reject);
    };
  });
}
Copy the code

1.5 Shallow copy

Concept:

Shallow copy: a reference that is copied to the same object as the copied object. If the data of one object is modified, the corresponding data of the other object is also modified.

Deep copy: A copy of the same data as the original object. There is no relationship between the two objects. Modifying the data in one object does not affect the other.

Prerequisite: There is no difference between shallow copy and shallow copy for primitive data types, we only refer to reference data types

The advantages and disadvantages of implementing deep copy:

  1. let cloneobj = JSON.parse(JSON.stringify(originobbj)); This approach does implement deep copy, but it has a disadvantage: undefined, function, and symbol are ignored during the conversion process, so objects with such data structures will be ignored.
  2. Object.assign(), concat(), slice(), (… If an attribute is a reference type, then it copies only the value of the attribute, which is a reference pointer, so there is no complete deep copy. To be precise, if there is only one layer and the data is of basic data type, then deep copy can be achieved, otherwise shallow copy can be achieved.
  3. For true deep copy, recurse

Handwritten deep copy

function cloneDeep3(source, uniqueList) {
    if(! isObject(source))return source; 
    if(! uniqueList) uniqueList = [];// Add code to initialize the array
        
    var target = Array.isArray(source) ? [] : {};
    // Data already exists, return saved data
    var uniqueData = find(uniqueList, source);
    if (uniqueData) {
        return uniqueData.target;
    };
        
    // Data does not exist, save the source data, and the corresponding reference
    uniqueList.push({
        source: source,
        target: target
    });

    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep3(source[key], uniqueList);  // Add code to the array
            } else{ target[key] = source[key]; }}}return target;
}

// Add a method for finding
function find(arr, item) {
    for(var i = 0; i < arr.length; i++) {
        if (arr[i].source === item) {
            returnarr[i]; }}return null;
}
Copy the code

The simplest implementation

function cloneDeep2(source) {

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

Use linked lists to solve the problem of circular reference writing

function deepCopy3(obj) {
    // Hash table to record the reference relation of all objects
    let map = new WeakMap(a);function dp(obj) {
        let result = null;
        let keys = Object.keys(obj);
        let key = null,
            temp = null,
            existobj = null;

        existobj = map.get(obj);
        // If the object is already logged, return it
        if(existobj) {
            return existobj;
        }

        result = {}
        map.set(obj, result);

        for(let i =0,len=keys.length; i<len; i++) { key = keys[i]; temp = obj[key];if(temp && typeof temp === 'object') {
                result[key] = dp(temp);
            }else{ result[key] = temp; }}return result;
    }
    return dp(obj);
}
Copy the code

1.6 Event Mechanism /Event Loopzhuanlan.zhihu.com/p/34229323)

Introduce some basic concepts:

JavaScript Engine and JavaScript Runtime

In short, there are two parts to getting JavaScript to work:

  • Compile and execute JavaScript code, complete memory allocation, garbage collection, etc.
  • Provide objects or mechanisms for JavaScript to interact with the outside world.

While JavaScript is running, the JavaScript Engine creates and maintains the appropriate Heap and Stack, The JavaScript Runtime provides a set of apis (such as setTimeout, XMLHTTPRequest, etc.) to accomplish a variety of tasks.

JavaScript is a single-threaded programming language with only one call stack, which means it can only do one thing at a time.

In the process of running JavaScript, only one thread is really responsible for executing JavaScript code, usually called the main thread, and various tasks will be executed synchronously in a queued way.

However, JavaScript is a non-blocking, Asynchronous, Concurrent programming language, which brings us to JavaScript’s Event Loop mechanism.

Event Loop

Event loops are the core mechanism that allows JavaScript to be single-threaded without blocking, and are the basis of the JavaScript Cocurrency Model. Is a mechanism for coordinating events, user interactions, script execution, UI rendering, network requests, and so on.

To put it simply: Event loops are simply a mechanism for implementing asynchrony

Note: In JavaScript Engine (V8 as an example), the ECMAScript standard is implemented, not the Event Loop. That is, the Event Loop belongs to the JavaScript Runtime and is provided by the host environment (such as the browser). So make no mistake, and that’s why JavaScript Engine and Runtime were introduced earlier

Task queue in Event Loop

When executing and coordinating various tasks, Event Loop maintains its own Task Queue, which is divided into Task Queue and Microtask Queue.

  1. Task Queue

An Event Loop will have one or more Task queues, which are ordered FIFO lists of tasks from different Task sources (Task sources).

In the HTML standard, several common Task sources are defined:

  • DOM manipulation;
  • User interaction;
  • Networking C.
  • History Traversal (History API operation).

The definition of Task Source is very broad, common mouse and keyboard events, AJAX, database operations (such as IndexedDB), timer related setTimeout, setInterval, etc. All tasks from these Task sources are placed in the corresponding Task Queue for processing.

Tasks, Task Queues, and Task sources are specified as follows:

  • Tasks from the same Task Source must be placed in the same Task Queue.
  • Tasks from different Task sources can be placed in different Task queues.
  • Tasks in the same Task Queue are executed sequentially;
  • However, for different Task queues (Task Sources), the browser schedules tasks that come from a particular Task Source first.

2.Microtask Queue

Like Task Queue, Microtask Queue is an ordered list. The difference is that an Event Loop has only one Microtask Queue.

Microtask Sources are not explicitly defined in the HTML standard, but are generally considered to be:

  • Promise
  • MutationObserver
How JavaScript Runtime works

Once you understand the basic concepts of Event loops and queues, you can take a relatively macro view of how JavaScript Runtime works. The simplified steps are as follows:

  1. The main thread loops continuously;
  2. For synchronous tasks, the execution context is created and the execution stack is entered in sequence.
  3. For asynchronous tasks:
  • As in Step 2, execute this code synchronously;
  • Add tasks (or microtasks) to the Event Loop Task queue;
  • Specific asynchronous operations are performed by other threads.

Other threads are: although JavaScript is single-threaded, the browser kernel is multi-threaded, and it offloads GUI rendering, timer firing, HTTP requests, and so on to a dedicated thread.

In addition, in Node.js, asynchronous operations are performed first by an asynchronous interface provided by the OS or third-party system before being handled by the thread pool.

4. When the main thread finishes executing all the tasks in the current execution stack, it will read the task queue of Event Loop, take out and execute the tasks;

5. Repeat the preceding steps.

Take setTimeout for example:

  1. The main thread executes the setTimeout function itself in step.
  2. Add the Task responsible for executing the setTimeout callback to the Task Queue.
  3. The timer starts to work (in fact, the Event Loop constantly checks the system time to determine whether the specified time point has been reached).
  4. The main thread continues to perform other tasks.
  5. When the stack is empty and the timer fires, the main thread fetches the Task and executes the corresponding callback function. Obviously, executing setTimeout does not cause a block. Of course, if the main thread is busy (the stack is not empty), the callback will not be executed even when the time is up, so callback functions such as setTimeout cannot guarantee execution timing.

Obviously, executing setTimeout does not cause a block. Of course, if the main thread is busy (the stack is not empty), the callback will not be executed even when the time is up, so callback functions such as setTimeout cannot guarantee execution timing.

Event Loop processing model

The whole process of running JavaScript Runtime has been briefly introduced, and the Event Loop, as an important part of the process, is quite complex each time, so it will be introduced separately. I will try to keep the HTML standard definition of the Processing Model as simple as possible. The steps are as follows:

  1. Execute Task: Remove the oldest Task from the Task Queue and execute it. If there is no Task, skip it.
  2. Execute Microtasks: Traverse the Microtask Queue and execute all Microtasks (see Perform a Microtask checkpoint).
  3. Enter the Update the rendering stage:
  • Set the return value of now() in the Performance API. Performance API is a part of W3C High Resolution Time API, which is used for front-end Performance measurement. It can measure various rendering indicators such as first rendering and first rendering content in fine granularity. It is an important technical means for front-end Performance tracking, and interested students can pay attention to it.

  • Go through the Documents related to the Event Loop and perform the update rendering. During the iteration, the browser decides whether to skip the update based on various factors.

  • When the browser confirms to continue with the update, it handles the update rendering:

1. Trigger various events: Resize, Scroll, Media Queries, CSS Animations, and Fullscreen API.

2. Perform animation frame callbacks, window requestAnimationFrame right here.

Update intersection Observations, also known as the Intersection Observer API (for lazy loading of images). Update the render and UI and submit the final results to the interface.

At this point, one Loop of the Event Loop ends… Instead, use a schematic diagram (Process. nextTick is highlighted to distinguish it).

Microtask Queue Execution time

In the Event Loop processing model described above, the Microtask Queue is executed at step 2. In fact, according to the HTML standard, Microtask Queue can be executed in the following situations:

  1. When a Task finishes executing (as described above).
  2. When you enter the Clean up after Running script phase of Calling Scripts.
  3. When nodes are created and inserted.
  4. When parsing an XML document.

Meanwhile, Microtasks that were dynamically added in the current Event Loop round will also be executed in this Event Loop (the figure above is actually drawn).

Finally, it is important to note that executing Microtasks is conditional: the current execution stack must be empty and there is no execution context running. Otherwise, you must wait until all the tasks in the execution stack are completed before you can start executing Microtasks.

That is: JavaScript ensures that the currently executing synchronized code is not interrupted by Microtasks.

Here’s a classic example:

<div id="outer">
    <div id="inner"></div>
</div>

const inner = document.getElementById("inner");
const outer = document.getElementById("outer");

// Listen for outer's property changes.
new MutationObserver(() = > console.log("mutate outer"))
    .observe(outer, { attributes: true });

// Handle the click event.
function onClick(){
    console.log("click");
    setTimeout(() = > console.log("timeout"), 0);
    Promise.resolve().then(() = > console.log("promise"));
    outer.setAttribute("data-mutation".Math.random());
}

// Listen for the click event.
inner.addEventListener("click", onClick);
outer.addEventListener("click", onClick);
inner.click();

Outer click promise mutate outer timeout timeout inner. Click (); click click end promise mutate outer promise timeout timeout */

Copy the code

Conclusion: Why is the success of active call and user click call different?

According to multiple checks, only when the user really interactive operation on the tour trigger events will trigger the tour event thread, and then follow the PRINCIPLE of JS event cycle – asynchronous processing; This is the normal order of processing.

Active calls in js code are treated only as publish/subscribe (event nature) code execution — synchronous processing. So the onClick function in the trigger is handled synchronously (the main thread) and therefore has a higher priority than the microtask.

Functional programming

Please refer to the website: juejin.cn/post/684490…

Web Worker

Please refer to the website: github.com/linxiangjun…

Details of the ES6 major release

Please refer to juejin.cn/post/684490…

There are eight component communication modes in Vue

Props / $emit

This is easy, so we’re not going to do the summary here

Second,
c h i l d r e n / children /
parent

// In the parent component
<template>
  <div class="hello_world">
    <div>{{msg}}</div>
    <com-a></com-a>
    <button @click="changeA">Click Change child component values</button>
  </div>
</template>

<script>
import ComA from './test/comA.vue'
export default {
  name: 'HelloWorld'.components: { ComA },
  data() {
    return {
      msg: 'Welcome'}},methods: {
    changeA() {
      // Get the child component A
      this.$children[0].messageA = 'this is new value'}}}</script>
Copy the code
// In the child component
<template>
  <div class="com_a">
    <span>{{messageA}}</span>
    <p>Get the value of the parent component: {{parentVal}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      messageA: 'this is old'}},computed: {parentVal(){
      return this.$parent.msg; }}}</script>
Copy the code

Matters needing attention: Take note of the boundary case, for example, holding parent on #app yields an instance of newVue(), holding parent on #app yields an instance of newVue(), and holding parent on #app yields an instance of newVue(). Holding parent on this instance yields undefined, while the bottom child holds children as an empty array. Also notice that children is an empty array. Also notice that children is an empty array. $parent is an array of objects, and $parent is an object. $parent is an array of objects, and $parent is an object.

Third, dojo.provide/inject

Provide/Inject is a new API in Vue2.2.0. Simply put, provide variables in the parent component and inject variables in the child component.

This is the parent component <template><div>
    <div class="hello_world">
      <span>I am father:</span>
      <span style="margin-right: 20px;">{{msg}}</span>
      <button @click="changeM">Change the user</button>
      <button @click="changeA">Click Change child component values</button><br/><br/>
      <div>======= I'm a child =======</div><br/>
      <transformChild></transformChild>
    </div>
  </div>
</template>

<script>
  import transformChild from './transformChild';
  export default {
    data() {
      return {
        msg: 'Welcome'.user: { name: 'zs' },
        grade: 2}},provide() {
      return {
        / * reason: The provide/ Inject mechanism does not support responsive programming. Direct subsequent modification of the returned object provided will not refresh the provide/ Inject mechanism, that is, the top-level response mechanism of the returned object provided will become invalid. There is no way to operate on the top-level attributes of the object, nor to respond to this.user.name = 'ls' if the attributes referenced are primitive data types; This. user = {age: 222}; This approach does not work, because in order to respond, you need to reference the original object in order to respond */ 
        // for: this.user
        getContext: () = > ({
          user: this.user,
          grade: this.grade,
      })
      };
    },
    methods: {
      changeA(){
        this.$children[0].messageA = 'this is new value'
      },
      changeM(){
        // this.user.name = 'ls';
        this.user = {age: 222};
        this.grade += 1; }},components: {
      transformChild
    }
  }
</script>

<style lang="scss" scoped>

</style>
Copy the code
This component is a child <template><div class="com_a">
    <span>{{messageA}}</span>
    <p>Get the value of the parent component: {{parentVal}}</p>
    <button @click="changeSuper">Changes the value of the parent component</button>
    <p>{{ context.user }}</p>
    <p>{{ context.grade }}</p>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        messageA: 'this is old'}},// inject: ['for'],
    inject: ['getContext'].computed: {
      parentVal(){
        return this.$parent.msg;
      },
      context(){
        return this.getContext(); }},methods: {
      changeSuper(){
        this.$parent.msg = 'Thanks'}}}</script>

<style lang="scss" scoped>

</style>
Copy the code

Because the provide/ Inject mechanism does not support responsive programming, direct modification of the returned object provided will not refresh the provide/ Inject mechanism, that is, the top-level response mechanism of the returned object provided will be invalid and the top-level properties of the object cannot be operated. This mechanism causes responsive delivery to fail in three ways:

  • The context above cannot be declared in computed. Because computed returns a new value (reference) each time, provide records only the reference to the original context, and the new context is not flushed into provide when the data changes.
  • Context is declared in data, but if this. Context = {… }, the new context is not updated in provide, the context in provide is always the reference copied to it when initialized. This causes the context to refresh dynamically in the parent component, but not in the child component
  • If we return the context directly in the provide function, user and grade will become top-level properties, and reassignment operations in created and subsequent reassignment operations will not be reflected in the provide function and will lose responsiveness.

Best use:

provide() {
    return {
      getContext: () = > ({
          user: this.user,
          grade: this.grade,
      })
    };
}
Copy the code

In the child component

inject: ['getContext'].computed: {
    context() {
        return this.getContext(); }}Copy the code

Ref/refs

Ref: If used on a normal DOM element, the reference refers to the DOM element; If used on a child component, the reference refers to the component instance, which can be used to call the component’s methods or access data directly. Let’s look at an example of a ref accessing a component:

// Subcomponent a.ue

export default {
  data () {
    return {
      name: 'Vue.js'}},methods: {
    sayHello () {
      console.log('hello')}}}Copy the code
// Parent component app.vue

<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.name);  // Vue.js
      comA.sayHello();  // hello}}</script>
Copy the code

Five, the eventBus

EventBus, also known as the eventBus, can be used in vue as a bridge concept, as all components share the same event center to which they can register to send and receive events, so that components can notify other components.

  • EventBus also has its drawbacks. When a project is large, it can be a disaster that is difficult to maintain

How can YOU use eventBus to communicate data between components in a Vue project? Specifically through the following steps

1. Initialization

You first need to create an event bus and export it so that other modules can use it or listen on it.

// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()
Copy the code

2. Send events

Suppose you have two components: additionNum and showNum. These two components can be siblings or parent components. Here we take the sibling component as an example:

<template>
  <div>
    <show-num-com></show-num-com>
    <addition-num-com></addition-num-com>
  </div>
</template>

<script>
import showNumCom from './showNum.vue'
import additionNumCom from './additionNum.vue'
export default {
  components: { showNumCom, additionNumCom }
}
</script>

  <div>
    <show-num-com></show-num-com>
    <addition-num-com></addition-num-com>
  </div>
</template>

<script>
import showNumCom from './showNum.vue'
import additionNumCom from './additionNum.vue'
export default {
  components: { showNumCom, additionNumCom }
}
</script>
Copy the code
// addtionNum. Vue sends events

<template>
  <div>
    <button @click="additionHandle">+ adder</button>    
  </div>
</template>

<script>
import {EventBus} from './event-bus.js'
console.log(EventBus)
export default {
  data(){
    return{
      num:1}},methods: {additionHandle(){
      EventBus.$emit('addition', {
        num:this.num++
      })
    }
  }
}
</script>
Copy the code

3. Receive events

// events are received in shownum. vue

<template>
  <div>Count and: {{count}}</div>
</template>

<script>
import { EventBus } from './event-bus.js'
export default {
  data() {
    return {
      count: 0}},mounted() {
    EventBus.$on('addition'.param= > {
      this.count = this.count + param.num; }}})</script>
Copy the code

This makes it possible to click the add button in the component addtionNum. Vue and display the sum with the num passed in showNum. Vue.

4. Remove event listeners

If you want to remove listening for events, you can do something like this:

import { eventBus } from 'event-bus.js'
EventBus.$off('addition', {})
Copy the code

Six, Vuex

Specific implementations are available everywhere, please check them out.

LocalStorage/sessionStorage

This kind of communication is relatively simple, but the disadvantage is that the data and state are chaotic and not easy to maintain. Through the window. The localStorage. The getItem (key) to get the data through the window. The localStorage. SetItem (key, value) to store data

  • Json.parse ()/json.stringify () localStorage/sessionStorage can be combined with Vuex to persist data, and vuex can be used to solve the problem of data and state confusion.

eight
a t t r s with Attrs and
listeners

Now let’s discuss A case where component A and component D in the component diagram we gave at the beginning were intergenerational. How did they communicate before?

  1. The props binding is used to carry out the information transfer level by level. If the state change in component D requires data transfer to A, the event system is used to transfer data level by level
  2. With eventBus, this is fine in this case, but the code is less maintainable and less readable when developed in collaboration with multiple people
  3. Use Vuex for data management, but if you’re just passing data around without intermediate processing, using Vuex feels like overkill.

In Vue2.4, attrs and attrs and attrs and Listeners are introduced and the inheritAttrs option is added to address this requirement. Prior to version 2.4, feature bindings (except for class and style) that were not recognized (and retrieved) as prop in the parent scope by default were “rolled back” and applied to the root element of the child component as plain HTML features. Let’s look at an example of cross-level communication:

// app.vue
// index.vue

<template>
  <div>
    <child-com1
      :name="name"
      :age="age"
      :gender="gender"
      :height="height"
      title="Programmer growth is north."
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () = > import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      name: "zhang".age: "18".gender: "Female".height: "158"}; }};</script>
Copy the code
// childCom1.vue

<template class="border">
  <div>
    <p>name: {{ name}}</p>
    <p>ChildCom1 $attrs: {{$attrs}}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () = > import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false.// You can turn off properties that are not declared in props that are automatically mounted to component root elements
  props: {
    name: String // Name is bound as the props property
  },
  created() {
    console.log(this.$attrs);
     / / {" age ", "18", "gender" : "female" and "height" : "158", "title" : "programmers growth refers to the north"}}};</script>
Copy the code
// childCom2.vue

<template>
  <div class="border">
    <p>age: {{ age}}</p>
    <p>childCom2: {{ $attrs }}</p>
  </div>
</template>
<script>

export default {
  inheritAttrs: false.props: {
    age: String
  },
  created() {
    console.log(this.$attrs); 
    / / {" gender ":" female "and" height ":" 158 ", "title" : "programmers growth refers to the north"}}};</script>
Copy the code

conclusion

Common usage scenarios can be divided into three categories:

  • Parent-child component communication: props; parent/parent / parent/children; provide / inject ; ref ; attrs/attrs / attrs/listeners
  • Sibling communication: eventBus; vuex
  • Cross-level communication: eventBus; Vuex; Provide/inject, attrs/attrs /attrs /listeners

Full permutation of an array

The first is pure recursion

var res = [];
function permutation(arr,len,index){
    if(len == index){
        res.push(arr.slice());
    }
    for(vari = index; i<len; i++){ [arr[i],arr[index]] = [arr[index],arr[i]];console.log(arr);
        permutation(arr,len,index+1);
        console.log(arr);
        [arr[i],arr[index]] = [arr[index],arr[i]];
    }
}
permutation([1.2.3].3.0);
console.log(res);
Copy the code

The second one does some critical processing and is probably more efficient (this one is easier to understand)

function permutation(arr){
	if(arr.length == 1)
		return arr;
	else if(arr.length == 2)
		return [[arr[0],arr[1]],[arr[1],arr[0]]];
	else{
		var temp = [];
		for(var i=0; i<arr.length; i++){var save = arr[i];
			arr.splice(i,1);
			var res = permutation(arr);
			arr.splice(i,0,save);
			for(var j=0; j<res.length; j++){ res[j].unshift(save); temp.push(res[j]); }}returntemp; }}var result = permutation([1.2.3.4]);
console.log(result);
Copy the code

Hash table

Concept explanation:

  1. Hashing: The process of converting a large number to an array range index

  2. Hash function: Usually we convert words into large numbers, and the code implementation of the hash of large numbers is put into a function, which we call the hash function.

  3. Hash table: The array into which the data is ultimately inserted, encapsulating the entire structure, is called a hash table.

  4. Collisions: As we mentioned earlier, in general, the input range of a hash function is much larger than the output range, so when using a hash table, there will always be collisions, even if we use a perfect hash function, when enough keys are entered. However, most hash functions are not perfect, so there is still the possibility of hash collision, then need some methods to solve the problem of hash collision, the common methods are open addressing method and zipper method.

  5. Chain address method (zipper method) :

    • As we can see from the picture, the chained address method resolves the conflict by storing a chain instead of a single data in each array unit
    • What data does this chain use? The common ones are arrays or linked lists.
    • A linked list, for example, is a list of items stored in each array cell, and if a duplicate element is found, it can be inserted at the beginning or end of the list.
    • When querying, it first finds the corresponding position according to the hashized subscript value, then takes out the linked list and searches for the data to be searched successively.
    • Arrays or linked lists?
    • Arrays or linked lists are about as efficient. It is best to use a linked list when the inserted data is placed at the beginning of an array or list because it is more likely that the newly inserted data will be used for fetching. This is because the array is inserted at the beginning and all subsequent items are moved one place in sequence.
    • The specific choice of linked list or array depends on the specific requirements.
  6. Open address method: The main way to work is to find empty cells to add duplicate data. There are three different ways to explore this location:

    • Linear detection: Linear detection is a step by step search from index position +1 to find the appropriate position to place the conflicting elements. The appropriate position is the empty position. The search rule, which is also based on the index obtained by the hash function, starts the search until an empty position is found. If it is not found, it indicates that the data is not stored in the hash table. ** Linear detection has a serious problem with clustering, which affects the performance of the hash table, whether it is insert, query, or delete; Because when inserting elements, after encountering aggregation, they will be continuously probed, and the number of probes in this process will arbitrarily affect the performance.
    • Second detection: The second detection as long as the detection step is optimized, such as starting from the subscript value x, x+1², x+2², x+3². ** For example, if we insert 32-112-82-2-192 consecutively, then the complement length will be the same when adding consecutively.
    • Rehash: hash the keyword with another hash function again, using the result of this hash as the step size, need to have the following characteristics: and the first hash function is different, can not output 0, in fact, computer experts have designed a hash function that works well: StepSize = constant – (key%constany), where constant is a prime number and smaller than the capacity of the array
    • Load factor: The load factor represents the ratio of the data items already contained in the current hash table to the total length of the hash table. Loading factor = Total data items/hash table length.
    • Performance: the chain address method will increase linearly with the increase of the loading factor, while the open address method will increase exponentially, so in the actual development of the chain address method will choose more.
  7. How do you measure the merits of a hash function?

    • Fast calculation: The advantage of hash table lies in efficiency, so it is very important to quickly obtain the corresponding hashCode. We need to quickly obtain the hashCode of elements, which may be optimized using Horner algorithm.
    • Uniform distribution: In a hash table, whether linked address method or open address method, when multiple elements are mapped to the same location, it will affect the efficiency. Therefore, a good hash function should map elements to different locations as much as possible, so that elements are evenly distributed in the hash table. It is best to use prime numbers for constants in hash tables.
  8. Implementation of HashMap in Java:

    • Index = HashCode(key) & (length-1)
    • For example, calculate the hashcode of book and the result is 3029737 in decimal and 101110001110101110 1001 in binary
    • Assuming a HashMap Length of 16 by default, the calculation of Length-1 results in 15 in decimal and 1111 in binary
    • Add and to the two results, 1001, which is 9 in decimal, so index=9
    • This approach is more efficient than modulo because computer operations are close to binary
  9. How to achieve accurate timer (implementation principle: when each timer is executed, the system time is obtained to correct, although there may be errors in each run, but through the system time to repair each run, so that each time after each time can get a compensation)

Reference website: (HTTPS://mp.weixin.qq.com/s/-mje2nwKUpWoFdJsDcKaXg)
function timer() {
	   var speed = 500,
	   counter = 1, 
	   start = new Date().getTime();
	   
	   function instance(){
	    var ideal = (counter * speed),
	    real = (new Date().getTime() - start);
	    
	    counter++;
	    var diff = (real - ideal);
	    // Repair by system time
	    window.setTimeout(function() { instance(); }, (speed - diff)); 
	   };
	   
	   window.setTimeout(function() { instance(); }, speed);
	}
Copy the code
  1. Distinguish between splice, slice and split
String -> split('x'-> Slice (start, end) where start and end are index values, end is not mandatory, from start to end [)Array- > slice (start, end), and the operation of the string on the top of the same, if the end don't write, is from the start index to the end of the data - > splice (index, howmany, item1, item2, item...). Howmany indicates the number of elements to be replaced or deleted from the starting position. Item is an optional item indicating the new element to be replaced. If there are any elements to be replaced, if there are no elements to be replaced, if there are no elements to be deletedCopy the code
  1. Sliding window algorithm
var lengthOfLongestSubstring = function (s) {
    let map = new Map(a);let i = -1
    let res = 0
    let n = s.length
    for (let j = 0; j < n; j++) {
        if (map.has(s[j])) {
            i = Math.max(i, map.get(s[j]))
        }
        res = Math.max(res, j - i)
        map.set(s[j], j)
    }
    return res
};
// Test case
let str = 'abwcdefcrtyupc'
let maxLen = lengthOfLongestSubstring(str);
console.log(maxLen);  / / 7
Copy the code

Core ideas: * The advantage of setting I = -1 is that the element is accumulated until repeated elements occur, and then updating res * takes the element’s value as the key and index as value * Update I to the last subscript *, then the window’s starting index is not -1, but the index saved in the map for the current repeating element, that is, the starting position of the view window is moved from 0 to I * example: Abwcdefc When traversing c in the string, update I to 3, that is, the start position of the slide view is 3, then the width of the view is the value of j-i, then it is compared to the current res, the larger value is recorded, and finally returns.

Vue source code analysis

function mergeData (to: Object.from:?Object) :Object {
  // No from directly returns to
  if (!from) return to
  let key, toVal, fromVal
  const keys = Object.keys(from)
  // iterate over from key
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    toVal = to[key]
    fromVal = from[key]
    // If the key in the from object is not in the to object, use the set function to set the key and corresponding value for the to object
    if(! hasOwn(to, key)) { set(to, key, fromVal)// If the key in the from object is also in the to object, and both attributes are pure objects, then deep merge is performed recursively
    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
    // Do nothing else
  }
  return to
}
Copy the code

Description: At the top is the data merge strategy in vue source code (this is the most core code, but not all). The core idea is that if from does not exist, return to, if it does exist, determine whether the key in from is in to, if it does not exist, add it directly to the to object through the Set function. If val is an object, deep merge is done recursively, otherwise nothing is done; In either case, the mergedDataFn or mergedInstanceDataFn function will be returned, so the mergedOptions function must return a data object. Each component instance is guaranteed to have a unique copy of data, avoiding data interaction between components. As you will see later on when we initialize the Vue, when we initialize the data state, we fetch the data and process it by executing strats.data.

Why not merge the data in the merge phase instead of waiting until initialization?

What does this question mean? We know that strats.data will be processed as a function during the merge phase, but the function will not be executed until later in the initialization phase, when mergeData will be called to merge the data. What is the purpose of this?

When we initialize Vue, we will see that inject and props are initialized before data. This ensures that we can initialize data using props

Lifecycle merge strategy

function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function|?Array<Function>
): ?Array<Function> {
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook= > {
  strats[hook] = mergeHook
})

export const LIFECYCLE_HOOKS = [
  'beforeCreate'.'created'.'beforeMount'.'mounted'.'beforeUpdate'.'updated'.'beforeDestroy'.'destroyed'.'activated'.'deactivated'.'errorCaptured'
]
Copy the code

The parsed state of the ternary operator code

Return (does it have childVal, which determines whether the component's options have a life-cycle hook function with the corresponding name)? If childVal is present, does parentVal exist? If parentVal is present, combine the two into an array using the concat method: if parentVal is absent, is childVal an array? Returns parentVal if childVal is an array, otherwise it is an element of the array, and then returns the array: parentVal if there is no childValCopy the code

After the mergeHook process, the component option lifecycle hook functions are merged into an array. The first ternary operator checks if there is a childVal, that is, if the component’s options write a lifecycle hook function, then parentVal is returned. The strats[hooks] function will not execute at all if parentVal is not there:

new Vue({
  created: function () {
    console.log('created')
  }
})
Copy the code

If we use this code as an example, then for the Strats. created policy function (note that strats.created is mergeHooks), childVal is the created option in our example, which is a function. ParentVal should be Vue. Options. created, but Vue. Options. created does not exist, so the strats.created function returns an array:

options.created = [
  function () {
    console.log('created')
  }  
]
Copy the code

Such as:

const Parent = Vue.extend({
  created: function () {
    console.log('parentVal')
  }
})

const Child = new Parent({
  created: function () {
    console.log('childVal')
  }
})
Copy the code

Where Child is generated using new Parent, childVal is:

created: function () {
  console.log('childVal')
}
Copy the code

ParentVal is not Vue. Options. created, but parent-options. created. It is actually handled by mergeOptions inside the vue. extend function, so it should look like this:

Parent.options.created = [
  created: function () {
    console.log('parentVal')
  }
]
Copy the code

ParentVal. Concat (childVal) merges parentVal and childVal into an array. So here’s the end result:

[
  created: function () {
    console.log('parentVal')
  },
  created: function () {
    console.log('childVal')
  }
]
Copy the code

Also notice the third ternary operator:

: Array.isArray(childVal)
  ? childVal
  : [childVal]
Copy the code

It determines whether childVal is an array. What does that say? The lifecycle hook can be written as an array, although it is not documented in Vue.

new Vue({
  created: [
    function () {
      console.log('first')
    },
    function () {
      console.log('second')
    },
    function () {
      console.log('third')
    }
  ]
})
Copy the code

Merge strategy for assets options

Directives, filters, and components are considered as resources in Vue, and it is well understood that directives, filters, and components are provided as third-party applications, such as if you need a component that simulates rolling, Of course you can use a super powerful third-party component called scroll flip Page, so scroll flip page can be considered a resource, and the same goes for directives and filters except for components.

And the code we are going to look at is to combine resource options such as directives, filters, and components as follows:

/** * Assets * * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. */ function mergeAssets ( parentVal: ? Object, childVal: ? Object, vm? : Component, key: string ): Object { const res = Object.create(parentVal || null) if (childVal) { process.env.NODE_ENV ! == 'production' && assertObjectType(key, childVal, vm) return extend(res, childVal) } else { return res } } ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets }) export const  ASSET_TYPES = [ 'component', 'directive', 'filter' ]Copy the code

The above code itself is logically simple. First create the res object based on parentVal, then determine if there is a childVal, and if so, use extend to blend the properties from childVal into the RES object and return it. If there is no childVal, return res.

For example, you know that we can use components or etc directly in any component template, but we don’t explicitly declare those components in the components option of our own component instance. So how does this work? The answer lies in the mergeAssets function. Take the following code for example:

var v = new Vue({
  el: '#app',
  components: {
    ChildComponent: ChildComponent
  }
})
Copy the code

In the above code, we create a Vue instance and register a ChildComponent, ChildComponent. ChildVal within mergeAssets is the components option in this example:

components: {
  ChildComponent: ChildComponent
}
Copy the code

ParentVal is Vue.options.components, and Vue.options.components is Vue.options.components.

Vue.options = {
	components: {
	  KeepAlive,
	  Transition,
	  TransitionGroup
	},
	directives: Object.create(null),
	directives:{
	  model,
	  show
	},
	filters: Object.create(null),
	_base: Vue
}
Copy the code

So Vue.options.components should be an object:

{
  KeepAlive,
  Transition,
  TransitionGroup
}
Copy the code

ParentVal is an object with three built-in components

const res = Object.create(parentVal || null)
Copy the code

KeepAlive you can access KeepAlive objects through res.KeepAlive, because while a RES object doesn’t have a KeepAlive property on its own, its prototype does.

Return extend(res, childVal). The res variable is appended to the ChildComponent property, resulting in the following res:

Res = {ChildComponent // prototype__proto__ : {KeepAlive, Transition, TransitionGroup}}Copy the code

So that’s why we can use some of the built-in components without explicitly registering them, and that’s how the built-in components are implemented, as are subclasses created by vue.extend, which search the components through prototypes layer by layer.

A prototype pattern is used so that each component has KeepAlive, Transition, and TransitionGroup added by default.

Select watch’s merge strategy

/** * Watchers. * * Watchers hashes should not overwrite one * another, so we merge them as arrays. */ strats.watch = function ( parentVal: ? Object, childVal: ? Object, vm? : Component, key: string ): ? Object { // work around Firefox's Object.prototype.watch... if (parentVal === nativeWatch) parentVal = undefined if (childVal === nativeWatch) childVal = undefined /* istanbul ignore if */ if (! childVal) return Object.create(parentVal || null) if (process.env.NODE_ENV ! == 'production') { assertObjectType(key, childVal, vm) } if (! parentVal) return childVal const ret = {} extend(ret, parentVal) for (const key in childVal) { let parent = ret[key] const child = childVal[key] if (parent && ! Array.isArray(parent)) { parent = [parent] } ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child] } return ret }Copy the code
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
Copy the code

Where nativeWatch comes from the core/util/env.js file, you can check its function in the tool method full solution under the core/util directory. Object. Prototype has a native Watch function in Firefox, so even if you don’t define the watch attribute for an ordinary Object, you can still access the native Watch attribute through the prototype chain. This can be confusing for Vue when dealing with options, because Vue also provides an option called Watch. Even if you don’t have a Watch option in your component options, Vue accesses the native Watch through prototypes. This is not what we want, so the above two lines of code are intended as a workaround. When it is found that the component option is the browser’s native Watch option, it indicates that the user did not provide the Vue watch option and reset it to undefined

if (! childVal) return Object.create(parentVal || null)Copy the code

Checks if there is a childVal, that is, if the component option has a watch option, and if not, creates an object directly modeled after parentVal and returns it (if there is one).

If there is a watch option in the component options, i.e. childVal is present, the code continues, and this code will be executed:

if (process.env.NODE_ENV ! == 'production') { assertObjectType(key, childVal, vm) } if (! parentVal) return childValCopy the code

Since childVal exists at this time, assertObjectType is used in non-production environments to type check childVal to see if it is a pure object. We know that Vue’s watch option needs to be a pure object. It then checks if parentVal is present, and if not returns childVal directly, using the watch of the component option.

If parentVal exists, then the code continues, and parentVal and childVal both exist, so we need to do the merge, which is the following code:

Const ret = {} const ret = {} const ret = {} const ret = {} const ret = {} Extend (ret, parentVal) // traverses childVal for (const key in childVal) {// Traverses childVal for (const key in childVal); Let parent = ret[key] // child = ret[key] Const child = childVal[key] const child = childVal[key] const child = childVal[key] const child = childVal[key] Array.isarray (parent)) {parent = [parent]} ret[key] = parent So just put child concat in, right? Parent.concat (child) // If the parent does not exist, convert the child to an Array and return: array.isarray (child)? Child: [child]} // Return retCopy the code

The above code snippet is commented in detail. Ret constants are first defined and finally returned, so the code in the middle is enriching ret constants. The extend function is then used to blend the properties of parentVal into RET. It then starts a for in loop through childVal. The purpose of this loop is to check if the values in the child options are also in the parent options, and if so, to merge the parent options into an array, otherwise it returns the child options as an array

Here’s an example:

// Subclass const Sub = Vue. Extend ({test: function () {console.log('extend: Test change')}}}) const v = new Sub({el: '#app', data: {test: 1}, // Test: {test: Function () {console.log('instance: test change')}}}) // Change the value of test v.test = 2Copy the code

In the above code, when we modify the value of v.test, both functions are executed to observe test changes.

We create instance V with subclass Sub. For instance V, childVal is the watch of the component option:

watch: {
  test: function () {
    console.log('instance: test change')
  }
}
Copy the code

The parentVal is sub. options.

watch: {
  test: function () {
    console.log('extend: test change')
  }
}
Copy the code

Eventually the two Watch options will be merged into an array:

watch: {
  test: [
    function () {
      console.log('extend: test change')
    },
    function () {
      console.log('instance: test change')
    }
  ]
}
Copy the code

Merge policies for options props, Methods, Inject, and computed

/** * Other object hashes. */ strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { if (childVal && process.env.NODE_ENV ! == 'production') { assertObjectType(key, childVal, vm) } if (! parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return ret }Copy the code

This code adds props, methods, Inject, and computed policy functions to the Strats policy object, which, as the name implies, combine options of the same name and use the same policy.

The four options — props, Methods, Inject, and computed — have one thing in common. All of them have a pure object structure, although the options for props or inject may be an array. But as we learned in the section on normalization of Vue’s ideas options, Vue internally normalizes it as an object. So let’s look at how Vue handles these object hashes.

The content of the policy function is as follows:

If (childVal && process.env.node_env! == 'production') {assertObjectType(key, childVal, vm)} ParentVal) return childVal // If parentVal exists, create a ret object and mix the properties of parentVal and childVal into ret. Const ret = object. create(null) extend(ret, parentVal) if (childVal) extend(ret, ChildVal) // Finally returns the RET object. return retCopy the code

First, it checks if childVal exists, that is, if the child option has an attribute associated with it, and if so, it needs to be checked with assertObjectType in a non-production environment to ensure that its type is pure object. ParentVal is then checked to see if it exists and returns the child option if it does not.

If parentVal exists, extend is used to blend its properties into ret. If childVal also exists, extend is used to blend its properties into RET, so if the parent and child options have the same key, So the child option overwrites the parent option.

These are the common merge strategies for the four attributes of props, Methods, Inject, and computed.

Merge strategy with option provide

The merge policy for the last option, which is the provide option, is as follows:

strats.provide = mergeDataOrFn
Copy the code

That is, the merge strategy for the provide option is the same as the merge strategy for the data option, using the mergeDataOrFn function.

Option processing summary

Now that we know how processing options are combined in Vue, let’s summarize a bit:

  • Use the default merge policy defaultStrat for the EL, propsData options.
  • For the data option, the mergeDataOrFn function is used to process it, and the end result is that the data option becomes a function that executes as a real data object.
  • For the lifecycle hook options, an array is merged so that both parent and child hook functions can be executed
  • For resource options such as directives, filters, and components, parent-child options will be handled as prototype chains so that we can use built-in components, directives, and so on everywhere. Directives
  • For the watch option merge, similar to the lifecycle hook, if both parent and child options have the same observation field, it is merged into an array so that both observers are executed.
  • For props, Methods, Inject, and computed options, the parent option is always available, but the child option overrides the parent option field of the same name.
  • For the provide option, its merge strategy uses the same mergeDataOrFn function as for the data option.
  • Finally, any options not mentioned above will make the default option defaultStrat.
  • Finally, the default merge policy function defaultStrat uses the child option as long as the child option is not undefined, otherwise the parent option is used.

Look at mixins and extends

In the section on normalizing Vue options, we looked at the following code in the mergeOptions function:

const extendsFrom = child.extends
if (extendsFrom) {
  parent = mergeOptions(parent, extendsFrom, vm)
}
if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}
Copy the code

We didn’t go into that at the time because we didn’t know what the mergeOptions function did, but now we can go back and look at the code.

We know that mixins are used in Vue to address code reuse issues, such as mixing in an Created lifecycle hook that prints a sentence:

const consoleMixin = {
  created () {
    console.log('created:mixins')
  }
}

new Vue ({
  mixins: [consoleMixin],
  created () {
    console.log('created:instance')
  }
})
Copy the code

Run the above code to print two sentences:

// created:mixins
// created:instance
Copy the code

This is because mergeOptions recursively calls the mergeOptions function when processing mixins to merge mixins into the parent and use the new object as the new parent:

if (child.mixins) {
  for (let i = 0, l = child.mixins.length; i < l; i++) {
    parent = mergeOptions(parent, child.mixins[i], vm)
  }
}
Copy the code

In the example above we only dealt with merges of Created lifecycle hooks, so we will use merges strategy functions for lifecycle hooks. Now we know that mergeOptions merges lifecycle options into an array, so all lifecycle hooks will be executed. So not just lifecycle hooks, but any options written into mixins are handled using the corresponding merge strategy in mergeOptions, which is how mixins are implemented.

The extends option, as with mixins, is even simpler than the implementation of mixins, since the extends option can only be an object, not an array, and does not even require traversal.

Why can we access data in data in this way in a project

const vm = new Vue({
	el: '#app',
	data: {
		name: 'Li lei'
	}
})

console.log(vm.name) // Li lei
Copy the code

This is basically how you can access data like this

function initData (vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} // proxy data on instance const keys = Object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.NODE_ENV ! == 'production') { if (methods && hasOwn(methods, key)) { warn( `Method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasOwn(props, key)) { process.env.NODE_ENV ! == 'production' && warn( `The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm ) } else if (! IsReserved (key)) {proxy (vm, ` _data while forming `, key) / / this is the key}}} const sharedPropertyDefinition = {enumerable: // The implementation of this/ VM, which works without any additional information, provides a different control system, which works without any additional information, and controls any different elements. name -> Li lei) export function proxy (target: Object, sourceKey: string, key: string) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] } sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val } Object.defineProperty(target, key, sharedPropertyDefinition) }Copy the code

How to avoid duplicate collection of dependencies in Vue

<div id="demo"> <p>{{name}}</p> </div> { attrs:{ "id": "demo" } }, [_v("\n "+_s(name)+"\n ")] ) } } // Watcher this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = New Set() // when _init calls $mounte(), it calls -> mountComponent defined in lifecycle. Call this.value = this.lazy? Undefined: this.get() // call this.get(); get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { if (this.deep) { traverse(value) } popTarget() This.cleanupdeps ()} return value} // Call the top get(), UpdateComponent = () => {vm._update(vm._render(), hydrating)} {vm._update(vm._render(), hydrating)}} PutTarget (this) // dep.js export function pushTarget ?Watcher) { targetStack.push(target) Dep.target = target } export function popTarget () { targetStack.pop() Dep.target =  targetStack[targetStack.length - 1] } depend () { if (Dep.target) { Dep.target.addDep(this) } } // observer -> index.js  -> defineReactive get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } }} return value}, this is a get method for a property in data, which calls dep.depend(); Dep.target.adddep (this) dep.target refers to the current Watcher // dep.js Depend () {if (dep.target) { Dep.target.addDep(this) } } // watcher.js addDep (dep: Dep) { const id = dep.id if (! this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (! Has (id)) {depids. has(id)) {depsub (this)}} 1, the newDepIds attribute is used to avoid collecting duplicate observers in a single evaluation 3. DepIds is used to avoid repeated evaluation by collecting duplicate observers. From the above three points, we can conclude that: That is, the values of newDepIds and newDeps always store the Dep instance objects collected during the second evaluation, while the values of depIds and DEps always store the Dep instance objects collected during the last evaluation. In addition to the above three points, the deps attribute can also be used to remove discarded observers. The while loop at the beginning of the cleanupDeps method is used to do this, as shown below:  cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (! This.newdepids. has(dep.id)) {dep.removesub (this)}} // omit... } This while loop iterates through the DEPS array, that is, iterates through the Dep objects collected in the last evaluation, and then checks inside the loop whether the Dep instances collected in the last evaluation exist in the Dep instances collected in the current evaluation. If it does not exist, the Dep instance is no longer dependent on the observer. In this case, the dep.removesub (this) method is called and passed as an argument to remove the observer from the Dep instanceCopy the code