Vue2 源码分析

源码目录结构

  • src
    • compiler  编译相关
    • core  核心代码
    • platforms  不同平台的支持
    • server  服务器渲染
    • sfc  解析.vue文件
    • shared  共享代码

runtime+complier 和 runtime only 的区别

// runtime+compiler的 main.js
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

// runtime only的 main.js
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})

// cli4.0
new Vue({
  render: h => h(App),
}).$mount('#app')

new Vue() 都干了什么

_init 是定义在原型上的方法,主要就干了几件事情:

  • 合并配置
  • 初始化生命周期
  • 初始化事件中心
  • 初始化渲染
  • 初始化 data、props、computed、watcher

实例挂载

$mount 方法实际上会去调用 mountComponent 方法,mountComponent 核心就是先实例化一个渲染 Watcher,在它的回调函数中会调用 updateComponent 方法,updateComponent 的核心是:

vm._update(vm._render(), hydrating)

vm._render 最终是通过执行 createElement 方法并返回虚拟DOM,_update 方法是把虚拟DOM 渲染成真实的 DOM

_update 的核心就是调用 vm.__patch__ 方法,vm.__patch__ 在不同平台上定义是不同的,最终都是调用 createPatchFunction 返回一个 patch 方法在 _update 中调用
patch 中的重点是 createElm 方法,createElm 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中

Virtual DOM

一个 dom 元素有很多属性,频繁的做 dom 更新,会有性能问题

dom元素的原型链:dom->HTMLDivElement->HTMLElement->Element->Node->EventTarget->Object.prototype->null

Virtual DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,在 Vue.js 中,Virtual DOM 是用  VNode 这么一个 Class 去描述。Virtual DOM 映射到真实的 DOM 要经历 VNode 的 create、diff、patch 等过程

// 真实 dom
<div class="parent" style="height:0" href="test">
test
</div>

// 虚拟 dom
{
	tag:"test",
	data:{
		attr:{href:"test"},
		staticClass:"parent",
		staticStyle:{
		height:0
		}
	},
	children:[{
		tag:undefined,
		text:"test"
	}]
}

生命周期

new Vue初始化时
执行 beforeCreate、created 钩子函数
$mount挂载时
执行 beforeMount,mounted 钩子函数

响应式原理

Vue.js 实现响应式的核心是利用了 ES5 的  [[JS对象#Object.defineProperty()]],给数据添加了 [[JS对象#getter/setter]],目的就是为了在我们访问数据以及写数据的时候能自动执行一些逻辑:getter 做的事情是[[#依赖收集]],setter 做的事情是[[#派发更新]],一旦对象拥有了 getter 和 setter,就可以简单地把这个对象称为响应式对象

_init() 中调用 initState() 去初始化 props、methods、data、computed 和 watch,将 props、data 变为响应式对象

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

initProps() 通过 defineReactive() 把每个 prop 对应的值变成响应式
initData() 通过 observe() 观测整个 data 的变化,把 data 变成响应式

observe() 就是给数据实例化一个 Observer 对象实例,Observer 是一个类,它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新。Observer 的构造函数对于数组会调用 observeArray(),对纯对象调用 walk()。observeArray 遍历数组再次调用 observe(),walk() 是遍历对象的 key 调用 defineReactive()

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

defineReactive 的功能就是定义一个响应式对象,利用 Object.defineProperty() 给对象动态添加 getter 和 setter。对于子对象,会递归调用 observe 方法,这样就保证了无论对象的结构多复杂,它的所有子属性也能变成响应式的对象

依赖收集

在 defineReactive() 中,const dep = new Dep() 实例化一个 Dep 的实例,get 函数中通过 dep.depend() 做依赖收集,依赖收集的 watcher 对象被保存在 Dep 的 subs 中,Dep 类有一个静态属性 target,是一个全局唯一的 Watcher,Dep 对 Watcher 进行管理

什么时候触发 getter 做依赖收集?
mount 时调用 mountComponent(),mountComponent 实例化一个渲染 watcher 时,首先进入 watcher 的构造函数中,构造函数执行 this.get() ,get 函数执行 this.getter(),这个 getter 函数就是 updateComponent 函数,即执行 vm._update(vm._render(), hydrating)

派发更新

在 defineReactive() 中,set 函数中通过 dep.notify() 做派发更新,subs 是依赖收集到的所有 watcher,在 notify 中对每一个 watcher 执行 update

notify () {
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
update () {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }