Vue 响应式原理模拟以及最小版本的 Vue的模拟

在模拟最小的vue之前,先复习一下,发布订阅模式和观察者模式

对两种模式有了了解之后,对Vue2.0和Vue3.0的数据响应式核心原理

1.Vue2.0和Vue3.0的数据响应式核心原理

(1).  Vue2.0是采用Object.defineProperty的方式,对数据进行get,set方法设置的, 具体可以详见Object.defineProperty的介绍

浏览器兼容 IE8 以上(不兼容 IE8)
<script>     // 模拟 Vue 中的 data 选项     let data = {       msg: 'hello'     }      // 模拟 Vue 的实例     let vm = {}      // 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作     Object.defineProperty(vm, 'msg', {       // 可枚举(可遍历)       enumerable: true,       // 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)       configurable: true,       // 当获取值的时候执行       get () {         console.log('get: ', data.msg)         return data.msg       },       // 当设置值的时候执行       set (newValue) {         console.log('set: ', newValue)         if (newValue === data.msg) {           return         }         data.msg = newValue         // 数据更改,更新 DOM 的值         document.querySelector('#app').textContent = data.msg       }     })      // 测试     vm.msg = 'Hello World'     console.log(vm.msg)   </script>

如果,vm里的属性是对象如何处理,可以,对其遍历,在进行Object.defineProperty

<script>     // 模拟 Vue 中的 data 选项     let data = {       msg: 'hello',       count: 10,       person: {name: 'zhangsan'}     }      // 模拟 Vue 的实例     let vm = {}      proxyData(data)      function proxyData(data) {       // 遍历 data 对象的所有属性       Object.keys(data).forEach(key => {         // 把 data 中的属性,转换成 vm 的 setter/setter         Object.defineProperty(vm, key, {           enumerable: true,           configurable: true,           get () {             console.log('get: ', key, data[key])             return data[key]           },           set (newValue) {             console.log('set: ', key, newValue)             if (newValue === data[key]) {               return             }             data[key] = newValue             // 数据更改,更新 DOM 的值             document.querySelector('#app').textContent = data[key]           }         })       })     }      // 测试     vm.msg = 'Hello World'     console.log(vm.msg)   </script>

(2). Vue3.x是采用proxy代理的方式实现, 直接监听对象,而非属性。ES 6中新增,IE 不支持,性能由浏览器优化,具体可以详见MDN - Proxy

<script>     // 模拟 Vue 中的 data 选项     let data = {       msg: 'hello',       count: 0     }      // 模拟 Vue 实例     let vm = new Proxy(data, {       // 执行代理行为的函数       // 当访问 vm 的成员会执行       get (target, key) {         console.log('get, key: ', key, target[key])         return target[key]       },       // 当设置 vm 的成员会执行       set (target, key, newValue) {         console.log('set, key: ', key, newValue)         if (target[key] === newValue) {           return         }         target[key] = newValue         document.querySelector('#app').textContent = target[key]       }     })      // 测试     vm.msg = 'Hello World'     console.log(vm.msg)   </script>

2.Vue 响应式原理模拟

看图,整体分析

Vue 响应式原理模拟以及最小版本的 Vue的模拟

 Vue
  • 把 data 中的成员注入到 Vue 实例,并且把 data 中的成员转成 getter/setter
Observer
  • 能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知 Dep
Compiler
  • 解析每个元素中的指令/插值表达式,并替换成相应的数据
Dep
  • 添加观察者(watcher),当数据变化通知所有观察者
Watcher
  • 数据变化更新视图 

 (1) Vue

功能
  • 负责接收初始化的参数(选项)
  • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
  • 负责调用 observer 监听 data 中所有属性的变化
  • 负责调用 compiler 解析指令/插值表达式
