Virtual dom

The virtual DOM represents a tree of javascript objects for the real DOM, making it easy to update the DOM in bulk

General performance of VNodes

{
    tag: "div",
    children: [
        {
            text: "hi vNode"
        }
    ]
}
Copy the code

How do I become a real DOM

Vue uses the render function to convert the template into a vNode. Once the node is changed, the render function runs again to generate a new vNode. Vue compares the old and new vNodes to make modifications.

The core module

  1. Response: Create a javascript object and listen
  2. Compile: Get the HTML template to generate the render function
  3. Apply colours to a drawing
  • Render: Trigger render function to return vNode,
  • Mount: Mount to a mount
  • Patch: Send path to new and old nodes to compare the update

Custom simple mount

// function h(props, props,children) {return {tag,props, Function mount(vNode, container) {const {tag, props, children} = vNode const el = document.createElement(tag) // props if (props) { for(let key in props) { if (! key.startsWith('on')) { el.setAttribute(key, props[key]) } } } // children if (Array.isArray(children)) { children.forEach(child => { mount(child, AppendChild (el)} else {el.textContent = children} container. AppendChild (el)} const vDom = h('div', {class: 'red'}, [ h('span', null, 'hello vue h function') ]) mount(vDom, document.getElementById('app'))Copy the code

Customize a simple path

