美文网首页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 响应式原理实现

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