provide vue 响应式_实现一个简单的vue响应式(理解vue的响应式原理)
发布日期:2021-06-24 14:15:11 浏览次数:2 分类:技术文章

本文共 8639 字,大约阅读时间需要 28 分钟。

模拟实现的有vm实例对象中的$el、$data、$options、把data中的成员注入到vm实例对象中

实现模拟vue响应式的整体结构图:

2d98c75f8460d5dcd06c4fe3504e5ee2.png

- Vue

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

项目的目录

587275badf73f6ac6b7931f1b36af7b9.png

1、vue.js文件内实现的功能

负责接收初始化的参数(选项)

负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
负责调用 observer 监听 data 中所有属性的变化
负责调用 compiler 解析指令/插值表达式
代码如下:

class Vue {  constructor(options) {    // 1、保存vue实例传递过来的数据    this.$options = options // options是vue实例传递过来的对象    this.$data = options.data // 传递过来的data数据    // el 是字符串还是对象    this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el    // 2、把this.$data中的成员转换成getter 和setter ,并且注入到vue实例中,使vue实例中有data里面的属性    // 但是this.$data自身内部成员并没有实现在自身内部属性的gettter和setter,需要通过observer对象来实现    this._proxyData(this.$data)    // 3、调用observer对象,监视data数据的变化    new Observer(this.$data)    // 4、调用compiler对象,解析指令和差值表达式    new Compiler(this) // this是vue实例对象  }  _proxyData (data) {    // 遍历传递过来的data对象的数据,key是data对象中的属性名    Object.keys(data).forEach((key) => {      // 使用js的Object.defineProperty(),把数据注入到vue实例中,this就是vue实例      Object.defineProperty(this, key, {        configurable: true, // 可修改        enumerable: true, // 可遍历        // get 是 Object.defineProperty()内置的方法        get () {          return data[key]        },        // set 是 Object.defineProperty()内置的方法        set (newValue) {          if (newValue === data[key]) {            return          }          data[key] = newValue        }      })    })  }}

2、oberver.js文件实现的功能

负责把 data 选项中的属性转换成响应式数据

data 中的某个属性也是对象,把该属性转换成响应式数据
数据变化发送通知
代码如下:

/** * Observer类:作用是把data对象里面的所有属性转换成getter和setter * data 是创建vue实例的时候,传递过来的对象里面的data,data也是个对象 */class Observer {  // constructor 是创建实例的时候,立刻自动执行的  constructor(data) {    this.walk(data)  }  // 遍历data对象的所有属性  // data 是创建vue实例的时候,传递过来的对象里面的data,data也是个对象  walk (data) {    // 判断data是否是对象    if (!data || typeof data !== 'object') {      return    }    Object.keys(data).forEach((key) => {      this.defineReactive(data, key, data[key])    })  }  // 把data对象里面的所有属性转换成getter和setter  defineReactive (obj, key, val) {    // 解决this的指向问题    let that = this    // 为data中的每一个属性,创建dev对象,用来收集依赖和发送通知    // 收集依赖:就是保存观察者    let dep = new Dep()    // 如果val也是对象,就把val内部的属性也转换成响应式数据,    /// 也就是调用Object.defineProperty()的getter和setter    that.walk(val)    Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get () {        // Dep.target就是观察者对象,调用dev对象的addSub方法,把观察者保存在dev对象内        // target是Dep类的静态属性,但是却是在Watcher类中声明的        Dep.target && dep.addSub(Dep.target)        return val      },      set (newValue) {        if (newValue === val) {          return        }        val = newValue        // 对vue实例初始化后,传入的data数据的值进行修改,由字符串变成对象        // 也要把新赋值的对象内部的属性,转成响应式        that.walk(newValue)        // data里面的数据发生了变化,调用dev对象的notify方法,通知观察者去更新视图        dep.notify()      }    })  }}

3、compiler.js文件实现的功能

负责编译模板,解析指令/插值表达式

负责页面的首次渲染
当数据变化后重新渲染视图
代码如下:

/** * 主要就是用来操作dom * 负责编译模板,解析指令/插值表达式 * 负责页面的首次渲染 * 当数据变化后重新渲染视图 */class Compiler {  constructor(vm) {    this.el = vm.$el // vue实例下的模板    this.vm = vm // vm就是vue实例    this.compile(this.el) // compiler实例对象创建后,会立即调用这个方法  }  // 编译模板,处理文本节点和元素节点  compile (el) {    let childNodes = el.childNodes // 是个伪数组    Array.from(childNodes).forEach((node) => {      if (this.isTextNode(node)) {        // 编译文本节点,处理差值表达式{
{}} this.compileText(node) } else if (this.isElementNode(node)) { // 编译元素节点,处理指令 this.compileElement(node) } // 递归调用compile,把所有的子节点都处理掉,也就是嵌套的节点都处理掉 if (node.childNodes && node.childNodes.length) { this.compile(node) } }) } // 编译元素节点,处理指令,这里只处理v-text和v-model compileElement (node) { // console.dir(node.attributes) Array.from(node.attributes).forEach((attr) => { // console.log(attr.name) let attrName = attr.name // 指令属性名 v-modelv-texttypev-count // 判断是否是vue指令 if (this.isDirective(attrName)) { // v-text ==> text attrName = attrName.substr(2) // textmodelon:clickhtml let key = attr.value // 指令属性值 // msgcounttextclickBtn() // 处理v-on指令 if (attrName.startsWith('on')) { const event = attrName.replace('on:', ''); // 获取事件名 // 事件更新 this.onUpdater(node, key, event); } else { this.update(node, key, attrName); } } }) } update (node, key, attrName) { let updateFn = this[attrName + 'Updater'] // textUpdater(){} 或者 modelUpdater(){} // this 是compiler对象 updateFn && updateFn.call(this, node, this.vm[key], key) // updateFn的名字存在才会执行后面的函数 } // 处理v-text指令 textUpdater (node, value, key) { // console.log(node) node.textContent = value // 创建watcher对象,当数据改变去更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } // 处理v-html指令 htmlUpdater (node, value, key) { // console.log(node) node.innerHTML = value // 创建watcher对象,当数据改变去更新视图 // this.vm: vue的实例对象 key:data中的属性名称 ()=>{}: 回调函数,负责更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } // 处理v-model指令 modelUpdater (node, value, key) { // console.log(node, value) node.value = value // console.log(node.value) // 创建watcher对象,当数据改变去更新视图 new Watcher(this.vm, key, (newValue) => { node.value = newValue }) // 双向数据绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) } // 处理v-on指令 onUpdater (node, key, event) { // console.log(node ,key, event) node.addEventListener(event, () => { // 判断函数名称是否有() if (key.indexOf('(') > 0 && key.indexOf(')') > 0) { this.vm.$options.methods[key.slice(0,-2)]() } else { this.vm.$options.methods[key]() } }) } // 编译文本节点,处理差值表达式{
{ msg }} compileText (node) { // console.dir(node) let reg = /{
{(.+?)}}/ let value = node.textContent // 获取文本节点内容:{
{ msg }} if (reg.test(value)) { let key = RegExp.$1.trim() // 把差值表达式{
{ msg }}中的msg提取出来 // 把{
{ msg }}替换成 msg对应的值,this.vm[key] 是vue实例对象内的msg node.textContent = value.replace(reg, this.vm[key]) // 创建watcher对象,当数据改变去更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } } // 判断元素属性是否是vue指令 isDirective (attrName) { return attrName.startsWith('v-') } // 判断节点是否是文本节点(元素节点1属性节点2文本节点3) isTextNode (node) { return node.nodeType === 3 } // 判断节点是否是元素节点(元素节点1属性节点2文本节点3) isElementNode (node) { return node.nodeType === 1 }}

4、dep.js文件实现的功能 Dep(Dependency)

收集依赖,添加观察者(watcher)

通知所有观察者

d7262f174f1848cb8c6421d8ef063f60.png

代码如下:

/** * 收集依赖,添加所有观察者(watcher) * 通知观察者,数据发生改变了,去更新视图,通过watcher对象里面的update方法来实现 */class Dep {  constructor() {    this.subs = [] // 保存所有的观察者  }  // 把观察者添加到this.subs中去  addSub (sub) {    // 约定,每一个观察者对象中,必须要有一个update方法    if (sub && sub.update) {      this.subs.push(sub)    }  }  // 发送通知给观察者,告诉观察者,数据发生改变了,你要去更新视图  notify () {    this.subs.forEach((sub) => {      sub.update()    })  }}

5、watcher.js文件实现的功能 Dep(Dependency)

当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图

自身实例化的时候往 dep 对象中添加自己

4a9598d498ebde44137a5ab01cd18617.png

代码如下:

/** * 当data数据发生变化,dep对象中的notify方法内通知所有的watcher对象,去更新视图 * Watcher类自身实例化的时候,向dep对象中addSub方法中添加自己(1、2) */class Watcher {  constructor(vm, key, cb) {    this.vm = vm // vue的实例对象    this.key = key // data中的属性名称    this.cb = cb // 回调函数,负责更新视图    // 1、把watcher对象记录到Dev这个类中的target属性中    Dep.target = this // this 就是通过Watcher类实例化后的对象,也就是watcher对象    // 2、触发observer对象中的get方法,在get方法内会调用dep对象中的addSub方法    // this.oldValue = this.vm[this.key] //更新之前的页面数据    // Dep.target = null    this.oldValue = vm[key] //更新之前的页面数据    Dep.target = null  }  // 当data中的数据发生变化的时候,去更新视图  update () {    const newValue = this.vm[this.key]    if (newValue === this.oldValue) {      return    }    this.cb(newValue)  }}

6、index.html文件内容:

  
Mini Vue

标题:差值表达式

{
{ msg }}

{
{ count }}

标题:v-text

标题:v-model

标题:v-html 指令

标题:v-on 指令

7、直接打开index.html文件就可以查看效果

总结

问题一:给属性重新赋值成对象,是否是响应式的? 是

问题二:给 Vue 实例新增一个成员是否是响应式的? 不是

通过下图回顾整体流程:

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

参考

深入响应式原理https://github.com/DMQ/mvvm

转载地址:https://blog.csdn.net/weixin_33362096/article/details/112154081 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:vue 判断json是否为空_vue渲染过程解析-VDOM &DOM
下一篇:mysql 建表字段长度_30 分钟学会 MySQL 基础。

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月14日 09时20分52秒