Path here does the simplest processing, no special optimization

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta < span style> < span style>. Color: red; } .blue { color: blue; } </style> </head> <body> <div id="app"></div> <script> Function mount(vNode, container) {const {tag, props, props, Children} = vNode // vnode.el save node, Const el = vnode. el = document.createElement(tag) {for(let key in props) {if (! key.startsWith('on')) { el.setAttribute(key, props[key]) } } } // children if (Array.isArray(children)) { children.forEach(child => { mount(child, AppendChild (el)} else {el.textContent = children} container. AppendChild (el)} const vDom = h('div', {class: }, [h('div', null, 'hello vue h function')]) mount(vDom, document.getelementById ('app')) Function patch(n1, n2) {if (n1.tag === n2.tag) {const el = n2.el = n1.el; // props, // new porps === null, old props! == null // new porps ! If == null, old props === null == null, new porps! == null // props Whether equal const oldProps = n1. Props | | {} const newProps = n2 props | | {} / / compare properties for (let key in newProps) {const oldVal = OldProps [key] const newVal = newProps[key] // Unequalprops () if (newVal! == oldVal) {el.setattribute (key, newVal)}} for (let key in oldProps) { (key in newProps) {el.removeAttribute(key)}} // Compare children const oldChildren = n1. Children const newChildren = N2. children if (typeof oldChildren === 'string') {// Replace if (typeof oldChildren === 'string') {if (newChildren! == oldChildren) { el.textContent = newChildren } } else { el.textContent = newChildren } } else { if (typeof oldChildren === 'string') {el.innerhtml = 'newchildren. forEach(child => {mount(child, el)})} else { Write a simple // now compare the common length of children, Const commonLen = math. min(newchildren.length, oldchildren.length) // common for(let I = 0; i < commonLen; i++) { patch(oldChildren[i], NewChildren [I])} // newChildren unique if (newchildren.length > oldchildren.length) { newChildren.splice(oldChildren.length).forEach(child => { mount(child, el) }) } if (newChildren.length < oldChildren.length) { oldChildren.splice(newChildren).forEach(child => { El.removechild (child)})}}}} else {// } } const vDom2 = h('div', {class: 'blue'}, [ h('div', null, [h('span', null, 'hello path')]) ]) patch(vDom, vDom2) </script> </body> </html>Copy the code

Simply keep track of updates

Manual to inform

Subscribers = new Set() // Collect depend() {if (subscribers) = New Set() // Collect depend() {if (subscribers) = new Set() (activeEffect) {this.subscribers. Add (activeEffect)}} notify() {this.subscribers. ForEach (effect => {effect() Function watchEffect(effect) {activeEffect = effect effect() activeEffect = null} const dep = new dep () WatchEffect (() => {dep.depend() // track console.log('=====effect')}) // trigger dep.notify() // notify, Effect </script> is triggered againCopy the code

A little optimization to make it a little smarter, no need for manual notification, add data change function

Class Dep {constructor(value) {subscribers = new Set() this._value = Get value() {// trigger this.depend() // notification, Return this._value} set value(newVal) {this._value = newVal this.notify()} // collect dependent depend() {if (activeEffect) {this.subscribers. Add (activeEffect)}} notify() {this.subscribers. ForEach (effect => {effect() Function watchEffect(effect) {activeEffect = effect effect() activeEffect = null} const ok = new Dep(false) const msg = new Dep('hello') watchEffect(() => { if (ok.value) { console.log('=====effect', MSG. Value)} else {console.log(' do not trace ')}})Copy the code

Implement a simple Reactive

Subscribers = new Set() subscribers depend() {if (Subscribers) {class Dep = New Set() subscribers depend() {if (Subscribers) { This.subscribers. Add (activeEffect)}} // Notify () {this.subscribers. ForEach (effect => {effect()})}} // target Finally, each target object serves as a key in the map, and each target.get(key) corresponds to a map(map stores the target object's attributes, corresponding DEP relation). Const targetMap = new WeakMap() function getDep (target, key) {let depsMap = targetmap. get(target) if (! depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } let dep = depsMap.get(key) if (! dep) { dep = new Dep() depsMap.set(key, dep) } return dep } const reactiveHandlers = { get(target, key, receiver){ console.log('===target', target) console.log('==key',key) const dep = getDep(target, key) dep.depend() return Reflect.get(target, key, receiver) }, set(target, key, value, receiver){ const dep = getDep(target, key) const result = Reflect.set(target, key, value, receiver) dep.notify() return result } } function reactive(obj) { return new Proxy(obj, reactiveHandlers) } const state = reactive({ count: 1, age: Function watchEffect(effect) {activeEffect = effect() activeEffect = null} watchEffect(() => { console.log('=====effect', state.count) }) state.count++Copy the code

Implement a Mini-vUE

Now that you have the reactive path function, you can combine the two to create a mini-vue

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta  name="viewport" content="width=device-width, Initial-scale =1.0"> <title>Document</title> </head> <body> <div id="app"></div> <script> function h(tag, props, children) { return { tag, props, children } } function mount(vNode, container) { const {tag, props, Children} = vNode // vnode.el save node, Const el = vnode. el = document.createElement(tag) {for(let key in props) {if (! key.startsWith('on')) { el.setAttribute(key, props[key]) } else { el.addEventListener(key.slice(2).toLowerCase(), props[key]) } } } // children if (Array.isArray(children)) { children.forEach(child => { mount(child, el) }) } else { el.textContent = children } container.appendChild(el) } function patch(n1, n2) { if (n1.tag === n2.tag) { const el = n2.el = n1.el; Const oldProps = n1. Props | | {} const newProps = n2 props | | {} / / compare properties for (let key in newProps) {const oldVal = oldProps[key] const newVal = newProps[key] if (newVal ! == oldVal) { el.setAttribute(key, newVal) } } for (let key in oldProps) { if (! (key in newProps) {el.removeAttribute(key)}} // Compare children const oldChildren = n1. Children const newChildren = n2.children if (typeof newChildren === 'string') { if (typeof oldChildren === 'string') { if (newChildren ! == oldChildren) { el.textContent = newChildren } } else { el.textContent = newChildren } } else { if (typeof oldChildren  === 'string') { el.innerHTML = '' newChildren.forEach(child => { mount(child, el) }) } else { const commonLen = Math.min(newChildren.length, oldChildren.length) for(let i = 0; i < commonLen; i++) { patch(oldChildren[i], newChildren[i]) } if (newChildren.length > oldChildren.length) { newChildren.splice(oldChildren.length).forEach(child =>  { mount(child, el) }) } if (newChildren.length < oldChildren.length) { oldChildren.splice(newChildren).forEach(child => { El.removechild (child)})}}}} else {// Subscribers = new Set() subscribers depend() {if (subscribers) {if (subscribers) { This.subscribers. Add (activeEffect)}} // Trigger notify() {this.subscribers. ForEach (effect => {effect()})}} const targetMap = new WeakMap() function getDep (target, key) { let depsMap = targetMap.get(target) if (! depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } let dep = depsMap.get(key) if (! dep) { dep = new Dep() depsMap.set(key, dep) } return dep } const reactiveHandlers = { get(target, key, receiver){ const dep = getDep(target, key) dep.depend() return Reflect.get(target, key, receiver) }, set(target, key, value, receiver){ const dep = getDep(target, key) const result = Reflect.set(target, key, value, receiver) dep.notify() return result } } function reactive(obj) { return new Proxy(obj, reactiveHandlers) } function watchEffect(effect) { activeEffect = effect effect() activeEffect = null } const App = { data: reactive({count: 1}), render() { return h('div', {onClick: () => { this.data.count++ }}, String(this.data.count) ) } } function mountApp(component, container) { let isMounted = false; let oldVdom watchEffect(() => { if (! isMounted) { oldVdom = component.render() mount(oldVdom, container) isMounted = true } else { const newVdom = component.render() patch(oldVdom, newVdom) oldVdom = newVdom } }) } mountApp(App, document.getElementById('app')) </script> </body> </html>Copy the code