浅析Vue响应系统原理与搭建Vue2.x迷你版

开发 前端
数据模型仅仅是普通的JavaScript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率。简言之,在改变数据的时候,视图会跟着更新。

[[407211]]

Vue2.x响应式原理怎么实现的?

Vue 最独特的特性之一,是其非侵入性的响应式系统。那么什么是响应式原理?

数据模型仅仅是普通的JavaScript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率。简言之,在改变数据的时候,视图会跟着更新。

了解概念之后,那么它是怎么实现的呢?

其实是利用Object.defineProperty()中的getter 和setter方法和设计模式中的观察者模式。

那么,我们先来看下Object.defineProperty()。MDN中它是这样解释它的:Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

  1. let data = { 
  2.  msg:'hello' 
  3. }; 
  4.  
  5. let vm = {}; 
  6.  
  7. Object.defineProperty(vm, 'msg', { 
  8.         enumerable: true, // 可枚举(可遍历) 
  9.         configurable: true, // 可配置(可以使用delete 删除,可以通过defineProperty重新定义) 
  10.  
  11.         // 当获取值的时候执行 
  12.         get() { 
  13.             console.log('get', data.msg); 
  14.             return data.msg 
  15.         }, 
  16.         // 当设置值的时候执行 
  17.         set(newVal) { 
  18.             if (newVal === data.msg) { 
  19.                 return 
  20.             } 
  21.             data.msg = newVal; 
  22.             console.log('set', data.msg); 
  23.         } 
  24. }) 
  25.  
  26. // 测试 
  27.  
  28. console.log(vm.msg); 
  29. /*  
  30. "get" "hello" 
  31. "hello" 
  32. */ 
  33. vm.msg = 'world'; // > "set" "world" 

简单介绍Object.defineProperty()之后,接着就是了解观察者模式,看到它,你可能会想起发布-订阅模式。其实它们的本质是相同的,但是也存在一定的区别。

我们不妨先来看下发布-订阅模式。

发布-订阅者模式里面包含了三个模块,发布者,订阅者和统一调度中心。这里统一调度中心相当于报刊办事大厅。发布者相当与某个杂志负责人,他来中心这注册一个的杂志,而订阅者相当于用户,我在中心订阅了这分杂志。每当发布者发布了一期杂志,办事大厅就会通知订阅者来拿新杂志。发布-订阅者模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。

