最近翻读vue的源码,总结一下几点:
vue 首次渲染的过程
在源码中有4个导出vue的文件
src/core/instance/index.js
// 注册 vm 的 _init() 方法,初始化 vm
initMixin(Vue)
// 注册 vm 的 $data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy
lifecycleMixin(Vue)
// 混入 render
// $nextTick/_render
renderMixin(Vue)
src/core/index.js
调用initGlobalAPI() 给vue的构造函数初始化静态成员
- 设置 keep-alive 组件 extend(Vue.options.components, builtInComponents);
// 初始化 Vue.config 对象
Object.defineProperty(Vue, "config", configDef);
// 静态方法 set/delete/nextTick
Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
// 初始化 Vue.options 对象,并给其扩展
// components/directives/filters
Vue.options = Object.create(null);
ASSET_TYPES.forEach((type) => {
Vue.options[type + "s"] = Object.create(null);
});
Vue.options._base = Vue;
// 设置 keep-alive 组件
extend(Vue.options.components, builtInComponents);
// 注册 Vue.use() 用来注册插件
initUse(Vue);
// 注册 Vue.mixin() 实现混入
initMixin(Vue);
// 注册 Vue.extend() 基于传入的options返回一个组件的构造函数
initExtend(Vue);
// 注册 Vue.directive()、 Vue.component()、Vue.filter()
initAssetRegisters(Vue);
src/platforms/web/runtime/index.js
注册平台相关的属性和指令 注册Vue.prototype.patch,Vue.prototype.$mount
src/platforms/web/entry-runtime-with-compiler.js
重写$mount方法,新增了把 template/el 转换成 render 函数
总结渲染过程:
在new Vue()之前先初始化vue构造函数
- 1、在src/core/instance/index.js文件中主要负责给vue的原型上挂载一些实例成员和属性
- 2、在src/core/index.js文件中负责initGlobalAPI(Vye) 给vue的构造函数初始化静态成员
- 3、src/platforms/web/runtime/index.js初始化和平台相关的内容 如:初始化指令,组件,patch,$mount方法
- 4、src/platforms/web/entry-runtime-with-compiler.js是入口文件 重写$mount方法,新增了把 template/el 转换成 render 函数
初始化结束后调用new Vue(),在构造函数中调用_init()方法(_init方法是整个vue的入口)
- 在_init()中定义了mount中先判断是否传入render,调用comileToFunctions将模版编译成render函数,将render存入options选项中 options.render=render
- 在$mount方法的最后调用mount.call(this, el, hydrating)方法; 也就是在runtime/index.js中定义的方法,改方法中重新获取el,调用mountComponent渲染DOM
在mountComponent中
- 判断是否有render选项,如果没有但是传入了模版,并且当前是开发环境的话会发送警告
- 在挂载之前调用callHook(vm,'beforeMount')触发钩子函数
- 定义updateComponent函数
内部调用vm._update(vm._render(),hydrating)
- vm._render()作用是调用用户传入的render()或编译器中的人的人(),返回虚拟DOM,
- vm._update()作用是将虚拟DOM转换成真实DOM,更新到页面上
- 之后创建一个 new Watcher(vm,updateComponent)对象
创建完watcher会调用一次get()
- 调用updateComponent()方法
- 调用vm._render()创建VNode
- 调用vm._update(vnode),当中调用__patch__方法 挂载真实DOM,记录到vm.$el
- 最后触发 callHook(vm,'mounted')挂载完毕 返回vm
vue响应式原理实现过程
src/core/instance/index.js中调用initState()方法
initState()中 初始化vue实例的状态
if(opts.data){
initData(vm)//遍历data成员注入到vue实例
}else{
observe(vm._Data={},true) //将data对象转换成响应式对象
}
observe(value)中
- 判断 value 是否是对象如果不是对象直接返回
- 判断value对象是否有__ob__,有直接返回
- 没有创建observer对象
- 返回observer对象
// 判断 value 是否是对象
if (!isObject(value) || value instanceof VNode) {
return
}
// 如果 value 有 __ob__(observer对象) 属性 结束
// 如果没有ob,创建一个 Observer 对象
ob = new Observer(value)
// 返回ob
Observer中
- 给value对象定义不可枚举的__ob__属性,记录当前的observer对象
- 数组的响应式处理
- 对象的响应式处理 调用walk方法
- 被附加到每一个对象,将目标对象的每个属性转换成getter和setter,目的是为了收集依赖和派发更新
constructor (value: any) {
this.value = value
this.dep = new Dep()
// 初始化实例的 vmCount 为0
this.vmCount = 0
// 将实例挂载到观察对象的 __ob__ 属性
def(value, '__ob__', this)
// 数组的响应式处理
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
// 为数组中的每一个对象创建一个 observer 实例
this.observeArray(value)
} else {
// 遍历对象中的每一个属性,转换成 setter/getter
this.walk(value)
}
}
walk (obj: Object) {
// 获取观察对象的每一个属性
const keys = Object.keys(obj)
// 遍历每一个属性,设置为响应式数据
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
defineReactive中 src/core/observer/index.js
- 为每一个属性创建dep对象
- 如果当前属性是对象,调用observe
- 定义getter 收集依赖 返回属性值
- 定义setter 保存新值,如果新值是对象,调用observe;派发更新发送通知,调用dep.notify()
// 创建依赖对象实例
const dep = new Dep()
// 判断是否递归观察子对象,并将子对象属性都转换成 getter/setter,返回子观察对象
let childOb = !shallow && observe(val)
//定义get set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get:(){},
set:(){},
})
收集依赖
- 在watcher对象的get方法中调用pushTarget记录Dep.target属性
- 访问data中的成员时候收集依赖,defineReactive的getter中收集依赖
- 把属性对应的wathcer对象添加到dep的subs数组中
- 个体childOb收集依赖,目的是子对象添加和删除成员时候发送通知
```js
// 如果存在当前依赖目标,即 watcher 对象,则建立依赖
if (Dep.target) {
dep.depend()
// 如果子观察目标存在,建立子对象的依赖关系
if (childOb) {
childOb.dep.depend()
// 如果属性是数组,则特殊处理收集数组对象依赖
if (Array.isArray(value)) {
dependArray(value)
}
}
}
```
Watcher
- dep.notify()调用wacher对象的update()方法
- queueWatcher()判断wacher是否被处理,如果没有的话添加到queue队列,并调用flushSchedulerQueue()
- flushSchedulerQueue()
-
触发beforeUpdate钩子函数
-
调用wacher.run() run()->get()->getter()->updateComponent
-
清空上一次依赖
-
触发actived钩子函数
-
触发updated钩子函数
-
虚拟 DOM 中 Key 的作用和好处
可以减少 DOM 的操作
vue 模版编译的过程
首先调用$mount(),通过模版编译的入口函数 compilerToFunction 将 template 模版编译成 render 函数 ,compilerToFunction中主要实现:
读取缓存中的 CompiledFunctionResult 对象,如果有直接返回
调用compile(template, options) 把模板编译为编译对象(render, staticRenderFns),字符串形式的 js 代码
- compile函数作用是:合并选项 调用baseCompile编译 记录错误返回编译好的对象
- baseComipile中主要做三件事
- 把模板转换成 ast 抽象语法树
const ast = parse(template.trim(), options);-
优化抽象语法树
- 标记AST tree中的静态sub trees
- 检测到静态子树,设置为静态,不需要在每次重新渲染的时候重新生成节点
- patch阶段跳过静态子树
if (options.optimize !== false) { optimize(ast, options); }- 把抽象语法树生成字符串形式的 js 代码
const code =generate(ast, options);