new Vue()发生了什么?

128 阅读2分钟

1. new Vue()

在创建一个vue实例时,主要做了三件事情:

  1. 挂载data、methods、props等选项到this上,并对data的每个属性通过Object.defineProperty反向代理到this
  2. 调用 Observe(data) 对数据做响应式处理
  3. 调用 Compile(el, vm) 编译模板
// vue.js
import Observe from './observe.js'
import Compile from './compile.js'

function Vue(el, options) {
  this.el = document.querySelector(el)
  
  this._data = options.data
  this._methods = options.methods
  Object.keys(this._data).forEach(key => this.setProxy(key)) // 反向代理data所有属性
  
  new Observe(this._data) // 对data做响应式处理
  
  new Compile(this.el, this) // 编译template模板
}
Vue.prototype = {
  setProxy(key) {
    let self = this
    Object.defineProperty(self, key, {
      enumerable: false,
      configurable: true,
      get: () => self._data[key],
      set: (newVal) => self._data[key] = newVal
    })
  }
}

2. Observe对data做响应式处理

调用Observe(data)对数据做响应式处理很简单,在get时让dep收集依赖,在set时让dep通知依赖队列更新

// Observe.js
function Observe(data) {
  Object.keys(data).forEach(key => {
    if (Array.isArray(data[key])) {
      // 数组方法拦截重写...
      data[key].forEach(item => new Observe(item))
    } 
    else if (data[key] instanceof Object) {
      new Observe(data[key])
    }
    this.walk(data, key, data[key])
  })
}
Observe.prototype = {
  walk(data, key, val) {
    let dep = new Dep()
    
    Object.defineProperty(data, key, {
      enumerable: false,
      configurable: true,
      get() {
        if (Dep.target) dep.addSub(Dep.target)
        return val
      },
      set(newVal) {
        if (newVal === val) return
        
        dep.notify()
        val = newVal
      }
    })
  }
}

function Dep(){
  this.subs = []
}
Dep.prototype = {
  addSub(sub) {
    this.subs.push(sub)
  },
  notify() {
    this.subs.forEach(sub => sub.update)
  }
}
Dep.target = null

3. Compile编译template模板

Compile编译模板大致分为四步:

  1. 为考虑性能,创建fragment,将根节点里的内容放入其中,对fragment里的内容进行模板编译完成后append到根节点里
  2. 遍历fragment所有的子节点,对元素标签,文本标签区别做编译
  3. 对于元素标签,解析其所有的attribute,得到attributeNameattributeValue,若以v-on开头,则给节点监听事件以及事件回调;若是v-model,对节点做双向数据绑定,具体是这样实现的:通过new Watcher(),观察data,触发响应式,当data更新时,视图也会跟着改变,同时,给节点绑定input事件,当事件发生时,data = e.target.value;对于文本节点,解析出{{ xx }}里边的xx,让节点的文本值为xx即可,同时调用new Watcherxx做响应式处理.
  4. 递归编译当前节点的子节点
// compile.js
function Compile(el, vm) {
  this.el = el
  this.vm = vm
  this.fragment = null
  this.init()
}
Compile.prototype = {
  init() {
    this.fragment = this.nodeToFragment(this.el.children) // 创建fragment,考虑性能!
    this.compileElement(this.fragment) // 编译模板
    this.el.appendChild(this.fragment)
  },
  nodeToFragment(node) {
    let fragment = document.createDocumentFragment()
    fragment.appendChild(node)
    return fragment
  },
  compileElement(el) {
    let childNodes = el.childNodes
    let self = this
    let reg = /\{\{(.*)\}\}/
 
    childNodes.forEach(node => {
      if (node.nodeType === 1) { // 元素节点
        this.compile(node)
      } 
      else if (node.nodeType === 3 && reg.test(node.textContent)) { // 文本节点
        this.compileText(node, reg.exec(node.textContent)[1])
      }
      if (node.childNodes && node.childNodes.length) { // 递归子节点
        this.compileElement(node)
      }
    })
  },
  compileText(node, exp) {
    // <div>{{ str }}</div>
    new Watcher(this.vm, exp, () => node.innerText = this.vm._data[exp])
  },
  compile(node) {
    let attrs = node.attributes
    let self = this
    attrs.forEach(attr => {
      let attrName = attr.name
      let exp = attr.value
      if (!attrName.startsWith('v-')) return
      
      if (attrName.startsWith('v-on')) {
        self.bindEvent(node, attrName, exp)
      }
      else if (attrName.startsWith="v-model") {
        self.bindModel(node, exp)
      } 
      else {
        // 其他情况略...
      }
      node.removeAttribute(attrName)
    })
  },
  bindEvent(node, dir, exp) { // 单向数据绑定
    let event = dir.split(':')[1]
    let eventCb = this.vm._methods[exp]
    // view -> model
    node.addEventListener(event, eventCb.bind(this.vm), false)
  },
  bindModel(node, exp) { // 双向数据绑定
    // model -> view
    new Watcher(this.vm, exp, () => node.value = this.vm._data[exp])
    // view -> model
    node.addEventListener('input',  (e) => this.vm._data[exp] = e.target.value)
  },
  compileText(node, exp) { // 单向数据绑定
    // model -> view
    new Watcher(this.vm, exp, () => node.innerText = this.vm._data[exp])
  }
}
// watcher.js
function Watcher(vm, exp, cb) {
  this.vm = vm
  this.exp = exp
  this.cb = cb
  this.value = this.get()
}
Watcher.prototype = {
  get() {
    Dep.target = this
    let value = this.vm._data[this.exp]
    this.cb && this.cb()
    Dep.target = null
    return value
  },
  update() {
    let newValue = this.vm._data[this.exp]
    let oldValue = this.value
    if (newValue === oldValue) return 
    
    this.cb.call(this.vm, newValue, oldValue)
  }
}