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

太久没有拓展自己的知识,这次从简单的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'

参考

  • https://suzixuan.win/2017/12/25/watcher-dep-observer/

你可能感兴趣的:(「Vue学习笔记」学习Vue响应式原理)