~preface~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
The virtual DOM, dom-diff, is a question that interviewers often ask. The popular React and Vue design principles also use Virtual DOM and DOM-diff. Even the new React Fiber can be seen. The use of Virtual DOM brings cross-platform capabilities to both frameworks (React-Native, React VR and Weex) and SSR implementation. React/diff react/diff react/diff react/diff react/Diff react
* The article is longer, we patience to view, should write more than the column words ~~~~~o(╥﹏╥) O *
Before you read the article, here are a few questions to see if you can answer them. These are also questions that interviewers often ask these days. If you don’t know, it’s okThe problemLet’s see ~
- 1. What is vDOM (Virtual DOM)? Why does vDOM exist?
- 2. How to apply VDOM and what is the core API?
- 3. Is there a necessary relationship between VDOM and JSX?
- 4. Introduce the DIff algorithm.
- 5. Simple implementation of diFF principle (core)
** The contents of this article include:
- 1. Introduction to Virtual DOM (VDOM)
- 2. Briefly describe the implementation process and core API of Virtual DOM (VDOM)
- 3. Simulate the implementation of VDOM (Virtual DOM) (excluding diFF part)
- 4. Principle of diFF algorithm implementation process (key and difficult points !!!!!!)
- 5. Simulate the implementation of preliminary DIFF algorithm
- 6, application: simulate vDOM (Virtual DOM) in
react
The implementation in - 7, application: simulate the DIFF algorithm in VDOM (Virtual DOM)
react
The implementation of the - 8, summary
- 9. Explanation and annotation of key knowledge (It’s also a question that interviewers often ask)
Dom – Diff algorithm is the core, focus and difficulty of virtual DOM.
The body of the~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Why do you want to know about the virtual DOM? Why do react and Vue both use the virtual DOM? How does that happen? Is there any optimization for performance? Why is the virtual DOM dead? What can we learn about the virtual DOM?
What about the beginning? React/Vue virtual DOM/DOM-diff algorithm: React/Vue virtual DOM/DOM-DIff algorithm
1. Introduce the Virtual DOM
The Virtual DOM is an abstraction of the DOM, essentially a JavaScript object, which is a more lightweight representation of the DOM and improves redraw performance.
1.1) What is dom?
DOM stands for Document Object Model. It is an interface for JavaScript to manipulate web pages. What it does is turn the web page into a JavaScript object that you can script for various operations (such as adding and deleting content).
Case study:
Real dom :(code 1.1)
<ul id='list'>
<li class='item'>itemA</li>
<li class='item'>itemB</li>
</ul>
Copy the code
And when we get it in JS, we use the code
let ulDom = document.getElementById('list');
console.log(ulDom);
Copy the code
1.2) What is the virtual DOM
The Virtual DOM is an abstraction of the DOM, essentially a JavaScript object, which is a more lightweight description of the DOM. Vdom for short.
For example: DOM (code 1.1)
<ul id='list'>
<li class='item'>itemA</li>
<li class='item'>itemB</li>
</ul>
Copy the code
The virtual DOM is: code: 1.2 (compare the actual DOM tree structure in case 1.1 above to implement the following JS object)
{
tag:'ul'.// The label type of the element
attrs:{ // Specifies an attribute on the element
id:'list'
},
children: [// Child node of ul element
{
tag: 'li'.attrs: {className:'item'
},
children: ['itemA'] {},tag: 'li'.attrs: {className:'item'
},
children: ['itemB']]}}Copy the code
Virtual DOM object (code: 1.2)
- Tag: Specifies the tag type of the element, in this case: ‘ul’ (react type)
- Attrs: props for specifying attributes on an element, such as id,class, style, and custom attributes.
- Children: specifies whether the element has child nodes. The argument is passed in as an array, or as a string if it is text
1.3) Why does the virtual DOM exist?
Since we already have the DOM, why do we need an extra layer of abstraction?
- First of all, we all know that
** Front-end performance optimization **
One of the secrets is to manipulate the DOM as little as possible, not only because DOM is relatively slow, but also because frequent DOM changes can cause browser backflow or redraw (Redraw and Reflux section: 9.1), these are performance killers, so we need this layer of abstraction to update the differences into the DOM as much as possible at once during patch to ensure that the DOM doesn’t suffer from poor performance. - Second, is a basic requirement of the modern front frame does not need to manually DOM, on the one hand because of manual DOM cannot guarantee program performance, collaboration projects if you don’t strict review, there may be a developer write performance low code, on the other hand, more important is to omit the manual DOM manipulation can greatly improve the development efficiency.
- Opens the door to functional UI programming,
- Finally, and the original purpose of Virtual DOM, is better cross-platform, for example, Node.js does not have DOM, if you want to achieve SSR(server-side rendering), then one way is to use Virtual DOM, because Virtual DOM itself is a JavaScript object. In ReactNative, React VR and WEEX all use virtual DOM.
Why is DOM manipulation “expensive” and JS efficient?
For example, we just create a simple div element on the page, print it out, and we can see it in the output
(Code: 1.3)
var div = document.createElement(div);
var str = ' ';
for(var key in div){
str += key+' ';
}
console.log(str)
Copy the code
(figure 1.1)
As Figure 1.1 shows, the actual DOM elements are quite large, because browser standards make DOM very complex. There are performance issues when frequent DOM updates cause page rearrangements.
To better understand the virtual DOM, you need to understand how the browser works (Browser runtime: 9.1).
1.4) Disadvantages of the virtual DOM
- When rendering a large number of DOM for the first time, it is slower than innerHTML insertion due to the extra layer of virtual DOM computation. The virtual DOM requires maintaining a copy of the DOM in memory.
- This is appropriate if your scene is a virtual DOM with a lot of changes. But with a single, frequent update, the virtual DOM will take more time to compute. For example, if you have a page with relatively few DOM nodes, using the virtual DOM, it might actually be slower. But for most single-page applications, this should be faster. This is why updates in React and Vue are made asynchronously, with frequent updates only being made the last time.
1.5) Summary:
-
The virtual DOM is a JS object
-
DOM manipulation is “expensive” and JS is efficient
-
Minimize DOM manipulation, not “push to redo”
-
The more complex the project, the more serious the impact
-
Better cross-platform
—————— vDOM can solve these problems ————————
2. Briefly describe the implementation process and core API of VDOM
React and VUE both use the virtual DOM technology to varying degrees, so it is necessary to learn the virtual DOM technology through a simple library in both shallow and deep understanding
Why snabbdom.js? There are two main reasons:
- Source brief.
- The popular Vue framework’s virtual DOM implementation also references the snabbdom.js implementation. The React virtual DOM is similar.
Let’s use the SnabbDOM library to explain:
API: snabbdom: github.com/snabbdom/sn…
You can also check out the library virtual-dom: github.com/Matt-Esch/v…
2.1 Virtual DOM implementation case of snabbdom.js
If we want to implement a virtual DOM ourselves, we can deal with the following three core issues in the implementation process of the snabbdom.js library:
- Compile, how to compile the real DOM into a vNode virtual node object. (By h function)
- Diff, algorithmatically, how do we know what’s changed between oldVnode and newVnode. (Internal diff algorithm)
- Patch, if these changes are patched to the real DOM.
Let’s see, it’s your snabbdom case
Let’s use the SNabbDOM library to see what happens.
Step 1: Create an HTML file (demo.html) with an empty div tag with the ID of Container
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="container"></div>
</body>
</html>
Copy the code
Step 2: SnabbDOM library is introduced. The content of this article is introduced in the form of CDN. Due to the introduction of multiple JS, it is necessary to pay attention to the consistency of versions
<! -- SnabbDOM -- snabbDOM -- snabbDOM<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script><! -- SnabbDOM -- snabbDOM -- snabbDOMCopy the code
Step 3: Initialize and define h function
<script>
let snabbdom = window.snabbdom;
/ / define h
let h = snabbdom.h;
The h function returns the virtual node
let vnode = h('ul', {id:'list'},[
h('li', {'className':'item'},'itemA'),
h('li', {'className':'item'},'itemB')]);console.log('h returns the virtual DOM as',vnode);
</script>
Copy the code
(figure 2.2.1)
We can see from the above code:
H function accepts three parameters, respectively representing the label name, attribute and child node of DOM element (children have multiple child nodes), and finally returns an object of virtual DOM. I can see that key (the unique identifier of the node) and text (if it is a text node) are also returned in the virtual node.
Step 4: Define patch and update VNode
/ / define the patch
let patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
]);
// Get the DOM of the container
let container = document.getElementById('container');
// First patch
patch(container,vnode);
Copy the code
We are running the browser as shown below:
(figure 2.2.2)
Ps: We can see from Figure 2.2.2 that the rendering is successful. It should be noted that vNode overwrites the original real DOM during the first patch.
Render adds child nodes to the DOM
Step 5: Add button. Click trigger event to trigger the second patch method
<button id="btn-change">Change</button>
Copy the code
1) If our new node (virtual node) is not changed,
// Add an event to trigger the second patch
let btn = document.getElementById('btn-change');
document.addEventListener('click'.function (params) {
let newVnode = h('ul#list',{},[
h('li.item', {},'itemA'),
h('li.item', {},'itemB')]);// Second patch
patch(vnode,newVnode);
});
Copy the code
Since the structure of vNode and newVnode is the same, we look at the browser and click on the event and find no render
2) Let’s change newVnode
document.addEventListener('click'.function (params) {
let newVnode = h('ul#list',{},[
h('li.item', {},'itemC'),
h('li.item', {},'itemB'),
h('li.item', {},'itemD')]);// Second patch
patch(vnode,newVnode);
});
Copy the code
(figure 2.2.3)
The entire demo.html code is as follows :(code 2.3.1)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change">Change</button>
<! -- SnabbDOM -- snabbDOM -- snabbDOM
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>
<! -- SnabbDOM -- snabbDOM -- snabbDOM
<script>
let snabbdom = window.snabbdom;
/ / define h
let h = snabbdom.h;
The h function returns the virtual node
let vnode = h('ul#list',{},[
h('li.item', {},'itemA'),
h('li.item', {},'itemB')]);console.log('h returns the virtual DOM as',vnode);
/ / define the patch
let patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
]);
// Get the DOM of the container
let container = document.getElementById('container');
// First patch
patch(container,vnode);
// Add an event to trigger the second patch
let btn = document.getElementById('btn-change');
/ / newVnode changes
document.addEventListener('click'.function (params) {
let newVnode = h('ul#list',{},[
h('li.item', {},'itemC'),
h('li.item', {},'itemB'),
h('li.item', {},'itemD')]);// Second patch
patch(vnode,newVnode);
});
</script>
</body>
</html>
Copy the code
2.2 Initial virtual Dom case effects in React
Don’t understand the React can go to check the website address: facebook. Making. IO/React/docs /…
React uses the JSX syntax and, unlike SNabbDOM, converts code through Babel first. In addition, mainly
Example: 2.2.1 DOM Tree definition
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<h1>hello,lee</h1>, document.getElementById('root'));
Copy the code
When we look at example 2.2.1, we see that react is introduced.
hello,lee
(JSX syntax 9.2); React.createElement(“h1”, null, “hello,lee”);
(2.3.1)
Tree with child nodes
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<ul id="list">
<li class="item">itemA</li>
<li class="item">itemB</li>
</ul>,
document.getElementById('root'));
Copy the code
The compiled
React.createElement("ul", {
id: "list"
}, React.createElement("li", {
class: "item"
}, "itemA"), React.createElement("li", {
class: "item"
}, "itemB"));
Copy the code
Ps:
react.js
React is the core libraryreact-dom.js
The internally important method is Render, which is used to insert DOM elements into the browser
Example: 2.2.2 Functional components
import React from 'react';
import ReactDOM from 'react-dom';
function Welcome(props){
return (
<h1>hello ,{props.name}</h1>
)
}
ReactDOM.render( <Welcome name='lee' /> , document.getElementById('root'));
Copy the code
Welcome is the function component. The function component receives a single props object and returns a React element.
function Welcome(props) {
return React.createElement("h1", null, "hello ,", props.name);
}
Copy the code
Example: Class 2.2.3 components
import React from 'react';
import ReactDOM from 'react-dom';
class Welcome1 extends React.Component{
render(){
return (
<h1>hello ,{this.props.name}</h1>
)
}
}
ReactDOM.render( < Welcome1 name = 'lee' / > , document.getElementById('root'));
Copy the code
Welcome1 is a class component compiled to return the following:
class Welcome1 extends React.Component { render() { return React.createElement("h1", null, "hello ,", this.props.name); }}Copy the code
This is JSX syntax (JSX Tutorial: 9.2)
Example: 2.2.4 Text
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render( '<h1>hello,lee</h1>' , document.getElementById('root'));
Copy the code
‘< H1 >hello, Lee ‘ is inserted as text into the page.
Conclusion:
The react.createElement () function is similar to the h function in snabbdom.
H function: [type, attribute, child node] three arguments the last one in the array if there is a child node;
React.createelement (): The first argument is also a type, the second argument represents the property value, and the third and subsequent arguments represent child nodes.
Reactdom.render () : similar to the patch() function, but with the argument order reversed.
Reactdom.render (): The first argument is vnode, the second is the real DOM to hang on;
Patch () : first argument vnode virtual dom, or real DOM, second argument vnode;
Ps: Note:
-
React elements can be DOM tags as well as user-defined components
-
When the React element is a user-defined component, it converts attributes received by JSX to a single object passed to the component, which is called props
-
Component names must begin with a capital letter
-
A component must define or reference it when it is used
-
The return value of a component can have only one root element
-
Pay attention to when the render ()
1, need to pay attention to special handling of some attributes, such as: style, class, event, children, etc
2. Define component time zone classification component and function component and label component
3. Simulate the implementation of VDOM
We’ll create a new project here to implement it. In order to start the service, we’ll use Webpack to package it, and we’ll start webpack-dev-server.
3.1 Set up the development environment and initialize the project
First step: create an empty folder lee-vdom, in the initialization project: NPM init -y, if you let git upload the best to create an ignore file to ignore some unnecessary files. Gitignore
Step 2: Install the dependency packages
npm i webpack webpack-cli webpack-dev-server -D
Copy the code
Step 3: Configure the scripts part of package.json
"scripts": {
"build": "webpack --mode=development",
"dev": "webpack-dev-server --mode=development --contentBase=./dist"
},
Copy the code
Step 4: Create a SRC directory in the root directory of the project. Create an index.js file in the SRC directory (ps: webpack default entry file is index.js in the SRC directory, and the default output directory is dist in the root directory of the project).
We can input the test file output in index.js
console.log("Test vdom SRC /index.js")
Copy the code
Step 5: In the dist directory, we create a new index.html file and import the output main.js
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vdom dom-diff</title>
</head>
<body>
<div id="app"></div>
<script src="./main.js"></script>
</body>
</html>
Copy the code
Step 6: Execute NPM run dev to start the project, and type http://localhost:8080 in the browser. The browser console output the test vdom SRC /index.js, indicating that the project has been successfully initialized.
3.2 Virtual DOM implementation
According to the above code in 2.3.1, we found the core API: H function and patch function. The main content of this section is: Handwriting can generally generate the H function of the virtual node and the patch function of the first rendering.
Recall the code as shown in Figure 2.2.1
(figure 2.2.1)
h('< tag name >', {... Attribute... },[... child element...] // Generate vDOM node h('< tag name >', {... Attribute... },'Text node'(container,vnode) //render //Copy the code
Step 1: add SRC \vdom\index.js unified export
We will write these methods to the vDOM directory under SRC and export them through SRC \vdom\index.js.
import h from './h'; // Unify the exit of the exposed virtual DOM implementationexport {
h
}
Copy the code
Step 2: Create SRC \ vDOM \h.js
(FIG. 3.2.1)
- Create the h function method and return the virtual DOM object as shown in Figure 3.2.1.
- Parameter passed in
H ('< tag name >',{... Attribute... },[... child element...] )
// Generate a vDOM nodeH ('< tag name >',{... Attribute... },' text node ')
- Note that the key is separated from the attribute, which is unique and undefined if not
From Figure 3.2.1, we can use the h function method to return parameters of the virtual DOM object:
sel,data,children,key,text,elm
H.js Preliminary code:
import vnode from './vnode';
/** * @param {String} sel 'div' tag name can be an element selector, see jq * @param {Object} data {'style': {background:'red'}} The corresponding Vnode binding data attribute set includes attribute, eventListener, props, @param {Array} children [h(), 'text'] child element set * text Current text text itemA * elm corresponding to the real DOM element reference * key only used for comparison between different VNodes */
function h(sel, data, ... children) {
return vnode(sel,data,children,key,text,elm);
}
export default h;
Copy the code
Step 3: create SRC \vdom\vnode.js
// Use symbol to ensure the uniqueness of vnode
const VNODE_TYPE = Symbol('virtual-node')
/** * @param {String} sel 'div' tag name can be an element selector, see jq * @param {Object} data {'style': {background:'red'}} The corresponding Vnode binding data attribute set includes attribute, eventListener, props, @param {Array} children [h(), 'text'] child Element set * @param {String} text Current text text itemA * @param {Element} elm corresponding to the real DOM Element reference * @param {String} key unique * @return {Object} vnode */
function vnode(sel, data = {}, children, key, text, elm) {
return {
_type: VNODE_TYPE,
sel,
data,
children,
key,
text,
elm
}
}
export default vnode;
Copy the code
Ps: Code understanding note:
The value of the built-in _type is symbol (symbol: 9.3). When using the uniqueness of symbol to verify vnode, determine whether a virtual node is a basis.
The children/text of vnode cannot co-exist. For example, we are still writing h(‘li’,{},’itemeA’). We know that itemA is passed as children to h, but it is text. This is just for convenience, text nodes are handled differently than other types of nodes. H (p, 123) – > 123 < / p > < p > such as h (p, ‘[h (” h1 “, 123),’ 222 ‘]) – > < p > < h1 > 123 < / h1 > 222 < / p >
- Text indicates that the vnode is actually a VTextNode, but snabbdom does not distinguish between vnodes.
elm
It is used to save DOM nodes corresponding to vnodes.
Step 4: Perfect H.JS
The main job of the /** * h function is to encapsulate the passed argument as vnode */
import vnode from './vnode';
import {
hasValidKey,
isPrimitive, isArray
} from './utils'
const hasOwnProperty = Object.prototype.hasOwnProperty;
/** * RESERVED_PROPS Dictionary object for attributes to filter * / hasValidRef and hasValidKey methods are used to check whether config has ref and key attributes. * Config.__self and config.__source are then assigned to self and source variables, respectively, or null if they do not exist. * Omit ref, __self, __source */ in this code
const RESERVED_PROPS = {
key: true.__self: true.__source: true
}
// Add the old props back to the props object through the for in loop,
// Filter out the value of the RESERVED_PROPS attribute with a value of true
function getProps(data) {
let props = {};
const keys = Object.keys(data);
if (keys.length == 0) {
return data;
}
for (let propName in data) {
if(hasOwnProperty.call(data, propName) && ! RESERVED_PROPS[propName]) { props[propName] = data[propName] } }return props;
}
/** ** @param {String} sel selector * @param {Object} data attribute Object * @param {... Any} children collection * @returns {{sel, data, children, key, text, elm}} */
function h(sel, data, children) {
let props = {},c,text,key;
// If there are child nodes
if(children ! = =undefined) {
// // so the second argument to h is
props = data;
if (isArray(children)) {
c = children;
} else if (isPrimitive(children)) {
text = children;
}
/ / if the children
} else if(data ! =undefined) {// If there is no children, data exists, we consider that the attribute part is omitted, and data is the child node
// If it is an array, there are child nodes
if (isArray(data)) {
c = data;
} else if (isPrimitive(data)) {
text = data;
}else{ props = data; }}/ / get the key
key = hasValidKey(props) ? props.key : undefined;
props = getProps(props);
if(isArray(c)){
c.map(child= > {
return isPrimitive(child) ? vnode(undefined.undefined.undefined.undefined, child) : child
})
}
// Because children can also be a deep layer of h functions, flattening needs to be handled
return vnode(sel, props, c, key,text,undefined);
}
export default h;
Copy the code
Added help js, SRC \vdom\utils.js
/** ** some help tools public methods */
// If there is a key,
/** ** @param {Object} config Attribute Object in the virtual DOM tree */
function hasValidKey(config) {
config = config || {};
returnconfig.key ! = =undefined;
}
// if there is a ref,
/** ** @param {Object} config Attribute Object in the virtual DOM tree */
function hasValidRef(config) {
config = config || {};
returnconfig.ref ! = =undefined;
}
/* param {*} value */
function isPrimitive(value) {
const type = typeof value;
return type === 'number' || type === 'string'
}
@param {Array} arr */
function isArray(arr){
return Array.isArray(arr);
}
function isFun(fun) {
return typeof fun === 'function';
}
/** */ undefined* ** /
function isUndef(val) {
return val === undefined;
}
export {
hasValidKey,
hasValidRef,
isPrimitive,
isArray,
isUndef
}
Copy the code
Step 5: Preliminary rendering
Add patch, SRC \vdom\patch.js
// Do not consider the hook
import htmlApi from './domUtils';
import {
isArray, isPrimitive,isUndef
} from './utils';
// Generate real DOM from vDOM
function createElement(vnode) {
let {sel,data,children,text,elm} = vnode;
If there is no selector, ing is said to be a text node
if(isUndef(sel)){
elm = vnode.elm = htmlApi.createTextNode(text);
}else{
elm = vnode.elm = analysisSel(sel);
// If there is a child node, the recursive child element is inserted into elm for reference
if (isArray(children)) {
// analysisChildrenFun(children, elm);
children.forEach(c= > {
htmlApi.appendChild(elm, createElement(c))
});
} else if (isPrimitive(text)) {
// Child elements are text nodes inserted directly into the current vNode nodehtmlApi.appendChild(elm, htmlApi.createTextNode(text)); }}return vnode.elm;
}
function patch(container, vnode) {
console.log(container, vnode);
let elm = createElement( vnode);
console.log(elm);
container.appendChild(elm);
};
DivClass - > id = "divId" class = "divClass" ** @param {String} sel * @returns {Element} Element node */
function analysisSel(sel){
if(isUndef(sel)) return;
let elm;
let idx = sel.indexOf(The '#');
let selLength = sel.length;
let classIdx = sel.indexOf('. ', idx);
let idIndex = idx > 0 ? idx : selLength;
let classIndex = classIdx > 0 ? classIdx : selLength;
lettag = (idIndex ! =- 1|| classIndex ! =- 1)? sel.slice(0.Math.min(idIndex, classIndex)) : sel;
// Create a DOM node and elm reference it on the virtual DOM
elm = htmlApi.createElement(tag);
// Get id #divId -> divId
if (idIndex < classIndex) elm.id = sel.slice(idIndex + 1, classIndex);
// If sel has multiple class names such as.a.b.c -> a, b, c
if (classIdx > 0) elm.className = sel.slice(classIndex + 1).replace(/\./g.' ');
return elm;
}
// If there is a child node, the recursive child element is inserted into elm for reference
function analysisChildrenFun(children, elm) {
children.forEach(c= > {
htmlApi.appendChild(elm, createElement(c))
});
}
export default patch;
Copy the code
We can see the addition of some dom manipulation methods SRC \vdom\ domutils.js
/** DOM manipulation methods * element/node creation, deletion, judgment, etc. */
function createElement(tagName){
return document.createElement(tagName);
}
function createTextNode(text) {
return document.createTextNode(text);
}
function appendChild(node, child) {
node.appendChild(child)
}
function isElement(node) {
return node.nodeType === 1
}
function isText(node) {
return node.nodeType === 3
}
export const htmlApi = {
createElement,
createTextNode,
appendChild
}
export default htmlApi;
Copy the code
Step 6: Let’s take a quiz:
src\index.js
import { h,patch } from './vdom';
The h function returns the virtual node
let vnode = h('ul#list', {}, [
h('li.item', {}, 'itemA'),
h('li.item', {}, 'itemB')]);let container = document.getElementById('app');
patch(container, vnode);
console.log('The self-written h function returns the virtual DOM as', vnode);
Copy the code
(figure 3.2.2)
As shown in Figure 3.2.2, the initial rendering was successful.
Step 7: Work with properties
In the createElement function, we saw that we didn’t handle the data property of h. Because it’s a bit complicated, let’s look at how snabbDOM handles the data parameter.
Mainly includes several types of processing:
- Class :{active:true} we can control this variable to indicate whether this element is currently clicked
- Style: inline style
- On: binding event type
- The dataset: data attributes
- Hook: The hook function
Example:
vnode = h('div#divId.red', { 'class': { 'active': true }, 'style': { 'color': 'red' }, 'on': { 'click': ClickFn}}, [h('p', {}, 'text ')]) function clickFn() {console.log(click')} vnode = patch(app, vnode);Copy the code
New: SRC \ vdom \ updataAttrUtils
import {
isArray
} from './utils'
/** * Update the style attribute ** @param {Object} vnode new virtual DOM node Object * @param {Object} oldStyle * @returns */
function undateStyle(vnode, oldStyle = {}) {
let doElement = vnode.elm;
let newStyle = vnode.data.style || {};
/ / delete the style
for(let oldAttr in oldStyle){
if(! newStyle[oldAttr]) { doElement.style[oldAttr] =' '; }}for(let newAttr innewStyle){ doElement.style[newAttr] = newStyle[newAttr]; }}function filterKeys(obj) {
return Object.keys(obj).filter(k= > {
returnk ! = ='style'&& k ! = ='id'&& k ! = ='class'})}/** * Update the props property * to support vNode to use props to operate other properties. * @param {Object} vnode new virtual DOM Object * @param {Object} oldProps * @returns */
function undateProps(vnode, oldProps = {}) {
let doElement = vnode.elm;
let props = vnode.data.props || {};
filterKeys(oldProps).forEach(key= > {
if(! props[key]) {delete doElement[key];
}
})
filterKeys(props).forEach(key= > {
let old = oldProps[key];
let cur = props[key];
if(old ! == cur && (key ! = ='value'|| doElement[key] ! == cur)) { doElement[key] = cur; }})}/** * Update the className attribute class * in HTML to support vNode to use props to manipulate other properties. * @param {Object} Vnode new virtual DOM node Object * @param {*} oldName * @returns */
function updateClassName(vnode, oldName) {
let doElement = vnode.elm;
const newName = vnode.data.className;
if(! oldName && ! newName)return
if (oldName === newName) return
if (typeof newName === 'string' && newName) {
doElement.className = newName.toString()
} else if (isArray(newName)) {
let oldList = [...doElement.classList];
oldList.forEach(c= > {
if(! newName.indexOf(c)) { doElement.classList.remove(c); } }) newName.forEach(v= > {
doElement.classList.add(v)
})
} else {
// Set className to '' for all invalid or null values.
doElement.className = ' '}}function initCreateAttr(vnode) {
updateClassName(vnode);
undateProps(vnode);
undateStyle(vnode);
}
export const styleApis = {
undateStyle,
undateProps,
updateClassName,
initCreateAttr
};
export default styleApis;
Copy the code
Add method to patch.js:
. import attr from'./updataAttrUtils'
functioncreateElement(vnode) { .... attr.initCreateAttr(vnode); . }...Copy the code
Add the test code to SRC \index.js:
import { h,patch } from './vdom';
The h function returns the virtual node
let vnode = h('ul#list', {}, [
h('li.item', {style: {'color':'red'}}, 'itemA'),
h('li.item.c1', {
className: ['c1'.'c2']},'itemB'),
h('input', {
props: {
type: 'radio'.name: 'test'.value: '0'.className:'inputClass'}})]);let container = document.getElementById('app');
patch(container, vnode);
Copy the code
(figure 3.3.1)
4. Diff algorithm implementation process principle
Diff is an algorithm for comparing differences
4.1 What is the DIff algorithm
Git diff(github, gitlab…) git diff(github, gitlab…) git diff And so on.
4.2 Why does VDOM use diFF algorithm
In the case where snabbdom.js is used above, Patch (vnode,newVnode) uses this diff algorithm to determine whether there is a change between two virtual DOM. If there is no change, there is no need to render to the real DOM tree, saving performance.
Vdom uses the diff algorithm to find the nodes that need to be updated. Vdom use diff algorithm to compare the difference of two virtual dom, with minimal cost than 2 tree, on the basis of the previous tree generates minimum operation tree, but the time complexity of this algorithm to the third power of n = O (NNN), when the nodes of the tree is large, the algorithm of time cost leads to algorithm can hardly work.
4.3 Implementation rules of DIFF algorithm
Diff algorithm is the difference calculation, record the difference
4.3.1 Comparison of nodes at the same level should not be made across levels
(the picture found on the Internet), as shown below
(FIG. 4.3.1)
4.3.2 In-depth optimization of prior order and priority of breadth
1. Depth first
(FIG. 4.3.2)
2. Breadth first
Start at a vertex, visit the vertex first, then find all unvisited adjacent points of the node, then visit all the nodes of the first adjacent point of these nodes, repeat this method until all nodes are accessed.
4.4 Principle flow of DOM-DIff implementation in SNabbDOM and VUE (key!!)
4.4.1 Before comparison, we found that the same function patch is used to operate snabbDOM, so we need to judge. The first argument is passed as a virtual DOM or an HTML element.
4.4.2, when looking at the source code, IT is found that SNabbDOM will convert HTML elements into virtual DOM and continue to operate. This is for the convenience of the later update, after the update is mounted.
4.4.3 Determine whether it is the same node by method
Methods: Compare sel key of new node (newVnode) and sel key of oldVnode (oldVnode). If it is different (such as sel changing from ul to P), replace the oldVnodedom element directly with the DOM element via newVnode. As explained in 4.3.1, dom-diff splits the tree by level, only comparing with the same level, and does not move vNodes across layers. They’re not comparing their children. If not, compare the differences in detail and ‘patch’ on the old VNode.
(FIG. 4.4.3)
Ps: In fact, when using VUE, there is no need to define the key value under the condition that there is no V-for rendering component, and it does not affect the comparison.
4.4.4 Update data attribute
Loop the data and attribute of the old node, delete it if the data does not exist with the new node, and finally on the ELM where all the new nodes are added to the old node;
Style, class, and props are required to exclude key\ ID, because key is used for diff comparison, and id is used when there is no key, both of which have current indexes.
The code implementation can be seen in —- 5.2
4.4.5 Comparison of children (core focus)
4.4.5.1 Children of the new node is a text node and the text of OldvNode is different from that of Vnode, so it is updated to the text of Vnode
4.4.5.2 Judge whether only one party has children,
If the old node has children and the new node does not, delete the old node children directly
Ii. If the children of the old node does not exist and the children of the new node does, the dom of the children of the new node is directly referenced to the children of the old node.
4.4.5.3 Compare the old and new VNodes in two arrays (most difficult)
In order to facilitate the understanding of the following we will be the new and old node two arrays to illustrate the implementation process. Using a double-pointer method, head and tail simultaneously start scanning;
Repeat the following five comparisons until the head pointer (the starting index) exceeds the tail pointer (the ending index) in either array, and the loop ends:
OldStartIdx: array start index of the old node, oldEndIdx: array end index of the old node, newStartIdx: array start index of the new node newEndIdx: array end index of the new node oldStartVnode: Old start node oldEndVnode: old end node newStartVnode: new start node newEndVnode: The new end node loops through two arrays with (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx)Copy the code
(figure 4.4.5.1)
Head to tail comparison
1OldStartVnode - > newStartVnode2OldEndVnode - > newEndVnode3OldEndVnode - > newStartVnode4OldStartVnode - > newEndVnode5, using key comparisonCopy the code
Situation 1: Head comparison:
To determine whether oldStartVnode, newStartVnode same vnode: : patch (oldStartVnode, newChildren [newStartIdx]);
, and a + + + + oldStartIdx oldStartIdx,
OldStartVnode = oldChildren[oldStartIdx], newStartVnode = newChildren[oldStartIdx];
Optimized for some DOM operations: adding or subtracting nodes at the end;
Example 1: ABCD =>ABCDE ABCD =>ABC
At the beginning:
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:0OldStartVnode: A virtual DOM oldEndIdx:3OldEndVnode :D virtual dom newChildren:['A'.'B'.'C'.'D'.'E']
newStartIdx:0NewStartVnode :A virtual DOM newEndIdx:4Virtual dom newEndVnode: ECopy the code
(figure 4.4.5.2)
After the comparison,
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:4
oldStartVnode: undefined
oldEndIdx:3OldEndVnode :D virtual dom newChildren:['A'.'B'.'C'.'D'.'E']
newStartIdx:4NewStartVnode :D Dom newEndIdx:4Virtual dom newEndVnode: ECopy the code
NewStartIndex <= newEndIndex: indicates that after the loop comparison, the new node still has data, at this time, the creation of these virtual nodes is really new DOM reference to the elm of the old virtual DOM, and the new location is oldStartVnode of the old node, that is, the end;
NewStartIndex > newEndIndex: indicates that all newChildren have been compared and no action is required.
OldStartIdx >oldEndIdx: indicates that oldChildren are all compared and do not need to be processed;
OldStartIdx <= oldEndIdx: indicates that the old nodes still have data after the loop comparison. In this case, the real DOM of these virtual nodes should be deleted.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — – the implementation of the code to view 5.3.3
Case 2: Tail to tail comparison:
Check whether oldEndVnode and newEndVnode are the same vnode:
Patch (oldEndVnode, newEndVnode);
– oldEndIdx – newEndIdx;
OldEndVnode = oldChildren [oldEndIdx]; NewEndVnode = newChildren [newEndIdx],
Optimized for some DOM operations: adding or removing nodes in the header;
Example 2: ABCD =>EFABCD ABCD => BCD
At the beginning:
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:0OldStartVnode: A virtual DOM oldEndIdx:3OldEndVnode :D virtual dom newChildren:['E'.'A'.'B'.'C'.'D']
newStartIdx:0NewStartVnode :E newEndIdx:4Virtual dom newEndVnode: DCopy the code
(figure 4.4.5.3)
After the comparison,
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:0OldStartVnode: A virtual DOM oldEndIdx:- 1
oldEndVnode: undefined
newChildren:['E'.'A'.'B'.'C'.'D']
newStartIdx:0NewStartVnode :E newEndIdx:1NewEndVnode: A virtual domCopy the code
Situation 3. Comparison of old tail and new head:
Check whether oldStartVnode compares vNodes with newEndVnode.
Patch (oldStartVnode, newEndVnode);
Move the old oldStartVnode after newEndVnode,
+ + oldStartIdx;
– newEndIdx;
OldStartVnode = oldChildren [oldStartIdx];
newEndVnode = newChildren[newEndIdx];
** is optimized for some DOM operations: ** adds or subsides nodes in the header;
Example 3: ABCD => BCDA
At the beginning:
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:0OldStartVnode: A virtual DOM oldEndIdx:3OldEndVnode :D virtual dom newChildren:['B'.'C'.'D'.'A']
newStartIdx:0NewStartVnode :B newEndIdx:3NewEndVnode: A virtual domCopy the code
(figure 4.4.5.4)
['A'.'B'.'C'.'D'] - > ['B'.'C'.'D'.'A'] 1: old [0] - > new [0] 2: old [3] - > [3] new 3: old [0] - > new equal [3] Move old [0]. Elm to old [3]. The elm + + oldStartIdx after; --newEndIdx; Move the index pointer to compare 4: old [1] -> new [0] equal, 5: old [2] -> new [1] equal, 6: old [3] -> new [2] equalCopy the code
After the comparison,
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:4
oldStartVnode: undefined
oldEndIdx:3OldEndVnode :D virtual dom newChildren:['B'.'C'.'D'.'A']
newStartIdx:3NewStartVnode :A virtual DOM newEndIdx:2Virtual dom newEndVnode: DCopy the code
Situation 4. The old man and the new tail
Compare the old end node oldEndVnode with the new start node newStartVnode. Are vnodes the same?
Patch (oldEndVnode, newStartVnode);
Move the old oldEndVnode to the front of oldStartVnode,
+ + newStartIdx;
– oldEndIdx;
OldEndVnode = oldChildren [oldStartIdx];
newStartVnode = newChildren[newStartIdx];
** is optimized for some DOM operations: ** moves the head at the tail node;
Example 4: ABCD => DABC
At the beginning:
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:0OldStartVnode: A virtual DOM oldEndIdx:3OldEndVnode :D virtual dom newChildren:['D'.'A'.'B'.'C']
newStartIdx:0NewStartVnode :B newEndIdx:3NewEndVnode: A virtual domCopy the code
Process:
(figure 4.4.5.5)
['A'.'B'.'C'.'D'] - > ['D'.'A'.'B'.'C']
1: old [0] - > [new0] ranging2: old [3] - > [new3] ranging3: old [0] - > [new3] ranging4: old [3] - > [new0] equal, move old [3Old []. Elm to0+ + newStartIdx before]. Elm; --oldEndIdx; Move the index pointer to compare and the following are all compared according to case one5: old [2] - > [new3Equal]6: old [1] - > [new2Equal]7: old [0] - > [new1Equal]Copy the code
After the comparison,
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:3OldStartVnode: D virtual DOM oldEndIdx:2OldEndVnode :C virtual dom newChildren:['B'.'C'.'D'.'A']
newStartIdx:4
newStartVnode: undefined
newEndIdx:3NewEndVnode: A virtual domCopy the code
Case 5. Use keys for comparison
OldKeyToIdx: map of the oldChildren key and its corresponding index
oldChildren = [{key:'A'}, {key:'B'}, {key:'C'}, {key:'D'}, {key:'E'}];
oldKeyToIdx = {'A':0.'B':1.'C':2.'D':3.'E':4}
Copy the code
The oldChildren index map of the old key is used to map the new node’s key into the oldChildren index map.
Implementation principle and process:
1. OldKeyToIdx does not need to be newly created
2. Save the index of newStartVNode. key in oldKeyToIdx
3. The index exists, the new start node has the key in the old node, and the sel is equal to the oldChildren[oldIdxByKeyMap], which means it is similar to vnode, patch, and the old node is set to undefined. Move oldChildren[oldIdxByKeyMap]. Elm to before oldStartVnode
If the index does not exist, newStartVnode is a new Vnode. Create the dom and insert oldStartvNode. elm
++newStartIdx;
newStartVnode = newChildren[newStartIdx];
Case description:
There are possible reasons
1. The node (the new node to be compared) is newly created.
2, The current node (the new node to compare) is in the middle of the original position (oldStartIdx and oldEndIdx)
Example 5: ABCD -> EBADF
oldChildren:['A'.'B'.'C'.'D']
oldStartIdx:0OldStartVnode: A virtual DOM oldEndIdx:3OldEndVnode :D virtual dom newChildren:['E'.'B'.'A'.'D'.'F']
newStartIdx:0NewStartVnode :E newEndIdx:4Virtual dom newEndVnode: DCopy the code
Compare the process
1.
(figure 4.4.5.6-1)
Explanation:
OldKeyToIdx = {oldKeyToIdx= {oldKeyToIdx= {'A': 0.'B': 1,'C': 2.'D':3} NewStartvNode. key E does not exist in oldKeyToIdx, indicating that E is a new node to be created, and the DOM is inserted before oldEndvNode. elm. NewStartVnode = newChildren[++newStartIdx] -> BCopy the code
2,
(figure 4.4.5.6-2)
Explanation:
,,,, oldKeyToIdx= {'A': 0.'B': 1,'C': 2.'D':3} B exists in oldKeyToIdx and the index is 1. If sel is the same, the newStartVnode exists in oldChildren, patch(oldChildren[1], newStartVnode); oldChildren[1] = undefined; // Then move oldChildren[1] to oldStartVNode. elm; NewStartVnode = newChildren[++newStartIdx] -> ACopy the code
3,
(figure 4.4.5.6-3)
Explanation:
NewStartVnode = newChildren[++newStartIdx] ->D oldStartVnode = oldChildren[++EndIdx] = undefined; - > B is undefinedCopy the code
4,
(figure 4.4.5.6-4)
Explanation:
OldStartVnode is undefined; oldStarttidx ++oldStartIdx; oldStartVnode -> CCopy the code
5,
(figure 4.4.5.6-5)
Explanation:
5, head unequal, tail unequal, tail head equal implementation of the third case; patch(oldEndVnode, newStartVnode); Oldendvnode. elm move to oldStartVNode. elm; OldEndVnode = oldChildren[--oldEndIdx] -> C newStartVnode = newChildren[++newStartIdx] ->FCopy the code
6,
(figure 4.4.5.6-6)
Explanation:
NewStartVnode = newChildren[++newStartIdx] ->undefined newStartIdx > newEndIdx out of the loopCopy the code
In the end,
(figure 4.4.5.6-7)
OldStartIdx = oldEndIdx -> 2 -- -c indicates that these node elements C in oldChildren need to be deletedCopy the code
Providing a unique key attribute for list nodes can help compare nodes with the correct code, dramatically reducing DOM operations and improving performance. For different levels, it doesn’t matter if you don’t have a key. This is why vUE and React create lists through the for loop and ask you to pass keys.
4.5 diff policy rules in React
A patch is created from two virtual objects describing what has changed, and this patch is used to update the DOM
If you don’t know React: reactjs.org/docs/gettin…
4.5.1 diff strategy
1. DOM node movement across hierarchies in Web UI is rare and can be ignored.
2. Two components of the same type will generate similar tree structures. Two components of different types will generate different tree structures.
3. A group of child nodes at the same level can be distinguished by unique keys.
Based on the above strategies, React optimizes the tree Diff, Component Diff, and Element Diff algorithms.
Ps: Notice that in React we call setState
4.5.2 tree diff
Based on strategy 1, React optimizes the algorithm of trees in a concise way, that is, hierarchically compare trees. Two trees will only compare nodes at the same level, as shown in 4.3.1. Sequential deep loop traversal (e.g. 4.3.2); This improved scheme greatly reduces the algorithm complexity. When moving objects across hierarchies, React does not simply move objects, but deletes and creates objects, which affects React performance.
(figure 4.5.2.1)
When the root node finds that A has disappeared in the child node, it destroys A directly. When D discovers that A child node A is added, it creates A new child node (including the child node) as its child node. React diff run create A -> create B -> create C -> delete A.
4.5.3 component diff
This refers to functional components and class components (as in cases 2.2.2 and 2.2.3), and compares the flow:
1. Compare whether components are of the same type; If not, the component is judged to be a dirty Component and all child nodes under the entire component are replaced.
2. Components of the same type: continue to compare virtual DOM trees according to the original policy.
Ps:
Function component: run first (at this point the type(props) of the virtual DOM); Get the result returned; In accordance with the original strategy comparison;
Class component: need to build instance (new type(props).render()) before calling render(); Get the result returned; In accordance with the original strategy comparison;
The main optimization strategy in the Component diff phase is to use shouldComponentUpdate(). You can see 4.6 Details.
4.5.4 element diff
React Diff provides three types of node operations when nodes are at the same level. You can define rules for different types of nodes.
It can be defined as INSERT, MOVE, and REMOVE.
INSERT: indicates that the type of the new virtual DOM is not in the old set (we will generate a key-index set for the old virtual DOM), indicating that the node is new and needs to be inserted into the new node. MOVE: indicates that it exists in the old set. At this time, we need to compare the comparison index of the last saved node with the index of the old node. Moreover, element is an updatable type, so we need to do the MOVE operation, and we can reuse the previous DOM node. The old component types also exist in the new collection, but the corresponding elements cannot be reused or updated directly and need to be deleted. Or the old component types that are not in the new collection also need to be deletedCopy the code
To illustrate, here are some examples:
Example:
<ul>
<li key='A'>A<li/>
< li key= 'B' > B < li / >
< li key= 'C' > C < li / >
< li key='D' > D < li / >
</ul>
Copy the code
To:
<ul>
<li key='A'>A<li/>
< li key= 'C' > C < li / >
< li key= 'B' > B < li / >
< li key='E' > E < li / >
< li key='F' > F < li / >
</ul>
Copy the code
(figure 4.5.4.1)
Preparation:
NewCh: indicates the new son. NewCHUMap: indicates the new son. NewCHUMap: indicates the key of the new son. UpdateDepth = 0; // Update level Each node has its own mounted index value _mountIndexCopy the code
Cycle new son start comparison:
First comparison: I =0;
(figure 4.5.4.2)
Second comparison: I =1;
(figure 4.5.4.3)
Third comparison: I =2;
(figure 4.5.4.4)
The fourth time: I =3;
(figure 4.5.4.5)
Fifth: I = 4;
The same as the fourth; lasIndex = 4
If there is no newCHUMap in the set of new children, delete MOVE and insert it into the queue.
(figure 4.5.4.6)
Finally, update the patch pack;
4.6 When is DOM-DIff triggered
We know that the render function needs to be called here to trigger again, so when does the render function execute? Let’s look at the react declaration cycle
4.6.1 Old version life cycle
(chart 4.6.1)
4.6.2 New version of the declaration cycle
(figure 4.6.2)
4.6.3 summary:
The reactdom.render () function is called in the update phase: whether the new version or the old version of the declaration cycle, we need to note: ShouldComponentUpdate () {render ();} shouldComponentUpdate() {render (); React performance is affected if the react virtual DOM is of the same type as the react virtual DOM. There are very few opportunities for different types of Components to have similar DOM trees, so this extreme factor is unlikely to have a significant impact on implementation development; The default returns true.
Ps: Vue maintains the data as observable data. Each item of the data is collected through the getter for dependency collection, and then the dependency is converted into watcher and stored in the closure. After data modification, setter methods of the data are triggered, and then all watcher methods are called to modify the old virtual DOM, thus generating a new virtual DOM. Then, the diff algorithm is used to get the difference between the old and new DOM, and the real DOM is updated according to the difference.
4.7 summarize
Dom-diff compares the difference between two virtual DOM objects.
- The algorithm of sequential depth – first traversal is adopted
- A patch is created from two virtual objects describing what has changed, and this patch is used to update the DOM
5. Simulate the implementation of preliminary DIFF algorithm
5.1 differentsel
Types of implementation
Step 1: Check whether the parameter is a virtual DOM, isVnode(vnode) method implementation
/** * check whether vnode, mainly check __type. * @param {Object} Vnode to check the Object * @return {Boolean} is true, otherwise false */
export function isVnode(vnode){
return vnode && vnode._type === VNODE_TYPE
}
Copy the code
Step 2: Add isSameVnode. Check whether the isSameVnode is the same Vnode
/** * Check whether two vnodes are the same: Same key and same type * * @param {Object} oldVnode * @param {Object} newVnode * @returns {Boolean} If yes, true; otherwise, false */
export function isSameVnode(oldVnode,newVnode){
return oldVnode.sel === newVnode.sel && oldVnode.key === newVnode.key;
}
Copy the code
Step 3: The first parameter element of patch is not virtual DOM, so change it to virtual DOM
/** * convert a real DOM node to vnode *
convert to * {sel:'div# A.B.C ',data:{},children:[],text:undefined * elm:
} * @param {*} oldVnode */
function createEmptyNode(elm) {
let id = elm.id ? The '#' + elm.id : ' ';
let c = elm.className ? '. '+ elm.className.split(' ').join('. ') :' ';
return VNode(htmlApi.tagName(elm).toLowerCase() + id + c, {}, [], undefined.undefined, elm);
}
Copy the code
Step 4: Replace the old and new nodes with the same VNode
Update code of patch function in patch.js:
/** * Used to mount or update DOM ** @param {*} container * @param {*} vnode */
function patch(container, vnode) {
let elm, parent;
// let insertedVnodeQueue = [];
console.log(isVnode(vnode));
// If it is not a vnode, construct an empty vnode using the old DOM template.
if(! isVnode(container)) { container = createEmptyNode(container); }// If oldVnode and vnode are the same vnode (same key and same selector),
// Update the oldVnode.
if (isSameVnode(container, vnode)) {
patchVnode(container, vnode)
}else {
// If the old and new vNodes are different, replace the DOM corresponding to oldVnode directly
elm = container.elm;
parent = htmlApi.parentNode(elm);
createElement(vnode);
if(parent ! = =null) {// If the parent of the old node has and has sibling nodes,
// Insert the corresponding VNode DOM after its sibling.
htmlApi.insertBefore(parent,vnode.elm,htmlApi.nextSibling(elm));
// After inserting the corresponding DOM of vNode into the parent node of oldVnode, remove the corresponding DOM of oldVnode to complete the replacement.
removeVnodes(parent, [container], 0.0); }}};Copy the code
Patch. js adds processing to the removeVnodes function
** @param {Element} parentElm * @param {Array} vnodes vnode Array * @param {Number} @param {Number} endIdx Specifies the end index of the vNodes to be deleted
function removeVnodes(parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
let ch = vnodes[startIdx];
if(ch){
// if (ch.sel) {
// // first do not write events, hook processing
// } else {
// htmlApi.removeChild(parentElm,ch.elm);
// }htmlApi.removeChild(parentElm, ch.elm); }}}Copy the code
Step 5: Code test:
Index.js code changes:
import { h,patch } from './vdom';
The h function returns the virtual node
let vnode = h('ul#list', {}, [
h('li.item', {style: {'color':'red'}}, 'itemA'),
h('li.item', {
className: ['c1'.'c2']},'itemB'),
h('input', {
props: {
type: 'radio'.name: 'test'.value: '0'.className:'inputClass'}})]);let container = document.getElementById('app');
patch(container, vnode);
setTimeout((a)= > {
patch(vnode, h('p', {},'ul 'changed to' p'));
}, 3000);
Copy the code
(5.1.1)
5.2 Updating Properties
src\vdom\updataAttrUtils.js
import {
isArray
} from './utils'
/** * Update the style attribute ** @param {Object} vnode new virtual DOM node Object * @param {Object} oldStyle * @returns */
function undateStyle(vnode, oldStyle = {}) {
let doElement = vnode.elm;
let newStyle = vnode.data.style || {};
/ / delete the style
for(let oldAttr in oldStyle){
if(! newStyle[oldAttr]) { doElement.style[oldAttr] =' '; }}for(let newAttr innewStyle){ doElement.style[newAttr] = newStyle[newAttr]; }}function filterKeys(obj) {
return Object.keys(obj).filter(k= > {
returnk ! = ='style'&& k ! = ='id'&& k ! = ='class'})}/** * Update the props property * to support vNode to use props to operate other properties. * @param {Object} vnode new virtual DOM Object * @param {Object} oldProps * @returns */
function undateProps(vnode, oldProps = {}) {
let doElement = vnode.elm;
let props = vnode.data.props || {};
filterKeys(oldProps).forEach(key= > {
if(! props[key]) {delete doElement[key];
}
})
filterKeys(props).forEach(key= > {
let old = oldProps[key];
let cur = props[key];
if(old ! == cur && (key ! = ='value'|| doElement[key] ! == cur)) { doElement[key] = cur; }})}/** * Update the className attribute class * in HTML to support vNode to use props to manipulate other properties. * @param {Object} Vnode new virtual DOM node Object * @param {*} oldName * @returns */
function updateClassName(vnode, oldName) {
let doElement = vnode.elm;
const newName = vnode.data.className;
if(! oldName && ! newName)return
if (oldName === newName) return
if (typeof newName === 'string' && newName) {
doElement.className = newName.toString()
} else if (isArray(newName)) {
let oldList = [...doElement.classList];
oldList.forEach(c= > {
if(! newName.indexOf(c)) { doElement.classList.remove(c); } }) newName.forEach(v= > {
doElement.classList.add(v)
})
} else {
// Set className to '' for all invalid or null values.
doElement.className = ' '}}function initCreateAttr(vnode) {
updateClassName(vnode);
undateProps(vnode);
undateStyle(vnode);
}
function updateAttrs(oldVnode, vnode) {
updateClassName(vnode, oldVnode.data.className);
undateProps(vnode, oldVnode.data.props);
undateStyle(vnode, oldVnode.data.style);
}
export const styleApis = {
undateStyle,
undateProps,
updateClassName,
initCreateAttr,
updateAttrs
};
export default styleApis;
Copy the code
Added to patch.js:
function patchVnode(oldVnode, vnode) {
let elm = vnode.elm = oldVnode.elm;
if(isUndef(vnode.data)){
// The comparison of attributes is updatedattr.updateAttrs(oldVnode, vnode); }}Copy the code
(figure 5.2.1)
5.3 the children are
5.3.1 The new node is a text node
function patchVnode(oldVnode, vnode) {
// let elm = vnode.elm = oldvNode. elm, because vnode is not rendered, then vnode.
// The new give it the old
let elm = vnode.elm = oldVnode.elm;
if(isUndef(vnode.data)){
// The comparison of attributes is updated
attr.updateAttrs(oldVnode, vnode);
}
// The new node is not a text node
if(! isUndef(vnode.text)){ }// If oldvNode's text is different from vnode's text, update to vnode's text
else if(oldVnode.text !== vnode.text) {
htmlApi.setTextContent(elm, vnode.text);
}
}
Copy the code
5.3.2 Only one party has children
1. If the new VNode has children, oldvNode has no children
function patchVnode(oldVnode, vnode) {
// let elm = vnode.elm = oldvNode. elm, because vnode is not rendered, then vnode.
// The new give it the old
let elm = vnode.elm = oldVnode.elm,
oldCh = oldVnode.children,newCh = vnode.children;
if(! isUndef(vnode.data)){// The comparison of attributes is updated
attr.updateAttrs(oldVnode, vnode);
}
// The new node is not a text node
if(! vnode.text){if(! oldCh && (! newCh) ){ }else if (newCh) {
// If vNode has children, oldvNode has no children
// OldvNode is text node, then clear elm's text, because children and text have different values at the same time
if(! oldVnode.text) htmlApi.setTextContent(elm,' ');
// Add vNode's children
addVnodes(elm, null, newCh, 0, newCh.length - 1); }}// If oldvNode's text is different from vnode's text, update to vnode's text
else if(oldVnode.text !== vnode.text) {
htmlApi.setTextContent(elm, vnode.text);
}
}
Copy the code
Implement the addVnodes method:
function addVnodes(parentElm,before,vnodes,startIdx,endIdx){
for(; startIdx<=endIdx; ++startIdx){const ch = vnodes[startIdx];
if(ch ! =null){ htmlApi.insertBefore(parentElm,createElm(ch),before); }}}Copy the code
2. If the new node has no children, the old node has children
function patchVnode(oldVnode, vnode) {
// let elm = vnode.elm = oldvNode. elm, because vnode is not rendered, then vnode.
// The new give it the old
let elm = vnode.elm = oldVnode.elm,
oldCh = oldVnode.children,newCh = vnode.children;
// If two vNodes are identical, return directly
if (oldVnode === vnode) return;
if(! isUndef(vnode.data)){// The comparison of attributes is updated
attr.updateAttrs(oldVnode, vnode);
}
// The new node is not a text node
if (isUndef(vnode.text)) {
if (oldCh.length>0 && newCh.length>0) {
// If there are children in both the old and new nodes and they are different, diff the children
updateChildren(elm, oldCh, newCh);
} else if(newCh.length>0) {
// If vNode has children, oldvNode has no children
// OldvNode is text node, then clear elm's text, because children and text have different values at the same time
if(! oldVnode.text) htmlApi.setTextContent(elm,' ');
// Add vNode's children
addVnodes(elm, null, newCh, 0, newCh.length - 1);
} else if (oldCh.length>0) {
// Children does not exist on the new node. Children Exists on the old node
removeVnodes(elm, oldCh, 0, oldCh.length - 1)}}// If oldvNode's text is different from vnode's text, update to vnode's text
else if(oldVnode.text !== vnode.text) {
htmlApi.setTextContent(elm, vnode.text);
}
}
Copy the code
5.3.3 Comparison of heads
Add and delete elements in the tail,
The application effect is as follows: ABCD =>ABCDE ABCD =>ABC Specific implementation process —– Please refer to 4.4.5.3 Implementation Principles of Case 1
SRC \vdom\patch.js updateChildren function modified
function updateChildren(parentDOMElement, oldChildren, newChildren) {
// Compare two sets of data
let oldStartIdx = 0,oldStartVnode = oldChildren[0];
let oldEndIdx = oldChildren.length - 1,oldEndVnode = oldChildren[oldEndIdx];
let newStartIdx = 0,newStartVnode = oldChildren[0];
let newEndIdx = newChildren.length - 1,newEndVnode = newChildren[newEndIdx];
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
Vnodes are null;
// If the left vnode is empty, move the index right; if the right vnode is empty, move the index left
if (oldStartVnode == null) {
oldStartVnode = oldChildren[++oldStartIdx];
} else if (oldEndVnode == null) {
oldEndVnode = oldChildren[--oldEndIdx];
} else if (newStartVnode == null) {
newStartVnode = newChildren[++newStartIdx];
} else if (newEndVnode == null) {
newEndVnode = newChildren[--newEndIdx];
}
/ * * oldStartVnode/oldEndVnode/newStartVnode/newEndVnode two comparison, OldStartVnode - > newStartVnode * 2, oldEndVnode - > newEndVnode * 3, newStartVnode - > oldEndVnode * 4, NewEndVnode - > oldStartVnode * Performs the corresponding patch */ for the above four cases
Is the new start node the same vnode as the old start node
// oldStartVnode - > newStartVnode for example, add or delete nodes at the end
//
else if(isSameVnode(oldStartVnode, newStartVnode)) { patch(oldStartVnode, newStartVnode); oldStartVnode = oldChildren[++oldStartIdx]; newStartVnode = newChildren[++newStartIdx]; }}// After the loop is completed, the new nodes still have data, so the creation of these virtual nodes is dom
// Add a new reference to 'elm' of the old virtual DOM, and add it to oldStartVnode of the old dom.
if (newStartIdx <= newEndIdx) {
addVnodes(parentDOMElement, null, newChildren, newStartIdx, newEndIdx);
}
if (oldStartIdx <= oldEndIdx) {
// newChildren has been processed completely, and oldChildren still has old nodes, which need to be removedremoveVnodes(parentDOMElement, oldChildren, oldStartIdx, oldEndIdx); }}Copy the code
5.3.4 Tail to tail comparison
Application: Add and remove elements in the header
The implementation effect is as follows: ABCD => EABCD ABCD => BCD For details about the implementation process —–, see 4.4.5.3 Implementation Principles of Case 2
Change the SRC \vdom\patch.js updateChildren function
// 2, oldEndVnode - > newEndVnode for example, add or delete nodes in the header
else if (isSameVnode(oldEndVnode, newEndVnode)) {
patch(oldEndVnode, newEndVnode);
oldEndVnode = oldChildren[--oldEndIdx];
newEndVnode = newChildren[--newEndIdx];
}
Copy the code
// After the loop is completed, the new nodes still have data, so the creation of these virtual nodes is dom
// Add a new reference to 'elm' of the old virtual DOM, and add it to oldStartVnode of the old dom.
if (newStartIdx <= newEndIdx) {
let before = newChildren[newEndIdx + 1] = =null ? null : newChildren[newEndIdx + 1].elm;
addVnodes(parentDOMElement, before, newChildren, newStartIdx, newEndIdx);
}
Copy the code
5.3.5 Comparison of the old tail and the new head
Real application: move the header element to the tail
Implementation result: ABCD => DBCA For details about the implementation process —–, see 4.4.5.3 Implementation Principles of Case 3
Change the SRC \vdom\patch.js updateChildren function
// 3. NewEndVnode - > oldStartVnode moves the head node to the tail
else if (isSameVnode(oldStartVnode, newEndVnode)) {
patch(oldStartVnode, newEndVnode);
// Insert the old start node at the end
htmlApi.insertBefore(parentDOMElement, oldStartVnode.elm, htmlApi.nextSibling(oldEndVnode.elm));
oldStartVnode = oldChildren[++oldStartIdx];
newEndVnode = newChildren[--newEndIdx];
}
Copy the code
5.3.6 Comparison between old head and new tail
Application: Move the tail element to the head
Implementation result: ABCD => BCDA implementation process —– For details, see 4.4.5.3 Implementation Principles in Case 4
Change the SRC \vdom\patch.js updateChildren function
// 4. OldEndVnode -> newStartVnode moves the tail to the head
else if (isSameVnode(oldEndVnode, newStartVnode)) {
patch(oldEndVnode,newStartVnode);
// Move the old oldEndVnode to the front of oldStartVnode,
htmlApi.insertBefore(parentDOMElement, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldChildren[--oldEndIdx];
newStartVnode = newChildren[++newStartIdx];
}
Copy the code
5.3.7. Use key for comparison
Step 1: Create a map of the children node’s key and index
/** * creates a mapping between vnode's key and its subscript for vnode * in the range from begin to end. * * @param {Array} children * @param {Number} startIdx * @param {Number} endIdx * @returns {Object} Key mapping in the children's index index object * children = [{key: 'A'}, {key: 'B'}, {key: "C"}, {key: 'D'}, {key: 'E'}]. * startIdx = 1; endIdx = 3; The function returns {'B':1,'C':2,'D':3} */
function createOldKeyToIdx(children, startIdx, endIdx) {
const map = {};
let key;
for (let i = startIdx; i <= endIdx; ++i) {
let ch = children[i];
if(ch ! =null){
key = ch.key;
if(!isUndef(key)) map[key] = i;
}
}
return map;
}
Copy the code
Step 2: Save the index of the new start node in the old node
oldIdxByKeyMap = oldKeyToIdx[newStartVnode.key];
Copy the code
Step 3: Check whether oldIdxByKeyMap exists
// 1. Create a map from oldChildren with key > index. NewStartVnode = newStartVnode = newStartVnode = newStartVnode = newStartVnode // 3. If index exists, then there is a corresponding old vnode, patch. // 4. If index does not exist, newStartVnode is a new vnode. * /
else{
/** If oldKeyToIdx does not exist, * 1, create a map of old Children vnode key to index ** /
if (isUndef(oldKeyToIdx)) {
oldKeyToIdx = createOldKeyToIdx(oldChildren,oldStartIdx,oldEndIdx);
}
// 2. Try the newStartVnode key to retrieve the subscript
oldIdxByKeyMap = oldKeyToIdx[newStartVnode.key];
// 4. If the index does not exist, newStartVnode is a new Vnode
if (oldIdxByKeyMap == null) {
// Then create a DOM for newStartVnode and insert it in front of oldStartvNode.elm.
htmlApi.insertBefore(parentDOMElement,createElement(newStartVnode),oldStartVnode.elm);
newStartVnode = newChildren[++newStartIdx];
}
// 3. If the subscript exists, there are vnodes with the same key in oldChildren
else{
elmToMove = oldChildren[oldIdxByKeyMap];
// If the key is the same, you need to compare sel
if(elmToMove.sel ! == newStartVnode.sel) { htmlApi.insertBefore(parentDOMElement,createElement(newStartVnode),oldStartVnode.elm); }// If the SEL and key are the same, the VNodes are the same and need to be patched
else{
patch(elmToMove,newStartVnode);
oldChildren[oldIdxByKeyMap] = undefined; htmlApi.insertBefore(parentDOMElement,elmToMove.elm,oldStartVnode); } newStartVnode = newChildren[++newStartIdx]; }}Copy the code
Specific completion code can be viewed: github.com/learn-fe-co…
6. Simulate the preliminary rendering implementation of VDOM in React
Preface:
According to the preliminary rendering case and effect analysis in 2.2 React, we need to implement VDOM and render the page real DOM. The specific steps are as follows:
1. Create a project
Create react.js
Export createElment: returns a vDOM object
Export the Component class: Inheriting this class lets you pass the parameter props
Create react-dom.js
Export the render function that updates the virtual DOM to the elment element to be mounted
Note 1: how text nodes, functions, class components, and element components cannot be handled
Official code part:
6.1 Creating projects and Setting up environments
Step 1: We’ll create a React project quickly with scaffolding:
npm i create-react-app -g
create-react-app lee-vdom-react
Copy the code
Step 2: Delete unnecessary files and codes as shown in the figure below:
(Figure 6.1.1) implements an example of 2.2.1
6.2 Initial implementation of creating virtual nodes in react. Js
Step 1: Create react.js
src\react.js
import createElement from './element';
export default {
createElement
}
Copy the code
Step 2: Create element.js
src\element.js
Constructor (type, props) {this.type = type; // Constructor (type, props) {this.type = type; this.props = props; this.key = props.key ? props.key : undefined; }} /** ** create virtual DOM * @param {String} type tag name * @param {Object} [config={}] attribute * @param {*} * @returns */ function createElement(type,config = {},... children){ const props = {}; for(let propsName in config){ props[propsName] = config[propsName]; } // let len = children. Length; if (len>0) { props.children = children.length === 1 ? children[0] :children; } return new Element(type, props); } export {Element,createElement};Copy the code
Testing:
index.js
import React from './react'; // import ReactDOM from 'react-dom'; let virtualDom = React.createElement('h1', null,'hello,lee'); Console. log(' reference reactjs generated virtualDom :',virtualDom);Copy the code
(figure 6.2.1)
Conclusion:
// The createElement function in native React returns the following object: {$$typeof:REACT_ELEMENT_TYPE, // used to indicate that is a React element ignored in this articletype:type, the key: key, ref: ref, / / ignore props: props, _owner: owner, / / ignore _store: {}, / / ignore love: {}, / / ignore _source: {}} / / ignored; The _store, _self, and _source attributes are all provided for easy testing in the development environment to compare two ReactElementCopy the code
CreateElement function parameter analysis
- Type: Specifies the label type of an element, such as ‘li’, ‘div’, ‘a’, etc
- Props: specifies properties on an element, such as class, style, and custom properties
- Children: indicates an array of child nodes of the specified element. A length of 1 indicates a text node, and a length of 0 indicates that no text node exists
6.3 Simulating vDOM preliminary rendering implementation of React
Step 1: create react-dom.js
src\react-dom.js
import {isPrimitive} from './utils'; import htmlApi from './domUtils'; /** * the render method converts the virtual DOM to the real DOM ** @param {element} container */ function Render (element,container){// If it is a string or number, If (isPrimitive(Element)) {return htmlapi.appendChild (htmlapi.createTextNode (element)); } let type,props; type = element.type; let domElement = htmlApi.createElement(container,type;) ; htmlApi.appendChild(container,element); } export default { render}Copy the code
Step 2: Reference the utils.js and domutils. js of SRC \vdom\ in the previous lee-vdom project to the current project SRC directory
Step 3: Process the props in the element parameter into the real DOM
For (let [key, val] of object.entries (element.props)) {htmlapi.setattr (domElement, key, val); }Copy the code
/** ** Set attributes for dom * @param {Element} el DOM Element to set attributes * @param {*} key Key value to set attributes * @param {*} val Value value to set attributes */ function setAttr(el, key, val) { if (key === 'children') { val = isArray(val)? val : [val]; val.forEach(c=>{ render(c,el); }) }else if(key === 'value'){ let tagName = htmlApi.tagName(el) || ''; tagName = tagName.toLowerCase(); if (tagName === 'input' || tagName === 'textarea') { el.value = val; } else {// If the node is not input or textarea, use 'setAttribute' to set htmlapi.setAttribute (el,key, val); Else if (key === 'className') {if (val) el.className = val; }else if(key === 'style'){// JSX is not HTML. // Let cssText = object.keys (val).map(attr => {return) `${attr.replace(/([A-Z])/g,()=>{ return"-"+arguments[1].toLowerCase()})}:${val[attr]}`; }).join('; '); el.style.cssText = cssText; }else if(key === 'on'){else{htmlapi.setAttribute (el, key, val); }Copy the code
6.4 Adding logic to class and function components
1. Add logical rendering of class components
We code in example: 2.2.3
class Wecome1 extends Reat.Component{
render(){ return (....)}
}
Copy the code
When we use class components:
- Need to inherit reat.component;
- We need to return a react element through the render() function;
- Properties are received with this.props = {…. }, so we need constructor assignment, when using class inheritance;
Step 1: Modify react.js
import createElement from './element';
class Component{
// Is used to determine whether it is a class component
static isReactComponent = true;
constructor(props) {
this.props = props; }}export default {
createElement,
Component
}
Copy the code
Step 2: Modify react-dom.js
// If (type.isreactComponent) {// If (type.isreactComponent) {// If (type.isreactComponent) {// If (type.isreactComponent) {// If (type.isreactComponent) {// If (type.isreactComponent) {// If (type.isreactComponent) {// If (type.isreactComponent) { props = element.props; type = element.type; }Copy the code
2. Add rendering of function components
Modify the react – dom. Js
Else if(isFun(type)){// If (function){React element element element = type(props); props = element.props; type = element.type; }Copy the code
6.5 Optimize render method
We can see that the render method has some judgment on text nodes, components, many similar methods, each time to change the render function, according to the design mode of the idea does not meet.
We create a class to handle the different text components separately, and the class components handle the different logic.
src\unit.js
Import {primitive, isArray, isFun, isRectElement, isStr} from './utils'; import {primitive, isArray, isFun, isRectElement, isStr} from '. import htmlApi from './domUtils'; import EventFn from './event'; Class Unit{constructor(elm) {// take this._selfelm = elm; this._events = new EventFn(); } getHtml(){}} class TextUnit extends Unit{getHtml(){return htmlapi.createTextNode (this._selfelm); } } // class NativeUnit extends Unit{ getHtml() { let {type,props} = this._selfElm; // create dom let domElement = htmlapi.createElement (type); props = props ||{}; For (let [key, val] of object.entries (props)) {this.setProps(domElement, key, val); } return domElement; * @param {Element} el DOM Element to be set * @param {*} key Key to be set * @param {*} val Value to be set */ setProps(el, key, val) { if (key === 'children') { val = isArray(val) ? val : [val]; val.forEach(c => { let cUnit = createUnit(c); let cHtml = cUnit.getHtml(); htmlApi.appendChild(el,cHtml); }); } else if (key === 'value') { let tagName = htmlApi.tagName(el) || ''; tagName = tagName.toLowerCase(); if (tagName === 'input' || tagName === 'textarea') { el.value = val; } else {// If the node is not input or textarea, use 'setAttribute' to set htmlapi.setAttribute (el, key, val); Else if (key === 'className') {if (val) el.className = val; } else if (key === 'style') {// JSX is not HTML. // Let cssText = object.keys (val).map(attr => {return) `${attr.replace(/([A-Z])/g,()=>{ return"-"+arguments[1].toLowerCase()})}:${val[attr]}`; }).join('; '); el.style.cssText = cssText; } else if (key === 'on') {else {htmlapi.setAttribute (el, key, val); } } } class ComponentUnit extends Unit{ getHtml(){ let {type,props} = this._selfElm; let component = new type(props); / / if there is one component will render function to execute ponentWillMount component.com & component.com ponentWillMount (); let vnode = component.render(); let elUnit = createUnit(vnode); let mark = elUnit.getHtml(); this._events.on('mounted', () => { component.componentDidMount && component.componentDidMount(); }); return mark; FunctionUnit extends Unit{getHtml(){let {type,props} = this._selfelm; let fn = type(props); let vnode = fn.render(); let elUnit = createUnit(vnode); let mark = elUnit.getHtml(); return mark; } } function createUnit(vnode){ if(isPrimitive(vnode)){ return new TextUnit(vnode); } if (isRectElement(vnode) && isStr(vnode.type)) { return new NativeUnit(vnode); } if (isRectElement(vnode) && vnode.type.isReactComponent) { return new ComponentUnit(vnode); } if (isRectElement(vnode) && isFun(vnode.type)) { return new FunctionUnit(vnode); } } export default createUnit;Copy the code
src\react-dom.js
import htmlApi from './domUtils'; import EventFn from './event'; import createUnit from './unit'; /** * the render method converts the virtual DOM to the real DOM ** @param {element} container */ function render(element,container){ let unit = createUnit(element); let domElement = unit.getHtml(); htmlApi.appendChild(container, domElement); unit._events.emit('mounted'); } export default { render}Copy the code
7. Implementation of diff algorithm in React
Preface:
According to the analysis of 4.5 React Diff policy, the steps for implementing the process of comparing old and new nodes to page updates are as follows:
Create types.js to store node change types
Create diff.js
Diff function: oldTree (newTree) returns a patch packageCopy the code
DeepTraversal function:
3, patch. Js
Patch (node,patches) function: Specifies changes
4,
Official code part:
7.1 Text update
The example we expect to implement is as follows: SRC \index.js
import React from './react'; // ReactDOM import ReactDOM from './react-dom'; class Counter extends React.Component{ constructor(props) { super(props); this.state = {number:0} } componentDidMount() { setTimeout(() => { this.setState({number:this.state.number+1}) }, 3000); } // componentShouldUpdate(nextProps,newState) {return true; } render(){ return this.state.number; } } let el = React.createElement(Counter); ReactDOM.render(el,document.getElementById('root'));Copy the code
React elements are immutable. Once an element is created, you cannot change its contents or attributes.
So what to do? We can call the render() function again with setTimeout() to create a new element; Or change the state with setState. In the example above, the setState method is used.
Step 1: Add the setState method
Split component into a single JS SRC \component.js
Class Component {// Static isReactComponent = true; constructor(props) { this.props = props; } // Update the update method that calls each unit's own unit, state state objects or functions now do not consider or consider asynchronous setState(state) {// The first argument is a new node, The second argument is the new state this._selfunit.update (null, state); } } export default ComponentCopy the code
Step 2: Import import Component from ‘./ Component ‘in react.js;
Step 3: Add the UPDATE method
src\unit.js
You need to save _selfUnit\ the current component instance in this._ComponentInstance
React provides a component lifecycle function shouldComponentUpdate, which is called before a component decides to re-render (after the final DOM has been generated by the virtual DOM alignment). This function gives the developer permission to re-render or not. This function returns true by default. Default dom update:
class ComponentUnit extends Unit{ getHtml(){ let {type,props} = this._selfElm; let component = this._componentInstance = new type(props); // Save the current unit to the current instance component._selfunit = this; / / if there is one component will render function to execute ponentWillMount component.com & component.com ponentWillMount (); let vnode = component.render(); let elUnit = this._renderUnit = createUnit(vnode); let mark = this._selfDomHtml = elUnit.getHtml(); this._events.on('mounted', () => { component.componentDidMount && component.componentDidMount(); }); return mark; } / / here is responsible for processing components of update operations setState method call to update the update (newEl, partState) {/ / get the new element enclosing _selfElm = newEl | | this. _selfElm; Let newState = this._ComponentInstance. state = object. assign(this._ComponentInstance. state, partState); // The new property object let newProps = this._selfelm.props; let shouldUpdate = this._componentInstance.componentShouldUpdate; if (shouldUpdate && ! shouldUpdate(newProps, newState)) { return; } // let preRenderUnit = this._renderunit; let preRenderEl = preRenderUnit._selfElm; let preDomEl = this._selfDomHtml; let newRenderEl = this._componentInstance.render(); // If the old element type is the same as the old one, the depth comparison can be made. If the old element type is different, delete the old element directly. ShouldDeepCompare (preRenderEl, // Call prerenderUnit.update (preDomEl,newRenderEl); // call prerenderUnit.update (preDomEl,newRenderEl); this._componentInstance.componentDidUpdate && this._componentInstance.componentDidUpdate(); } else {}}} // Text node class TextUnit extends Unit{getHtml(){return htmlapi.createTextNode (this._selfelm); } update(node,newEl) {if (this._selfelm! == newEl) { this._selfElm = newEl; htmlApi.setTextContent(node.parentNode, this._selfElm); }}}Copy the code
7.2 Direct Replacement of Different types of updates
Example: SRC \index.js
import React from './react'; // introduce the corresponding method to create the virtual DOM import ReactDOM from'./react-dom';
class Counter extends React.Component{
constructor(props) {
super(props);
this.state = {number:0,isFlag:true}}componentWillMount() {
console.log('componentWillMount execution'); } // If the component wants to update componentShouldUpdate(nextProps,newState) {return true;
}
componentDidMount() {
console.log('componentDidMount execution');
setTimeout(() => {
this.setState({isFlag:false})}, 3000); }componentDidUpdate() {
console.log('componentDidUpdate Counter');
}
render() {return this.state.isFlag ? this.state.number : React.createElement('p',{id:'p'},'hello'); }}let el = React.createElement(Counter,{name:'lee'});
ReactDOM.render(el,document.getElementById('root'));
Copy the code
Change the update function for ComponentUnit in SRC \unit.js
/ / here is responsible for processing components of the update operation setState method call to update the update (newEl, partState) {/ / get the new element enclosing _selfElm = newEl | | this. _selfElm; Let newState = this._ComponentInstance. state = object. assign(this._ComponentInstance. state, partState); // The new property object let newProps = this._selfelm.props; let shouldUpdate = this._componentInstance.componentShouldUpdate; if (shouldUpdate && ! shouldUpdate(newProps, newState)) { return; } // let preRenderUnit = this._renderunit; let preRenderEl = preRenderUnit._selfElm; let preDomEl = this._selfDomHtml; let parentNode = preDomEl.parentNode; let newRenderEl = this._componentInstance.render(); // If the old element type is the same as the old one, the depth comparison can be made. If the old element type is different, delete the old element directly. ShouldDeepCompare (preRenderEl, // Call prerenderUnit.update (preDomEl,newRenderEl); // call prerenderUnit.update (preDomEl,newRenderEl); this._componentInstance.componentDidUpdate && this._componentInstance.componentDidUpdate(); } else {this._renderUnit = createUnit(newRenderEl); let newDom = this._renderUnit.getHtml(); parentNode.replaceChild(newDom,preDomEl); }}Copy the code
7.3 Updating Attributes
SRC \unit.js class NativeUnit extends Unit{
UpdateProps (oldNode,oldProps, props) {for (let key in oldProps) {if (! props.hasOwnProperty(key) && key ! = 'key') { if (key == 'style') { oldNode.style[key] = ''; }else{ delete oldNode[key]; } if (/^on[a-z]/.test(key)) {// unbinding}} for (let propsName in props) {let val = props[propsName]; if (propsName === 'key') { continue; } else if (propsName. StartsWith ('on')) else if (propsName === 'children') {continue; } else if (propsName === 'className') { oldNode.className = val; } else if (propsName === 'style') { let cssText = Object.keys(val).map(attr => { return `${attr.replace(/([A-Z])/g,()=>{ return"-"+arguments[1].toLowerCase()})}:${val[attr]}`; }).join('; '); oldNode.style.cssText = cssText; } else { htmlApi.setAttribute(oldNode,propsName,val); } } } update(oldNode,newEl){ let oldProps = this._selfElm.props; let props = newEl.props; This. UpdateProps (oldNode,oldProps, props); }Copy the code
7.4 children update
The new sons were passed over and diff was compared with the old sons, and then the difference patches were found and modified
Modify the SRC \ unit.and js
Step 1: Define the global,
letdiffQueue; // Difference queueletupdateDepth = 0; // Update levelCopy the code
Step 2: Compare the differences
1. Store the previous sons in the current _renderedChUs
2, get key->index set of old node
3, get the set of key->index of new sons and new sons
If the current _mountIndex is <lastIndex and can be inserted into the queue again, the type of the new child is MOVE.
If the old set does not exist, INSERT will be added. If the old set does not exist, INSERT will be added to the queue.
5, loop the old son, not in the new son set, it is deleted, insert into the queue
The resulting queue, known as the patch pack, is updated to the DOM
The complete code SRC \ ununit.js
/**
* 凡是挂载到私有属性上的_开头
* */
import {
isPrimitive,
isArray,
isFun,
isRectElement,
isStr
} from './utils';
import htmlApi from './domUtils';
import EventFn from './event';
// import diff from './diff';
import types from './types';
// import patch from './patch';
// import diff form './diff';
let diffQueue = []; //差异队列
let updateDepth = 0; //更新的级别
class Unit{
constructor(elm) {
// 将
this._selfElm = elm;
this._events = new EventFn();
}
getHtml(){
}
}
// 文本节点
class TextUnit extends Unit{
getHtml(){
this._selfDomHtml = htmlApi.createTextNode(this._selfElm);
return this._selfDomHtml;
}
update(newEl) {
// 新老文本节点不相等,才需要替换
if (this._selfElm !== newEl) {
this._selfElm = newEl;
htmlApi.setTextContent(this._selfDomHtml.parentNode, this._selfElm);
}
}
}
//
class NativeUnit extends Unit{
getHtml() {
let {type,props} = this._selfElm;
// 创建dom
let domElement = htmlApi.createElement(type);
props = props || {};
// 存放children节点
this._renderedChUs = [];
// 循环所有属性,然后设置属性
for (let [key, val] of Object.entries(props)) {
this.setProps(domElement, key, val,this);
}
this._selfDomHtml = domElement;
return domElement;
}
/**
*
* 给dom设置属性
* @param {Element} el 需要设置属性的dom元素
* @param {*} key 需设置属性的key值
* @param {*} val 需设置属性的value值
*/
setProps(el, key, val,selfU) {
if (key === 'children') {
val = isArray(val) ? val : [val];
val.forEach((c,i) => {
if(c != undefined){
let cUnit = createUnit(c);
cUnit._mountIdx = i;
selfU._renderedChUs.push(cUnit);
let cHtml = cUnit.getHtml();
htmlApi.appendChild(el, cHtml);
}
});
} else if (key === 'value') {
let tagName = htmlApi.tagName(el) || '';
tagName = tagName.toLowerCase();
if (tagName === 'input' || tagName === 'textarea') {
el.value = val;
} else {
// 如果节点不是 input 或者 textarea, 则使用 `setAttribute` 去设置属性
htmlApi.setAttribute(el, key, val);
}
}
// 类名
else if (key === 'className') {
if (val) el.className = val;
} else if (key === 'style') {
//需要注意的是JSX并不是html,在JSX中属性不能包含关键字,
// 像class需要写成className,for需要写成htmlFor,并且属性名需要采用驼峰命名法
let cssText = Object.keys(val).map(attr => {
return `${attr.replace(/([A-Z])/g,()=>{ return"-"+arguments[1].toLowerCase()})}:${val[attr]}`;
}).join(';');
el.style.cssText = cssText;
} else if (key === 'on') { //目前忽略
} else {
htmlApi.setAttribute(el, key, val);
}
}
// 记录属性的差异
updateProps(oldProps, props) {
let oldNode = this._selfDomHtml;
for (let key in oldProps) {
if (!props.hasOwnProperty(key) && key != 'key') {
if (key == 'style') {
oldNode.style[key] = '';
}else{
delete oldNode[key];
}
}
if (/^on[A-Z]/.test(key)) {
// 解除绑定
}
}
for (let propsName in props) {
let val = props[propsName];
if (propsName === 'key') {
continue;
}
// 事件
else if (propsName.startsWith('on')) {
// 绑定事件
} else if (propsName === 'children') {
continue;
} else if (propsName === 'className') {
oldNode.className = val;
} else if (propsName === 'style') {
let cssText = Object.keys(val).map(attr => {
return `${attr.replace(/([A-Z])/g,()=>{ return"-"+arguments[1].toLowerCase()})}:${val[attr]}`;
}).join(';');
oldNode.style.cssText = cssText;
} else {
htmlApi.setAttribute(oldNode,propsName,val);
}
}
}
update(newEl){
let oldProps = this._selfElm.props;
let props = newEl.props;
// 比较节点的属性是否相同
this.updateProps(oldProps, props);
// 比较children
this.updateDOMChildren(props.children);
}
// 把新的儿子们传递过来,与老的儿子们进行对比,然后找出差异,进行修改
updateDOMChildren(newChEls) {
updateDepth++;
this.diff(diffQueue, newChEls);
updateDepth--;
if (updateDepth === 0) {
this.patch(diffQueue);
diffQueue = [];
}
}
// 计算差异
diff(diffQueue, newChEls) {
let oldChUMap = this.getOldChKeyMap(this._renderedChUs);
let {newCh,newChUMap} = this.getNewCh(oldChUMap,newChEls);
let lastIndex = 0; //上一个的确定位置的索引
for (let i = 0; i < newCh.length; i++) {
let c = newCh[i];
let newKey = this.getKey(c,i);
let oldChU = oldChUMap[newKey];
if (oldChU === c) { //如果新老一致,说明是复用老节点
if (oldChU._mountIdx < lastIndex) { //需要移动
diffQueue.push({
parentNode: oldChU._selfDomHtml.parentNode,
type: types.MOVE,
fromIndex: oldChU._mountIdx,
toIndex: i
});
}
lastIndex = Math.max(lastIndex, oldChU._mountIdx);
} else {
if (oldChU) {
diffQueue.push({
parentNode: oldChU._selfDomHtml.parentNode,
type: types.REMOVE,
fromIndex: oldChU._mountIdx
});
// 去掉当前的需要删除的unit
this._renderedChUs = this._renderedChUs.filter(item => item != oldChU);
// 去除绑定事件
}
let node = c.getHtml();
diffQueue.push({
parentNode: this._selfDomHtml,
type: types.INSERT,
markUp: node,
toIndex: i
});
}
//
c._mountIdx = i;
}
// 循环老儿子的key:节点的集合,在新儿子集合中没有找到的都打包到删除
for (let oldKey in oldChUMap) {
let oldCh = oldChUMap[oldKey];
let parentNode = oldCh._selfDomHtml.parentNode;
if (!newChUMap[oldKey]) {
diffQueue.push({
parentNode: parentNode,
type: types.REMOVE,
fromIndex: oldCh._mountIdx
});
// 去掉当前的需要删除的unit
this._renderedChUs = this._renderedChUs.filter(item => item != oldCh);
// 去除绑定
}
}
}
// 打补丁
patch(diffQueue) {
let deleteCh = [];
let delMap = {}; //保存可复用节点集合
for (let i = 0; i < diffQueue.length; i++) {
let curDiff = diffQueue[i];
if (curDiff.type === types.MOVE || curDiff.type === types.REMOVE) {
let fromIndex = curDiff.fromIndex;
let oldCh = curDiff.parentNode.children[fromIndex];
delMap[fromIndex] = oldCh;
deleteCh.push(oldCh);
}
}
deleteCh.forEach((item)=>{htmlApi.removeChild(item.parentNode, item)});
for (let i = 0; i < diffQueue.length; i++) {
let curDiff = diffQueue[i];
switch (curDiff.type) {
case types.INSERT:
this.insertChildAt(curDiff.parentNode, curDiff.toIndex, curDiff.markUp);
break;
case types.MOVE:
this.insertChildAt(curDiff.parentNode, curDiff.toIndex, delMap[curDiff.fromIndex]);
break;
default:
break;
}
}
}
insertChildAt(parentNode, fromIndex, node) {
let oldCh = parentNode.children[fromIndex];
oldCh ? htmlApi.insertBefore(parentNode, node, oldCh) : htmlApi.appendChild(parentNode,node);
}
getKey(unit, i) {
return (unit && unit._selfElm && unit._selfElm.key) || i.toString();
}
// 老的儿子节点的 key-》i节点 集合
getOldChKeyMap(cUs = []) {
let map = {};
for (let i = 0; i < cUs.length; i++) {
let c = cUs[i];
let key = this.getKey(c,i);
map[key] = c;
}
return map;
}
// 获取新的children,和新的儿子节点 key-》节点 结合
getNewCh(oldChUMap, newChEls) {
let newCh = [];
let newChUMap = {};
newChEls.forEach((c,i)=>{
let key = (c && c.key) || i.toString();
let oldUnit = oldChUMap[key];
let oldEl = oldUnit && oldUnit._selfElm;
if (shouldDeepCompare(oldEl, c)) {
oldUnit.update(c);
newCh.push(oldUnit);
newChUMap[key] = oldUnit;
} else {
let newU = createUnit(c);
newCh.push(newU);
newChUMap[key] = newU;
this._renderedChUs[i] = newCh;
}
});
return {newCh,newChUMap};
}
}
class ComponentUnit extends Unit{
getHtml(){
let {type,props} = this._selfElm;
let component = this._componentInstance = new type(props);
// 保存当前unit到当前实例上
component._selfUnit = this;
// 如果有组件将要渲染的函数的话需要执行
component.componentWillMount && component.componentWillMount();
let vnode = component.render();
let elUnit = this._renderUnit = createUnit(vnode);
let mark = this._selfDomHtml = elUnit.getHtml();
this._events.once('mounted', () => {
component.componentDidMount && component.componentDidMount();
});
return mark;
}
// 这里负责处理组件的更新操作 setState方法调用更新
update(newEl, partState) {
// 获取新元素
this._selfElm = newEl || this._selfElm;
// 获取新状态 不管组件更新不更新 状态一定会修改
let newState = this._componentInstance.state = Object.assign(this._componentInstance.state, partState);
// 新的属性对象
let newProps = this._selfElm.props;
let shouldUpdate = this._componentInstance.componentShouldUpdate;
if (shouldUpdate && !shouldUpdate(newProps, newState)) {
return;
}
// 下边是需要深度比较
let preRenderUnit = this._renderUnit;
let preRenderEl = preRenderUnit._selfElm;
let preDomEl = this._selfDomHtml;
let parentNode = preDomEl.parentNode;
let newRenderEl = this._componentInstance.render();
// 新旧两个元素类型一样 则可以进行深度比较,不一样,直接删除老元素,新建新元素
if (shouldDeepCompare(preRenderEl, newRenderEl)) {
// 调用相对应的unit中的update方法
preRenderUnit.update(newRenderEl);
this._componentInstance.componentDidUpdate && this._componentInstance.componentDidUpdate();
} else {
// 类型相同 直接替换
this._renderUnit = createUnit(newRenderEl);
let newDom = this._renderUnit.getHtml();
parentNode.replaceChild(newDom,preDomEl);
}
}
}
// 不考虑hook
class FunctionUnit extends Unit{
getHtml(){
let {type,props} = this._selfElm;
let fn = type(props);
let vnode = fn.render();
let elUnit = createUnit(vnode);
let mark = elUnit.getHtml();
this._selfDomHtml = mark;
return mark;
}
}
// 获取key,没有key获取当前可以在儿子节点内的索引
function getKey(unit,i){
return (unit && unit._selfElm && unit._selfElm.key) || i.toString();
}
// 判断两个元素的类型是不是一样 需不需要进行深度比较
function shouldDeepCompare(oldEl, newEl) {
if (oldEl != null && newEl != null) {
if (isPrimitive(oldEl) && isPrimitive(newEl)) {
return true;
}
if (isRectElement(oldEl) && isRectElement(newEl)) {
return oldEl.type === newEl.type;
}
}
return false;
}
function createUnit(vnode){
if(isPrimitive(vnode)){
return new TextUnit(vnode);
}
if (isRectElement(vnode) && isStr(vnode.type)) {
return new NativeUnit(vnode);
}
if (isRectElement(vnode) && vnode.type.isReactComponent) {
return new ComponentUnit(vnode);
}
if (isRectElement(vnode) && isFun(vnode.type)) {
return new FunctionUnit(vnode);
}
}
export default createUnit;
Copy the code
The full project can be viewed at github.com/learn-fe-co…
8, summary
The virtual DOM is a JavaScript object
2. Use virtual DOM, use DOM-DIff to compare differences, and reuse nodes is the purpose. To reduce dom manipulation.
3. This paper explains the implementation process in detail through the initial rendering of the virtual DOM in Snabbdom. js and React and the DOM-diff process
4. Note that the React Fiber diff implementation is different from the react Fiber diff implementation, which will be analyzed in the following articles
5. Implementation process of the entire virtual DOM:
- 1. Simulate the DOM with JavaScript objects
- 2. Convert the virtual DOM into a real DOM and insert it into the page
- 3. If any event is modified, a new virtual DOM needs to be generated
- 4. Compare the differences between two virtual DOM trees to obtain the difference object (also known as patch)
- 5. Apply the difference object to a real DOM tree
9. Explanation of key knowledge ~~~~~~~~~
9.1. Redraw and backflow and browser rendering mechanisms
1. Browser operation mechanism
Once the browser kernel gets the HTML file, there are roughly five steps:
- \1. Use HTML analyzer to parse HTML elements and build A DOM tree
- \2. Use CSS analyzer to parse CSS and Style on elements, and generate page CSS Style Rules
- \3. Construct a Render tree by associating the DOM tree with the stylesheet. This process is also called Attachment. Each DOM node has a attach method that takes style information and returns a Render object (aka renderer). These render objects will eventually be built into a Render tree.
- \4. Layout/reflow: The browser will determine the size and position of each node in the Render tree on the screen
- \5. Render the Render tree, drawing the page pixel information to the screen, this process is called paint, the page is displayed
** Therefore, ** When you manipulate the DOM with native JS or jquery libraries, the browser will start the process by building the DOM tree, so frequent manipulation of the DOM will cause unnecessary computation, resulting in page stutter and impacting the user experience. So what to do? So now you have the Virtual DOM. Virtual DOM can solve this problem very well. It uses javascript objects to represent virtual nodes (VNodes), calculates minimum changes to the real DOM based on the VNode, and then manipulates the real DOM nodes to improve rendering efficiency.
2. Redraw and rearrange
Resources: Do you really understand browser page rendering?
9.2 JSX
-
What is JSX:
JSX is an extension of JS, a javascript-based language that incorporates XML, so we can write XML in JS. To define a component by aggregating its structure, data, and even style.
ReactDOM.render( <h1>Hello</h1>, document.getElementById('root') );
Benefits:
- 1. Faster execution speed
- 2. Type safety
- 3. Development efficiency
-
Use the JSX element
JSX cannot be parsed directly by the browser, so a plugin is needed to parse JSX, such as 2.2.1 in React
- Will eventually passbabeljsTranslated into
createElement
grammar
ReactDOM.render(<h1>hello</h1>,document.getElementById('app')); Copy the code
The code parsed by Babel looks like this:
ReactDOM.render(React.createElement("h1", null, "hello"), document.getElementById('app')); Copy the code
- Will eventually passbabeljsTranslated into
-
JSX grammar
XML can be written in JS
1. XML can contain child elements, but there can be only one top-level element in the structure.Copy the code
ReactDOM.render(<h1>hello</h1><h2>world</h2>,document.getElementById('app')); Copy the code
Error:
ReactDOM.render(<div><h1>hello</h1><h2>world</h2><div>,document.getElementById('app')); Copy the code
- Support interpolation expressions: {} what can be placed inside: interpolation expressions: similar to ES6 template string ${expression} interpolation expressions syntax: {expression} (returns a result: value) in the expression if: type: empty, Boolean, undefined (no error, browser will not see the output does not output anything) object: Interpolation expressions cannot be directly output object, complains, but if the object is an array, such as browser {} [1, 2, 3] see is: 123 that is to say the react to turn the string array operations, and connect with an empty string, arr. Join (‘ ‘)
ReactDOM.render(<h1>hello</h1><h2>{ 1+2 }</h2>,document.getElementById('app')); Copy the code
React does not have template syntax. Interpolation only supports expressions, not statements: for, if;
But we can:
- in
if
orfor
Statement usingJSX
- It can be assigned to a variable, passed in as an argument, or returned as a value
Var users =,23,34 [12]; Reactdom.render (<div> <ul> {/** * generates a new array with a structure based on the contents of the array, each element must contain a key attribute, */ users.map((user,index)=>{return <li key={index}>{user}</li> }) } </ul> <div>,document.getElementById('app')); Copy the code
-
JSX properties
JSX tags can also support attribute Settings. Basic use and HTML /XML type, add attribute name = attribute value to tag, value must use “” include value is acceptable interpolation
And the attribute names need to be humped
Note: 1, the class attribute: use the className attribute instead of 2, the style: value must use the object
Example:
var idName = 'h2Id'; ReactDOM.render(<div> <h1 id="title">hello</h1> <h2 id={idName }>world</h2> <h2 style={ {color:'yellow'}}>style</h2> <h2 className="classA"> </h2> <div>, document.getelementById ('app')); Copy the code
9.3 、symbol
In vNode, we add the _type attribute to determine whether the object is a virtual node. We use symbol for the value. —- “Symbol — ES6 introduces a sixth primitive type, representing unique values.
Recall: The species data types in ES5 are: string, number, Boolean, NULL, and undefined.
1, create:
We can use the Symbol() function to generate the Symbol value.
The Symbol function takes an optional parameter to add a text describing the Symbol to be created. This description is not available for property access, but it is recommended that such a description be added each time a Symbol is created for reading the code and debugging the Symbol program
let firstName = Symbol("first name"); let person = {}; person[firstName] = "lee"; console.log("first name" in person); // false console.log(person[firstName]); // "lee" console.log(firstName); // "Symbol(first name)" Copy the code
Ps: Symbol’s Description is stored in the internal [[Description]] property, which can only be read when Symbol’s toString() method is called. FirstName’s toString() method is implicitly called when console.log() is executed, so its Description is printed to the log, but not directly accessible in the code [[Description]]
2, do not use the new command before the Symbol function, otherwise an error will be reported. Because the generated Symbol is a primitive value, it is not an object.
var sym = new Symbol(); // TypeError Copy the code
ES6 extends the typeof operator to return “Symbol”. So you can use typeof to check if the variable is of type Symbol
4, Symbol values are not equal, which means that the Symbol value can be used as an identifier for the object attribute name, which can ensure that there will not be attributes with the same name.
5, use [] instead of using the dot operator.
var mySymbol = Symbol(a);var a = {}; a.mySymbol = 'Hello! '; a.mySymbol // undefined a[mySymbol] / / "Hello!" Copy the code
6. When the Symbol value is used as the attribute name, the attribute is still public, not private, and can be accessed outside the class. But not in the for… In, for… Of the loop, will not be the Object. The keys (), Object. GetOwnPropertyNames () returns. If you want to read an Object Symbol attribute, can pass the Object. The getOwnPropertySymbols () and Reflect the ownKeys take to (), the return value is an array containing all of the Symbol has its own properties.
let syObject = {}; syObject[sy] = "kk"; console.log(syObject); for (let i in syObject) { console.log(i); } / / no output Object.keys(syObject); / / [] Object.getOwnPropertySymbols(syObject); // [Symbol(key1)] Reflect.ownKeys(syObject); // [Symbol(key1)] Copy the code
You can take a string as an argument to provide a description of the newly created Symbol that can be displayed on the console or used as a string for easy differentiation.
8, share the symbol value
Sometimes you want to share the same Symbol in different code, for example, if you have two different object types in your application, but you want them to use the same Symbol attribute to represent a unique identifier. In general, it is difficult and error-prone to trace symbols in a large code base or across files, and for these reasons, ES6 provides a global Symbol registry that can be accessed anytime.
Symbol.for() can generate the same Symbol
var a = Symbol('a'); var b = Symbol('a'); console.log(a===b); // false var a1 = Symbol.for('a'); var b1 = Symbol.for('a'); console.log(a1 === b1); //true Copy the code
let uid = Symbol.for("uid"); let object = {}; object[uid] = "12345"; console.log(object[uid]); // "12345" console.log(uid); // "Symbol(uid)" Copy the code
The symbol.for () method first searches the global Symbol registry to see if a Symbol with the key “uid” exists. If it exists, return the existing Symbol directly, otherwise, create a new Symbol and register it in the Symbol global registry using this key and return the newly created Symbol
let uid = Symbol.for("uid"); let object = { [uid]: "12345" }; console.log(object[uid]); / / "12345" console.log(uid); // "Symbol(uid)" let uid2 = Symbol.for("uid"); console.log(uid === uid2); // true console.log(object[uid2]); / / "12345" console.log(uid2); // "Symbol(uid) Copy the code
In this example, uid and UID2 contain the same Symbol and can be used interchangeably. The first call to the symbol.for () method creates the Symbol, and the second call retrieves the Symbol directly from the Symbol’s global registry
9. The value of Symbol cannot be converted implicitly, so it will return an error when evaluated against other types of values.
Can be explicitly or implicitly converted to Boolean, but not numeric.
var a = Symbol('a'); Boolean(a) // true if(a){ console.log(a); } // Symbol('a') Copy the code
Reference: developer.mozilla.org/zh-CN/docs/…
https://www.runoob.com/w3cnote/es6-symbol.html
Copy the code
9.4 Export default is different from export
(figure 9.4.1)
The reasons for this error are as follows:
1, is really not exported;
Export default {patch} = export default {patch}
Export default and export:
-
Export and export the default can be used in derived constants, function, file, module, etc., you can be in other documents or in the module through the import + (constant) | | | function file module name, import it
-
There can be more than export and import, but only one export default
-
Export export objects need {}, export default does not need {}
export const str = 'hello world' export function f(a){ return a+1 } Copy the code
Corresponding import methods:
import { str, f } from 'demo1' // You can also write it twice, importing it with curly braces Copy the code
export default
export default const str = 'hello world' Copy the code
Corresponding import mode
import str from 'demo1'// Import without curly bracesCopy the code
-
Use the export default command to specify the default output for the module, so that we do not need to know the variable name of the module to be loaded. When we import, we can use any name
//demo.js let str = 'hello world'; export defaultSTR (STR cannot be parenthetical)// Export STR without default. However, a file can contain only one export Default at most. // The value of STR variable "hello world" is the default variable name of the system. Naturally, default can only have one value, so a file cannot have multiple export defaults. Copy the code
Corresponding import mode
import any from "./demo.js" import any12 from "./demo.js" console.log(any,any12) // hello world,hello world Copy the code