美文网首页vueVue
vue 响应式原理实现

vue 响应式原理实现

作者: 浅忆_0810 | 来源:发表于2021-08-01 23:31 被阅读0次

1. 整体分析

深入响应式原理

https://github.com/DMQ/mvvm

整体结构

1.1 Vue

data 中的成员注入到 Vue 实例,并且把 data 中的成员转成 getter/setter

1.2 Observer

能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知 Dep

1.3 Compiler

解析每个元素中的指令/插值表达式,并替换成相应的数据

1.4 Dep

添加观察者(watcher),当数据变化通知所有观察者

1.5 Watcher

数据变化更新视图


2. 具体实现

2.1 vue

功能:

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

2.2 Observer

功能:

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

  • data 中的某个属性也是对象,把该属性转换成响应式数据

  • 数据变化发送通知

// 负责数据劫持 
// 把 $data 中的成员转换成 getter/setter
class Observer { 
  constructor (data) { 
    this.walk(data)
  }
  walk(data) {
    // 1. 判断数据是否是对象,如果不是对象返回 
    // 2. 如果是对象,遍历对象的所有属性,设置为 getter/setter
    if (!data || typeof data !== 'object') { return }
    // 遍历 data 的所有成员 
    Object.keys(data).forEach(key => { 
      this.defineReactive(data, key, data[key]);
    })
  }
  // 定义响应式成员
  defineReactive (data, key, val) {
    const that = this 
    // 负责收集依赖,并发送通知
    let dep = new Dep();
    // 如果 val 是对象,继续设置它下面的成员为响应式数据
    this.walk(val)
    // 遍历 data 的所有属性 
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get () { 
        // 收集依赖
        Dep.target && dep.addSub(Dep.target)
        return val
      },
      set (newValue) { 
        if (data[key] === newValue) { return }
        // 如果 newValue 是对象,设置 newValue 的成员为响应式 
        that.walk(newValue)
        val = newValue 
        // 发送通知
        dep.notify();
      } 
    });
  } 
}

2.3 Compiler

功能

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

  • 负责页面的首次渲染

  • 当数据变化后重新渲染视图

// 负责解析指令/插值表达式 
class Compiler { 
  constructor (vm) { 
    this.vm = vm 
    this.el = vm.$el
    // 编译模板
    this.compile(this.el) 
  }
  // 编译模板 
  // 处理文本节点和元素节点 
  compile (el) { 
    const nodes = el.childNodes 
    Array.from(nodes).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) 
      } 
    }) 
  }
  // 判断是否是文本节点 
  isTextNode (node) { 
    return node.nodeType === 3 
  }
  // 判断是否是属性节点 
  isElementNode (node) { 
    return node.nodeType === 1 
  }
  // 判断是否是以 v- 开头的指令 
  isDirective (attrName) { 
    return attrName.startsWith('v-') 
  }
  // 编译文本节点 
  compileText (node) { }
  // 编译属性节点 
  compileElement (node) { } 
}
2.3.1 compileText()
  • 负责编译插值表达式
// 编译文本节点 
compileText (node) {
  const reg = /\{\{(.+?)\}\}/ 
  // 获取文本节点的内容 
  const value = node.textContent 
  if (reg.test(value)) { 
    // 插值表达式中的值就是我们要的属性名称 
    const key = RegExp.$1.trim() 
    // 把插值表达式替换成具体的值 
    node.textContent = value.replace(reg, this.vm[key])
    
    // 创建watcher对象,当数据改变时更新视图
    new Watcher(this.vm, key, value => { 
      node.textContent = value 
    });
  }
}
2.3.2 compileElement()
  • 负责编译元素的指令
  • 处理 v-text 的首次渲染
  • 处理 v-model 的首次渲染
// 编译属性节点 
compileElement (node) {
  // 遍历元素节点中的所有属性,找到指令 
  Array.from(node.attributes).forEach(attr => { 
    // 获取元素属性的名称 
    let attrName = attr.name 
    // 判断当前的属性名称是否是指令 
    if (this.isDirective(attrName)) { 
      // attrName 的形式 v-text v-model 
      // 截取属性的名称,获取 text model 
      attrName = attrName.substr(2)
      // 获取属性的名称,属性的名称就是我们数据对象的属性 v-text="name",获取的是 name 
      const key = attr.value 
      // 处理不同的指令 
      this.update(node, key, attrName) 
    } 
  });
}

