美文网首页
「Vue学习笔记」学习Vue响应式原理

「Vue学习笔记」学习Vue响应式原理

作者: 这名字真不对 | 来源:发表于2019-03-24 20:55 被阅读0次

    太久没有拓展自己的知识,这次从简单的Vue响应式开始。
    以下是简单的模拟一个利用Object.defineProperty方法进行数据绑定,实现响应式的方法。

    // dependency 订阅者收集
    // 用于收集属性的订阅者
    class Dep {
      constructor () {
        this.subs = [];
      }
    
      addSub(sub) {
        this.subs.push(sub)
      }
    
      // 通知订阅者
      notify() {
        this.subs.forEach(sub => {
          sub.update()
        })
      }
    }
    
    Dep.target = null; // 初始化时用,详细看后面的get() 
    
    // 工厂方法,将对象属性修改为响应式
    function observe(obj) {
      if (!obj || typeof obj !== 'object') {
        return;
      }
      Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key]) 
      })
    }
    
    function defineReactive(obj, key, val) {
      observe(val); // 递归对象子属性
    
      let dep = new Dep(); // 创建当前属性依赖收集
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get () {
          // 当创建watcher后,访问对象属性,此时为对象属性添加订阅者
          if (Dep.target) {
            dep.addSub(Dep.target)
          }
          return val
        },
        set (newVal) {
          val = newVal
          dep.notify() // 设置新值,触发收集到的订阅者的回调函数
        }
      })
    }
    
    // 订阅者
    class Watcher {
      // new Watcher时 ,将watcher实例添加到属性的订阅者收集当中
      constructor(obj, key, cb) {
        Dep.target = this; // Watcher实例添加Dep.target
        this.cb = cb;
        this.obj = obj;
        this.key = key;
        this.value = obj[key] // 触发属性的访问,将Watcher添加到了属性的dep当中
        Dep.target = null // 移除标记
      }
    
      update() {
        this.value = this.obj[this.key]
        this.cb(this.value)
      }
    }
    
    // 响应式框架
    class Vue {
      constructor(options) {
        this._data = options.data()
        observe(this._data)
        this._init()
      }
    
      // 初始化
      _init() {
        // ...do something
        this._compile(this._data)
      }
    
      // 模拟编译模板,并根据模板指令创建观察者和相关回调函数
      // 传入初始化时的数据对象
      _compile(data) {
        simulateDirective(data)
      }
    }
    
    // 模拟模板编译,创建相关回调函数,根据需要递归处理对象属性,这里就不做模拟了
    function simulateDirective(data) {
      function updateNameDiv(val) {
        console.log('div1 name update:', val)
      } // 模拟div1 中的{{ name }}绑定
    
      function updateNameDiv2(val) {
        console.log('div2 name update:', val)
      } // 模拟div2 中的{{ name }}绑定
    
      function updateAgeText (val) {
        console.log('div3 update: ', val)
      } // 模拟div3 {{age}}绑定
    
      // 添加第一个watcher
      new Watcher(data, 'name', updateNameDiv)
    
      // 添加第二个watcher
      new Watcher(data, 'name', updateNameDiv2)
    
      // 添加age属性的watcher
      new Watcher(data, 'age', updateAgeText);
    }
    
    // 配置数据属性
    const options = { 
      data() { 
        return { 
          name: 'xiong',
          age: 18
        }
      }
    }
    
    // 初始化
    let vm = new Vue(options)
    
    console.log('-------modify age--------')
    vm._data.age = 19
    console.log('-------modify name-------')
    vm._data.name = 'foo'
    

    参考

    相关文章

      网友评论

          本文标题:「Vue学习笔记」学习Vue响应式原理

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