vue响应式涉及依赖收集监听解析模板都干了那些事情及其原理

Vue工作机制

编译compile

核心逻辑获取dom, 遍历dom,获取{{}},v-和@开头的,设置响应式

class Compile {
  constructor(el,vm) {
    this.$vm = vm
    this.$el = document.queryySelector(el)
    if (this.$el) {
      this.$fragment = this.node2Fragment(this.$el)
      this.compileElement(this.$fragment)
      this.$el.appendChild(this.$fragment)
    }
  }
  node2Fragment(el) {
    let fragment = document.createDocument() // 新建文档碎片 dom接口
    let child
    while(child = el.firstChild) {
      fragment.appendChild(child) // 将原生节点拷贝到fragment
    }
    return fragment
  }
  
  compileElement(el) {
    let childNodes = el.childNodes
    Array.from(childNodes).forEach((node)=> {
      let text = node.textContent
      // 表达式文本
      // 识别{{}}中的数据
      let reg = /\{\{(.*\}\}/
      // 按元素节点类型编译
      if (this.isElementNode(node)){
        this.compile(node)
      }else if(this.isTextNode(node) && reg.test(text)){
        // 文本 并且有个{{}}
        this.compileText(node, RegExp.$1)
      }
      // 遍历编译子节点
      if (node.childNodes && node.childNodes.length){
        this.compileElement(node)
      }
    })
  }
  compile(node) {
    let nodeAttrs = node.attributes
    Array.from(nodeAttrs).forEach((attr) => {
      // 指令以v-xxx 命名
      // 如<span v-text = "content"></span>
      let attrName = attr.name // v-text
      let exp = attr.value // content
      if (this.isDirective(attrName)) {
        let dir = attrName.substring(2) // text
        // 普通指令
        this[dir] && this[dir](node, this.&vm, exp)
      }
      if (this.isEventDirective(attrName)) {
        let dir = attrName.substring(1) // text
        this.eventHandle(node, this.&vm, exp, dir)
      }
    })
  }
  compileText(node, exp){
    this.text(node, this.&vm, exp)
  }
  isDirective(attr) {
    return attr.indexOf("v-") == 0
  }
  isEventDirective(dir){
    return dir.indexOf('@') === 0
  }
  isElementNode(node) {
    return node.nodeType == 1
  }
  isTextNode(node) {
  return node.nodeType == 3
}
  text(node, vm, exp) {
    this.update(node, vm, exp, 'text')  
  }
  html(node, vm, exp) {
    this.update(node, vm, exp, 'html')  
  }
  model(node, vm, exp) {
    this.update(node, vm, exp, 'model')
    let val = vm.exp
    node.addEventListener("input", (e)=>{
      let newValue = e.target.value
      vm[exp] = newValue
      val = newValue
    })
  }
  update(node, vm, exp, dir){
    let updateFn = this[dir + 'updater']
    updateFn && updaterFn(node, vm[exp])
    new Watcher(vm, exp, function(value) {
      updaterFn && updaterFn(node, value)
    })
  }
  eventHandler(node, vm, exp, dir){
    let fn = vm.$options.methods && vm.$options.methods[exp]
    if (dir && fn) node.addEvent(dir, fn.bind(vm), false)
  }  
  textUpdater(node, value){
    node.textContent = value
  }

  htmlUpdater(node, value) {
    node.innerHTML = value
  }
  modelUpdater(node, value){
    node.value = value
  }
}

Vue响应式的原理defineProperty

class Vue {
  constructor(options){
    this._data = options.data;
    this.observer(this._data);
  }
  observer (value) {
    if (!value || typeof value !== 'object') return
    object.keys(value).forEach(key => {
      this.defineReactive(value, key, value[key]);
    })
  }
  defineReactive(obj, key, val) {
    Object.defineProperty(obj,key,{
      enumerable: true // 属性是否可枚举
      configurable: true // 属是否可被修改,
      get () {
        return val
      }
      set(newVal) {
        if (newVal === val) return;
        this.cb(newVal)
      }
    })
  }
  cb(val) {
    console.log('执行更新函数’,val)
  }
}

依赖搜集与跟踪

class Dep { // 收集依赖相关并触发更新
  constructor () {
    this.deps = [] // 依赖数组
  }
  addDep(dep){ // 向dep添加一个监听对象
    this.deps.push(dep)
  }
  depend() {
    Dep.target.addDep(this)
  }
  notify() { // 通知所有监听去更新视图
    this.deps.forEach((dep) => {
      dep.update()
  })
  }
}
class watcher {
   constructor (vm, key, cb) {
    // 在new一个监听对象时将该对象赋值给Dep.target, 在get中会用到
  // 将 Dep.target 指向自己
  // 然后出发属性的 getter 添加监听
  // 最后将 Dep.target 清空
this.cb = cb   this.vm = vm   this.key = key   this.value = this.get()    Dep.target = this      }
  get () {
    Dep.target = this
    let value = this.vm[this.key]
    return value
  }   
// 更新视图   update () {
    this.value = this.get()
    this.cb.call(this.vm, this.value)
  console.log(
'视图更新')    } }

 我们在增加一个Dep类的对象,用来收集Watcher对象。读数据的时候,会触发reactiveGetter函数把当前的Watcher对象(存放到Dep.target中)收集到Dep类中去。

写数据的时候,则会触发reactiveSetter方法,通知Dep类的调用notify来触发所有watcher对象的update方法更新相应视图

class Vue {
  constructor(options) {
    this._data = options.data
    this.observer(this._data)
    this.observer(this._data)
    new Watcher() // 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象
    if (options.created) {
      options.created.call(this)
    }
    this.$compile = new Compile(options.el, this)
  }
  observer(value){
    if (!value || (typeof value !== 'object')) {
      return
    }
    Object.key(value).forEach((key)=>{
      this.proxyData(key)
      this.defineReactive(value, key, value[key])
    })
  }
  
  defineReactive(obj, key, val) {
    const dep = new Dep()
    Object.defineProperty(obj, key, {
      enmerable: true,
      configurable: true,
      get function reactive () {
        dep.addDep(Dep.target)
        return val
      }
      set: function reactiveSetter(newVal){
        if (newVal === val) return
        dep.notify() // 在set的时候触发dep中的notify来通知所有watcher对象更新视图
      }

    })
  }

  proxyData(key) {
    Object.defineProperty(this, key, {
      configurable: false,
      enumerable: true,
      get() {
        return this.$data[key]
      }
      set (newVal) {
        this.$data[key] = newVal
      }
    })
  }
}

猜你喜欢

转载自www.cnblogs.com/chengeping/p/12073851.html