vueVue

vue 响应式原理实现

2021-08-01  本文已影响0人  浅忆_0810

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

功能:

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 中的成员转换成 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()
// 编译属性节点 
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)

2.5 Watcher


3. 总结

上一篇 下一篇

猜你喜欢

热点阅读