class Vue {     constructor (options) {         //1.通过属性保存选项的数据         this.$options = options || {}         this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el         this.$data = options.data || {}         //2.把data中的成员转换成getter和setter方法,注入到vue实例中         this._proxyData(this.$data)         //3.调用observer对象,监听数据变化         new Observer(this.$data)         //4.调用compiler对象, 解析指令和差值表达式         new Compiler(this)     }      _proxyData (data) {         //遍历data中的所有属性         Object.keys(data).forEach( key => {             //把data的属性注入到vue实例中             Object.defineProperty(this, key, {                 enumerable: true,                 configurable: true,                 get () {                     return data[key]                 },                 set (newValue) {                     if (newValue === data[key]) {                         return                      }                     data[key] = newValue                 }              })         })      }  }

(2)Observer

功能
  • 负责把 data 选项中的属性转换成响应式数据
  • data 中的某个属性也是对象,把该属性转换成响应式数据
  • 数据变化发送通知 
class Observer {     constructor (data) {         this.walk(data)     }     //1.     walk (data) {         //1.判断data是不是对象         if (!data || typeof data !== 'object') {             return         }         //遍历data对象里的所有属性         Object.keys(data).forEach( key => {             this.definedReactive(data, key, data[key])         })     }      definedReactive (obj, key, value) {         let that = this         //负责收集依赖(观察者), 并发送通知         let dep = new Dep()          this.walk(value)//如果data里的属性是对象,对象里面的属性也得是响应式的,所以得判断一下                  Object.defineProperty (obj, key, {             enumerable: true,             configurable: true,             get () {                 //收集依赖                 Dep.target && dep.addSubs(Dep.target)                 return value                 // return obj[key]//这么写会引起堆栈溢出             },             set (newValue) {                 if (newValue === value) {                     return                  }                                  value = newValue                 that.walk(newValue)//如果赋值为对象,对象里面的属性得是响应式数据                  //数据变换 ,发送通知给watcher的update ,在渲染视图里的数据                 dep.notify()             }                              })      }  }

(3).Compiler

功能
  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染
  • 当数据变化后重新渲染视图 
class Compiler {      constructor (vm) {//传个vue实例         this.el = vm.$el         this.vm = vm         this.compile(this.el)     }      //编译模板, 处理文本节点和元素节点     compile (el) {          let childNodes = el.childNodes //获取子节点  伪数组         console.dir(el.childNodes)         Array.from(childNodes).forEach( node => {             if (this.isTextNode(node)) { //是文本节点                 this.compileText(node)             } else if (this.isElementNode(node)) {//是元素节点                 this.compileElement(node)             }              if (node.childNodes && node.childNodes.length) { //子节点里面还有节点,递归遍历获取                 this.compile(node)             }         })     }      //编译元素节点, 处理指令     compileElement (node) {         //console.log(node.attributes)          Array.from(node.attributes).forEach( attr => {              //判断是不是指令             let attrName = attr.name //<div v-text="msg"></div> 里的v-text             if (this.isDirective(attrName)) {                 //v-text --> text                 attrName = attrName.substr(2)                 let key = attr.value   //<div v-text="msg"></div> 里的msg                 this.update(node , key, attrName)              }         })     }      update (node, key, attrName) {         let updateFn = this[attrName + 'Updater']         updateFn && updateFn.call(this, node, this.vm[key], key)//call方法改变this指向     }     //处理v-text 命令     textUpdater (node, value, key) {         node.textContent = value         new Watcher(this.vm, key, (newValue) => {             node.textContent = newValue         })     }     //v-model     modelUpdater (node, value, key) {         node.value = value         new Watcher(this.vm, key, (newValue) => {             node.value = newValue         })          //双向绑定,视图改变,数据也会更新         node.addEventListener('input', () => {             this.vm[key] = node.value         })     }      //编译文本节点,处理差值表达式     compileText (node) {         //console.dir(node)         // {{  msg   }}         let reg = /{{(.+?)}}/         let value = node.textContent //里面的内容, 也可以是nodeValue         if (reg.test(value)) {             let key = RegExp.$1.trim()  //匹配到的第一个             node.textContent = value.replace(reg, this.vm[key])              //创建watcher对象, 当数据改变更新视图             new Watcher(this.vm, key, (newValue) => {                 node.textContent = newValue             })         }     }      //判断元素属性是否是指令     isDirective (attrName) {         return attrName.startsWith('v-')     }      //判断节点是否是文本节点     isTextNode (node) {         return node.nodeType === 3     }      //判断节点是否是元素节点     isElementNode (node) {         return node.nodeType === 1     } }

(4).Dep(Dependency)

Vue 响应式原理模拟以及最小版本的 Vue的模拟

 

 功能

  • 收集依赖,添加观察者(watcher)
  • 通知所有观察者 
class Dep {      constructor () {         //收集观察者         this.subs = []     }      //添加观察者     addSubs (watcher) {         if (watcher && watcher.update) {             this.subs.push(watcher)         }     }     //数据变换,就调watcher的update方法     notify () {         this.subs.forEach(watcher => {             watcher.update()         });     } }

(5).Watcher

Vue 响应式原理模拟以及最小版本的 Vue的模拟

 

 功能

  • 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
  • 自身实例化的时候往 dep 对象中添加自己 
class Watcher {     constructor (vm, key, callback) {         this.vm = vm         //data中的属性名         this.key = key         this.callback = callback         //将watcher对象记录在Dep的静态属性target         Dep.target = this         //触发get方法,触发get里的addsubs方法,添加watcher         this.oldValue = vm[key]         Dep.target = null     }      //当数据变化的时候,更新视图     update () {         let newValue = this.vm[this.key]         if (this.oldValue === newValue) {             return         }         this.callback(newValue)     } }

总结:

Vue 响应式原理模拟以及最小版本的 Vue的模拟

 

 Vue

  • 记录传入的选项,设置 $data/$el
  • data 的成员注入到 Vue 实例
  • 负责调用 Observer 实现数据响应式处理(数据劫持)
  • 负责调用 Compiler 编译指令/插值表达式等
Observer
  • 数据劫持
  • 负责把 data 中的成员转换成 getter/setter
  • 负责把多层属性转换成 getter/setter
  • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
  • 添加 Dep Watcher 的依赖关系
  • 数据变化发送通知
Compiler
  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染过程
  • 当数据变化后重新渲染
Dep
  • 收集依赖,添加订阅者(watcher)
  • 通知所有订阅者
Watcher
  • 自身实例化的时候往dep对象中添加自己
  • 当数据变化dep通知所有的 Watcher 实例更新视图

 

发表评论

相关文章