// 负责更新 DOM 
// 创建 Watcher 
update (node, key, dir) { 
  // node 节点,key 数据的属性名称,dir 指令的后半部分 
  const updaterFn = this[dir + 'Updater'] 
  updaterFn && updaterFn.call(this, node, this.vm[key], key);
}

// v-text 指令的更新方法 
textUpdater (node, value, key) { 
  node.textContent = value;
  new Watcher(this.vm, key, value => { 
    node.textContent = value 
  });
}

// v-model 指令的更新方法 
modelUpdater (node, value, key) { 
  node.value = value;
  new Watcher(this.vm, key, value => { 
    node.value = value
  });
  // 监听视图的变化 
  node.addEventListener('input', () => { 
    this.vm[key] = node.value 
  });
}

2.4 Dep(Dependency)

  • 功能

    • 收集依赖,添加观察者(watcher)
    • 通知所有观察者
  • 代码

    class Dep { 
      constructor () { 
        // 存储所有的观察者 
        this.subs = [] 
      }
      
      // 添加观察者 
      addSub (sub) { 
        if (sub && sub.update) { 
          this.subs.push(sub) 
        } 
      }
      
      // 通知所有观察者 
      notify () { 
        this.subs.forEach(sub => { 
          sub.update() 
        });
      } 
    }
    

2.5 Watcher

  • 功能

    • 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
    • 自身实例化的时候往 dep 对象中添加自己
  • 代码

    class Watcher { 
      constructor (vm, key, cb) { 
        this.vm = vm 
        // data 中的属性名称 
        this.key = key 
        // 回调函数负责更新视图
        this.cb = cb 
        // 把watcher对象记录到Dep类的静态属性target上
        Dep.target = this 
        // 触发get方法,在get方法中会调用addSub
        this.oldValue = vm[key] 
        Dep.target = null 
      }
    
      // 当数据发生变化时更新视图
      update () { 
        const newValue = this.vm[this.key] 
        if (this.oldValue === newValue) { return }
        this.cb(newValue) 
      } 
    }
    

3. 总结

  • Vue

    • 记录传入的选项,设置 $data/$el

    • data 的成员注入到 Vue 实例

    • 负责调用 Observer 实现数据响应式处理(数据劫持)

    • 负责调用 Compiler 编译指令/插值表达式等

  • Observer

    • 数据劫持

    • 负责把 data 中的成员转换成 getter/setter

    • 负责把多层属性转换成 getter/setter

    • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter

    • 添加 DepWatcher 的依赖关系

    • 数据变化发送通知

  • Compiler

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

    • 负责页面的首次渲染过程

    • 当数据变化后重新渲染

  • Dep

    • 收集依赖,添加订阅者(watcher)

    • 通知所有订阅者

  • Watcher

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

    • 当数据变化dep通知所有的 Watcher 实例更新视图

相关文章

  • Vue

    vue双向绑定原理及实现从零带你手把手实现Vue3响应式原理-上从零带你手把手实现Vue3响应式原理-下为什么说 ...

  • 面试总结之基础(2)

    Vue2响应式原理 Vue3响应式原理

  • 双向绑定

    数据响应式原理 vue实现数据响应式的原理就是利用了Object.defineProperty(),重新定义了对象...

  • 前端面试题【Day02】

    本篇绪论 1,Vue响应式原理 1,Vue响应式原理 在vue实例中声明的数据就是响应式的。响应式:数据发生改变,...

  • vue系列--- vue响应式原理

    vue响应式原理 要说vue响应式原理首先要说的是Object.defindProperty(),这个是响应式原理...

  • 【Vue3.0】- 响应式

    响应式原理 响应式是 Vue.js 组件化更新渲染的一个核心机制 Vue2.x响应式实现 Object.defin...

  • VUE双向绑定原理(深入响应式原理)

    vue官网-深入响应式原理 深入响应式原理

  • Vue的34道题

    1、如何理解MVVM原理? MVVM的实现原理 2、响应式数据的原理是什么? 响应式数据与数据依赖基本原理vue双...

  • Vue 进阶系列(一)之响应式原理及实现

    Vue 进阶系列(二)之插件原理及实现Vue 进阶系列(三)之Render函数原理及实现 什么是响应式Reacti...

  • Vue原理学习(二)

    响应式系统的基本原理 Vue基于Object.defineProperty来实现响应式,对于Object.defi...

网友评论

    本文标题:vue 响应式原理实现

    本文链接:https://www.haomeiwen.com/subject/emndpltx.html