下面,我们将通过一个实现Vue自定义事件的例子来更进一步了解发布-订阅模式。

  1. function EventEmitter(){ 
  2.     // 初始化统一调度中心 
  3.     this.subs = Object.create(null); // {'click':[fn1,fn2]} 
  4. // 注册事件 
  5. EventEmitter.prototype.$on = function (eventType,handler){ 
  6.         console.log(this); 
  7.         this.subs[eventType]= this.subs[eventType]||[]; 
  8.         this.subs[eventType].push(handler); 
  9. // 触发事件 
  10. EventEmitter.prototype.$emit = function (eventType,data){ 
  11.         if(this.subs[eventType]){ 
  12.                 this.subs[eventType].forEach(handler => { 
  13.                     handler(data); 
  14.                 }); 
  15.         } 
  16.  
  17. // 测试 
  18. const em = new EventEmitter(); 
  19.  
  20. //订阅者 
  21. em.$on('click1',(data)=>{ 
  22.     console.log(data); 
  23. }) 
  24. // 发布者 
  25. em.$emit('click1','maomin') //maomin 

这种自定义事件广泛应用于Vue同级组件传值。

接下来,我们来介绍观察者模式。

观察者模式是由目标调度,比如当事件触发时,目标就会调用观察者的方法,所以观察者模式的订阅者(观察者)与发布者(目标)之间存在依赖。

  1. // 发布者(目标) 
  2. function Dep(){ 
  3.     this.subs = []; 
  4. Dep.prototype.addSub = function (sub){ 
  5.     if(sub&&sub.update){ 
  6.             this.subs.push(sub); 
  7.     } 
  8.  
  9. Dep.prototype.notify = function (data){ 
  10.         this.subs.forEach(sub=>{ 
  11.             sub.update(data); 
  12.         }) 
  13. // 订阅者(观察者) 
  14. function Watcher(){} 
  15.     Watcher.prototype.update=function(data){ 
  16.     console.log(data); 
  17.  
  18. // 测试 
  19. let dep = new Dep(); 
  20. let watcher = new Watcher(); 
  21. // 收集依赖 
  22. dep.addSub(watcher); 
  23. // 发送通知 
  24. dep.notify('1'); 
  25. dep.notify('2'); 

下图是区分两种模式。

实现Vue2.x迷你版本

为什么要实现一个Vue迷你版本,目的就是加深对Vue响应式原理以及其中一些API的理解。首先我们先来分析Vue2.x 响应式原理的整体结构。

如下图所示:

我们接下来,将根据这幅图片描述的流程来实现一款迷你版Vue。Vue2.x采用了Virtual DOM,但是因为这里只需要实现一个迷你版,所以我们这里做了简化,我们这里就是直接操作DOM。

下面,我们来看下我是如何搭建一款Vue mini的。

第一步

页面结构如下,我们可以先引入Vue2.x完整版本,看下实现效果。

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3.  
  4. <head> 
  5.     <meta charset="UTF-8"
  6.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  7.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  8.     <title>Vue2.x Reactive</title> 
  9. </head> 
  10.  
  11. <body> 
  12.     <div id="app"
  13.         <h2>文本节点</h2> 
  14.         <div>{{msg}}</div> 
  15.         <div>{{count}}</div> 
  16.         <div>{{obj.name}}</div> 
  17.         <div>{{arr[0]}}</div> 
  18.         <div>{{obj.inner.age}}</div> 
  19.         <div>{{obj.inner.name}}</div> 
  20.         <h2>v-text</h2> 
  21.         <div v-text="msg"></div> 
  22.         <h2>v-model</h2> 
  23.         <input type="text" v-model="msg"
  24.         <input type="text" v-model="count"
  25.         <h2>v-html</h2> 
  26.         <div v-html="html"></div> 
  27.         <h2>v-show</h2> 
  28.         <div v-show="isShow">{{isShow}}</div> 
  29.         <h2>v-on</h2> 
  30.         <button v-on:click="handler">handler</button> 
  31.         <button @click="onClick">onClick</button> 
  32.         <h2>v-if</h2> 
  33.         <div> 
  34.             <p v-if="isIf">{{isIf}}</p> 
  35.         </div> 
  36.     </div> 
  37.     <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script> 
  38.     <script> 
  39.         const vm = new Vue({ 
  40.                 el: '#app'
  41.                 data() { 
  42.                     return { 
  43.                         msg: 'maomin'
  44.                         count: 1, 
  45.                         obj: { 
  46.                             name'hello'
  47.                             inner: { 
  48.                                 age: 17 
  49.                             } 
  50.                         }, 
  51.                         arr: ['string1'], 
  52.                         html: '<div>{{msg}}</div>'
  53.                         isShow: false
  54.                         isIf:true 
  55.                     } 
  56.                 }, 
  57.                 methods: { 
  58.                     handler() { 
  59.                         // this.count = 2; 
  60.                         this.isIf = !this.isIf; 
  61.                     }, 
  62.                     onClick() { 
  63.                         this.obj.inner.age = 18; 
  64.                         // console.log(this.obj.inner.age); 
  65.                     } 
  66.                 } 
  67.             }); 
  68.     </script> 
  69. </body> 
  70.  
  71. </html> 

 经过测试,Vue2.x完整版搭载的页面显示如下。我们将使用Vue迷你版本同样实现以下页面效果。

 

第二步

我们将根据整体结构图和页面结构来搭建这个Vue迷你版本,我们姑且将这个版本叫做vuemini.js。

通过整体结构图我们发现,一共有Vue、Observer、Compiler、Dep、Watcher这几个构造函数。我们首先创建这几个构造函数,这里不使用class类来定义是因为Vue源码大部分也使用构造函数,另外,相对也好拓展。

Vue

  1. // 实例。 
  2. function Vue(options) { 
  3.     this.$options = options || {}; 
  4.     this._data = typeof options.data === 'function' ? options.data() : options.data || {}; 
  5.     this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el; 
  6.     // 负责把data中的属性注入到Vue实例,转换成getter/setter 
  7.     this._proxyData(this._data); 
  8.     this.initMethods(this, options.methods || {}) 
  9.     // 负责调用observer监听data中所有属性的变化 
  10.     new Observer(this._data); 
  11.     // 负责调用compiler解析指令/插值表达式 
  12.     new Compiler(this); 
  13. // 将data中的属性挂载到this上 
  14. Vue.prototype._proxyData = function (data) { 
  15.     Object.keys(data).forEach(key => { 
  16.         Object.defineProperty(this, key, { 
  17.             configurable: true
  18.             enumerable: true
  19.             get() { 
  20.                 return data[key
  21.             }, 
  22.             set(newVal) { 
  23.                 if (newVal === data[key]) { 
  24.                     return 
  25.                 } 
  26.                 data[key] = newVal; 
  27.             } 
  28.         }) 
  29.     }) 
  30.  
  31. function noop(a, b, c) { } 
  32. function polyfillBind(fn, ctx) { 
  33.     function boundFn(a) { 
  34.         var l = arguments.length; 
  35.         return l 
  36.             ? l > 1 
  37.                 ? fn.apply(ctx, arguments) 
  38.                 : fn.call(ctx, a) 
  39.             : fn.call(ctx) 
  40.     } 
  41.  
  42.     boundFn._length = fn.length; 
  43.     return boundFn 
  44. function nativeBind(fn, ctx) { 
  45.     return fn.bind(ctx) 
  46. const bind = Function.prototype.bind 
  47.     ? nativeBind 
  48.     : polyfillBind; 
  49.  
  50. // 初始化methods属性 
  51. Vue.prototype.initMethods = function (vm, methods) { 
  52.     for (var key in methods) { 
  53.         { 
  54.             if (typeof methods[key] !== 'function') { 
  55.                 warn( 
  56.                     "Method \"" + key + "\" has type \"" + (typeof methods[key]) + "\" in the component definition. " + 
  57.                     "Did you reference the function correctly?"
  58.                     vm 
  59.                 ); 
  60.             } 
  61.         } 
  62.         vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm); 
  63.     } 

Observer

  1. // 数据劫持。 
  2. // 负责把data(_data)选项中的属性转换成响应式数据。 
  3. function Observer(data) { 
  4.     this.walk(data); 
  5. Observer.prototype.walk = function (data) { 
  6.     if (!data || typeof data !== 'object') { 
  7.         return 
  8.     } 
  9.     Object.keys(data).forEach(key => { 
  10.         this.defineReactive(data, key, data[key]); 
  11.     }) 
  12. Observer.prototype.defineReactive = function (obj, key, val) { 
  13.     let that = this; 
  14.     // 负责收集依赖 
  15.     let dep = new Dep(); 
  16.     // 如果val是对象,把val内部的属性转换成响应式数据 
  17.     this.walk(val); 
  18.     Object.defineProperty(obj, key, { 
  19.         enumerable: true
  20.         configurable: true
  21.         get() { 
  22.             // 收集依赖 
  23.             Dep.target && dep.addSub(Dep.target) 
  24.             return val 
  25.         }, 
  26.         set(newVal) { 
  27.             if (newVal === val) { 
  28.                 return 
  29.             } 
  30.             val = newVal; 
  31.             // data内属性重新赋值后,使其转化为响应式数据。 
  32.             that.walk(newVal); 
  33.             // 发送通知 
  34.             dep.notify(); 
  35.         } 
  36.     }) 

Compiler

  1. // 编译模板,解析指令/插值表达式 
  2. // 负责页面的首次渲染 
  3. // 当数据变化时重新渲染视图 
  4. function Compiler(vm) { 
  5.     this.el = vm.$el; 
  6.     this.vm = vm; 
  7.     // 立即编译模板 
  8.     this.compile(this.el); 
  9. // 编译模板,处理文本节点和元素节点 
  10. Compiler.prototype.compile = function (el) { 
  11.     let childNodes = el.childNodes; 
  12.     Array.from(childNodes).forEach(node => { 
  13.         // 处理文本节点 
  14.         if (this.isTextNode(node)) { 
  15.             this.compileText(node); 
  16.         } 
  17.         // 处理元素节点  
  18.         else if (this.isElementNode(node)) { 
  19.             this.compileElement(node); 
  20.         } 
  21.         // 判断node节点,是否有子节点,如果有子节点,要递归调用compile方法 
  22.         if (node.childNodes && node.childNodes.length) { 
  23.             this.compile(node); 
  24.         } 
  25.     }) 
  26. // 编译文本节点,处理插值表达式 
  27. Compiler.prototype.compileText = function (node) { 
  28.     // console.dir(node); 
  29.     let reg = /\{\{(.+?)\}\}/; 
  30.     let value = node.textContent; 
  31.     if (reg.test(value)) { 
  32.         let key = RegExp.$1.trim(); 
  33.         if (this.vm.hasOwnProperty(key)) { 
  34.             node.textContent = value.replace(reg, typeof this.vm[key] === 'object' ? JSON.stringify(this.vm[key]) : this.vm[key]); 
  35.             // 创建watcher对象,当数据改变更新视图 
  36.             new Watcher(this.vm, key, (newVal) => { 
  37.                 node.textContent = newVal; 
  38.             }) 
  39.         } else { 
  40.             const str = `this.vm.${key}`; 
  41.             node.textContent = value.replace(reg, eval(str)); 
  42.             // 创建watcher对象,当数据改变更新视图 
  43.             new Watcher(this.vm, key, () => { 
  44.                 const strw = `this.vm.${key}`; 
  45.                 node.textContent = value.replace(reg, eval(strw)); 
  46.             }) 
  47.         } 
  48.  
  49.  
  50.     } 
  51. // 判断节点是否是文本节点 
  52. Compiler.prototype.isTextNode = function (node) { 
  53.     return node.nodeType === 3; 
  54. // 判断节点是否是元素节点 
  55. Compiler.prototype.isElementNode = function (node) { 
  56.     return node.nodeType === 1; 
  57.  
  58. // 编译元素节点,处理指令 
  59. Compiler.prototype.compileElement = function (node) { 
  60.     // console.log(node.attributes); 
  61.  
  62.     // 遍历所有的属性节点 
  63.     Array.from(node.attributes).forEach(attr => { 
  64.         let attrName = attr.name
  65.         // console.log(attrName); 
  66.         // 判断是否是指令 
  67.         if (this.isDirective(attrName)) { 
  68.             // 判断:如v-on:click 
  69.             let eventName; 
  70.             if (attrName.indexOf(':') !== -1) { 
  71.                 const strArr = attrName.substr(2).split(':'); 
  72.                 attrName = strArr[0]; 
  73.                 eventName = strArr[1]; 
  74.             } else if (attrName.indexOf('@') !== -1) { 
  75.                 eventName = attrName.substr(1); 
  76.                 attrName = 'on'
  77.             } else { 
  78.                 attrName = attrName.substr(2); 
  79.             } 
  80.             let key = attr.value; 
  81.  
  82.             this.update(node, key, attrName, eventName); 
  83.         } 
  84.     }) 
  85. // 判断元素属性是否是指令 
  86. Compiler.prototype.isDirective = function (attrName) { 
  87.     return attrName.startsWith('v-') || attrName.startsWith('@'); 
  88. // 指令辅助函数 
  89. Compiler.prototype.update = function (node, key, attrName, eventName) { 
  90.     let updateFn = this[attrName + 'Updater']; 
  91.     updateFn && updateFn.call(this, node, this.vm[key], key, eventName); 
  92. // 处理v-text指令 
  93. Compiler.prototype.textUpdater = function (node, value, key) { 
  94.     node.textContent = value; 
  95.     new Watcher(this.vm, key, (newVal) => { 
  96.         node.textContent = newVal; 
  97.     }) 
  98. // 处理v-html指令 
  99. Compiler.prototype.htmlUpdater = function (node, value, key) { 
  100.     node.insertAdjacentHTML('beforeend', value); 
  101.     new Watcher(this.vm, key, (newVal) => { 
  102.         node.insertAdjacentHTML('beforeend', newVal); 
  103.     }) 
  104. // 处理v-show指令 
  105. Compiler.prototype.showUpdater = function (node, value, key) { 
  106.     !value ? node.style.display = 'none' : node.style.display = 'block' 
  107.     new Watcher(this.vm, key, (newVal) => { 
  108.         !newVal ? node.style.display = 'none' : node.style.display = 'block'
  109.     }) 
  110. // 处理v-if指令 
  111. Compiler.prototype.ifUpdater = function (node, value, key) { 
  112.     const nodew = node; 
  113.     const nodep = node.parentNode; 
  114.     if (!value) { 
  115.         node.parentNode.removeChild(node) 
  116.     } 
  117.     new Watcher(this.vm, key, (newVal) => { 
  118.         console.log(newVal); 
  119.         !newVal ? nodep.removeChild(node) : nodep.appendChild(nodew); 
  120.     }) 
  121. // 处理v-on指令 
  122. Compiler.prototype.onUpdater = function (node, value, key, eventName) { 
  123.     if (eventName) { 
  124.         const handler = this.vm.$options.methods[key].bind(this.vm); 
  125.         node.addEventListener(eventName, handler); 
  126.     } 
  127. // 处理v-model指令 
  128. Compiler.prototype.modelUpdater = function (node, value, key) { 
  129.     node.value = value; 
  130.     new Watcher(this.vm, key, (newVal) => { 
  131.         node.value = newVal; 
  132.     }) 
  133.     // 双向绑定,视图变化更新数据 
  134.     node.addEventListener('input', () => { 
  135.         this.vm[key] = node.value; 
  136.     }) 

Dep

  1. // 发布者。 
  2. // 收集依赖,添加所有的观察者(watcher)。通知所有的观察者。 
  3. function Dep() { 
  4.     // 存储所有的观察者watcher 
  5.     this.subs = []; 
  6. // 添加观察者 
  7. Dep.prototype.addSub = function (sub) { 
  8.     if (sub && sub.update) { 
  9.         this.subs.push(sub); 
  10.     } 
  11. // 发送通知 
  12. Dep.prototype.notify = function () { 
  13.     this.subs.forEach(sub => { 
  14.         sub.update(); 
  15.     }) 

Watcher

  1. function Watcher(vm, key, cb) { 
  2.     this.vm = vm; 
  3.     this.key = key
  4.     this.cb = cb; 
  5.     // 把当前watcher对象记录到Dep类的静态属性target 
  6.     Dep.target = this; 
  7.     if (vm.hasOwnProperty(key)) { 
  8.         this.oldVal = vm[key]; 
  9.     } else { 
  10.         const str = `vm.${key}`; 
  11.         this.oldVal = eval(str); 
  12.     } 
  13.     Dep.target = null
  14. // 当数据发生变化的时候更新视图 
  15. Watcher.prototype.update = function () { 
  16.     let newVal; 
  17.     if (this.vm.hasOwnProperty(this.key)) { 
  18.         newVal = this.vm[this.key]; 
  19.     } else { 
  20.         const str = `this.vm.${this.key}`; 
  21.         newVal = eval(str); 
  22.     } 
  23.     this.cb(newVal); 

以上这几个构造函数就实现了我们所说的迷你版本,将它们整合成一个文件vuemini.js。在上面所提示的页面引入,查看效果。

另外,我在data中绑定了一个html属性,值为一个'<div>{{msg}}</div>',与之前完整版相比,图中的v-html下方的maomin文本也被渲染出来。

尤大开发的Vue2.x迷你版本

下面,我们将看下尤大开发的迷你版本,这个版本引入了Virtual DOM,但是主要是针对响应式式原理的,可以根据尤大的迷你版本与上面的版本作个比较,可以看下有哪些相似之处。

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3.  
  4. <head> 
  5.     <meta charset="UTF-8"
  6.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  7.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  8.     <title>vue2mini</title> 
  9. </head> 
  10.  
  11. <body> 
  12.     <div id="app"></div> 
  13.     <script> 
  14.         // reactivity --- 
  15.         let activeEffect 
  16.  
  17.         class Dep { 
  18.             subscribers = new Set() 
  19.             depend() { 
  20.                 if (activeEffect) { 
  21.                     this.subscribers.add(activeEffect) 
  22.                 } 
  23.             } 
  24.             notify() { 
  25.                 this.subscribers.forEach(effect => effect()) 
  26.             } 
  27.         } 
  28.  
  29.         function watchEffect(effect) { 
  30.             activeEffect = effect 
  31.             effect() 
  32.             activeEffect = null 
  33.         } 
  34.  
  35.         function reactive(raw) { 
  36.             // use Object.defineProperty 
  37.             // 1. iterate over the existing keys 
  38.             Object.keys(raw).forEach(key => { 
  39.                 // 2. for each keycreate a corresponding dep 
  40.                 const dep = new Dep() 
  41.  
  42.                 // 3. rewrite the property into getter/setter 
  43.                 let realValue = raw[key
  44.                 Object.defineProperty(raw, key, { 
  45.                     get() { 
  46.                         // 4. call dep methods inside getter/setter 
  47.                         dep.depend() 
  48.                         return realValue 
  49.                     }, 
  50.                     set(newValue) { 
  51.                         realValue = newValue 
  52.                         dep.notify() 
  53.                     } 
  54.                 }) 
  55.             }) 
  56.             return raw 
  57.         } 
  58.  
  59.         // vdom --- 
  60.         function h(tag, props, children) { 
  61.             return { tag, props, children }; 
  62.         } 
  63.  
  64.         function mount(vnode, container, anchor) { 
  65.             const el = document.createElement(vnode.tag); 
  66.             vnode.el = el; 
  67.             // props 
  68.             if (vnode.props) { 
  69.                 for (const key in vnode.props) { 
  70.                     if (key.startsWith('on')) { 
  71.                         el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key]) 
  72.                     } else { 
  73.                         el.setAttribute(key, vnode.props[key]); 
  74.                     } 
  75.                 } 
  76.             } 
  77.             if (vnode.children) { 
  78.                 if (typeof vnode.children === "string") { 
  79.                     el.textContent = vnode.children; 
  80.                 } else { 
  81.                     vnode.children.forEach(child => { 
  82.                         mount(child, el); 
  83.                     }); 
  84.                 } 
  85.             } 
  86.             if (anchor) { 
  87.                 container.insertBefore(el, anchor) 
  88.             } else { 
  89.                 container.appendChild(el); 
  90.             } 
  91.         } 
  92.  
  93.         function patch(n1, n2) { 
  94.             // Implement this 
  95.             // 1. check if n1 and n2 are of the same type 
  96.             if (n1.tag !== n2.tag) { 
  97.                 // 2. if notreplace 
  98.                 const parent = n1.el.parentNode 
  99.                 const anchor = n1.el.nextSibling 
  100.                 parent.removeChild(n1.el) 
  101.                 mount(n2, parent, anchor) 
  102.                 return 
  103.             } 
  104.  
  105.             const el = n2.el = n1.el 
  106.  
  107.             // 3. if yes 
  108.             // 3.1 diff props 
  109.             const oldProps = n1.props || {} 
  110.             const newProps = n2.props || {} 
  111.             for (const key in newProps) { 
  112.                 const newValue = newProps[key
  113.                 const oldValue = oldProps[key
  114.                 if (newValue !== oldValue) { 
  115.                     if (newValue != null) { 
  116.                         el.setAttribute(key, newValue) 
  117.                     } else { 
  118.                         el.removeAttribute(key
  119.                     } 
  120.                 } 
  121.             } 
  122.             for (const key in oldProps) { 
  123.                 if (!(key in newProps)) { 
  124.                     el.removeAttribute(key
  125.                 } 
  126.             } 
  127.             // 3.2 diff children 
  128.             const oc = n1.children 
  129.             const nc = n2.children 
  130.             if (typeof nc === 'string') { 
  131.                 if (nc !== oc) { 
  132.                     el.textContent = nc 
  133.                 } 
  134.             } else if (Array.isArray(nc)) { 
  135.                 if (Array.isArray(oc)) { 
  136.                     // array diff 
  137.                     const commonLength = Math.min(oc.length, nc.length) 
  138.                     for (let i = 0; i < commonLength; i++) { 
  139.                         patch(oc[i], nc[i]) 
  140.                     } 
  141.                     if (nc.length > oc.length) { 
  142.                         nc.slice(oc.length).forEach(c => mount(c, el)) 
  143.                     } else if (oc.length > nc.length) { 
  144.                         oc.slice(nc.length).forEach(c => { 
  145.                             el.removeChild(c.el) 
  146.                         }) 
  147.                     } 
  148.                 } else { 
  149.                     el.innerHTML = '' 
  150.                     nc.forEach(c => mount(c, el)) 
  151.                 } 
  152.             } 
  153.         } 
  154.  
  155.         // paste all previous code from Codepen 
  156.         const app = { 
  157.             data: reactive({ 
  158.                 count: 0 
  159.             }), 
  160.             render() { 
  161.                 return h('div', { 
  162.                     onClick: () => { 
  163.                         app.data.count++ 
  164.                     } 
  165.                 }, String(app.data.count)) 
  166.             } 
  167.         } 
  168.  
  169.         function mountApp(component, selector) { 
  170.             let isMounted = false 
  171.             let oldTree 
  172.             watchEffect(() => { 
  173.                 if (!isMounted) { 
  174.                     mount(oldTree = component.render(), document.querySelector(selector)) 
  175.                     isMounted = true 
  176.                 } else { 
  177.                     const newTree = component.render() 
  178.                     patch(oldTree, newTree) 
  179.                     oldTree = newTree 
  180.                 } 
  181.             }) 
  182.         } 
  183.  
  184.         mountApp(app, '#app'
  185.     </script> 
  186. </body> 
  187.  
  188. </html> 

 【编辑推荐】

 

责任编辑:姜华 来源: 前端历劫之路
相关推荐

2020-10-20 18:42:17

Vue 3.0vue2.x数据

2021-04-02 11:24:22

Vue2.x双向绑定前端

2017-09-27 14:46:37

Vue2.xDOM diff原理

2011-12-18 12:36:59

摩托

2020-06-09 11:35:30

Vue 3响应式前端

2019-07-01 13:34:22

vue系统数据

2021-01-22 11:47:27

Vue.js响应式代码

2022-03-29 09:59:58

响应式系统Vue2

2017-08-30 17:10:43

前端JavascriptVue.js

2022-04-03 19:27:35

Vue2响应式系统

2011-06-29 18:11:33

笔记本评测

2022-04-14 08:46:46

响应式系统js

2022-04-02 09:56:41

Vue2响应式系统

2021-06-21 07:36:32

Virtual DOMDOMvue.js

2024-04-10 08:45:51

Vue 3Proxy对象监测数据

2022-04-06 07:28:47

数组响应式系统

2011-11-08 17:28:26

美信云网管

2021-05-19 14:25:19

前端开发技术

2021-09-27 06:29:47

Vue3 响应式原理Vue应用

2022-03-31 10:15:10

分支切换响应式系统
点赞
收藏

51CTO技术栈公众号