My Little World

Vue编译初探

Vue声明周期
初始化之后调用$mount会挂载组件编译template

编译

compile编译可以分成parse,optimize与generate三个阶段,最终得到render function
parse
parse 会用正则等方式循环切割字符串,解析template模板中的指令,class,style等数据,形成AST
optimize
标记static静态结点,这是编译过程的一处优化
generate
将AST转化成render function字符串的过程,得到的结果是render的字符串以及staticRenderFns

当render function 被调用的时候,因为会读取所需对象的值,所以会触发getter函数进行【依赖收集】
【依赖收集】的目的是将观察者Watcher对象存放到当前闭包中的订阅者Dep的subs中
在修改对象的值的时候,会触发对应的setter,setter通知之前【依赖收集】得到的Dep中的每一个Watcher
告诉他们自己的值改变了,需要重新渲染视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Vue.prototype.$mount = function(){
//挂在组件 已生成渲染函数,被调用
return mountComponent(this,el,hydrating)
}

//缓存 mount
var mount = Vue.prototype.$mount;

//重新覆盖 编译的功能
Vue,prototype.$mount = function(){
//挂在组件 生成render function
return mount.call(this,el)
}
编译过程:字符串模板转成渲染函数
运行时:调用渲染函数
独立构建 = 编译+运行时
运行时构建 = 运行时

//模板的编译 分成parse,optimize与generate三个阶段,最终得到render function
compileToFunctions(template)
//render function生成通过new Function,可以将函数功能通过字符串传递进去生成新函数
var render = new Function(参数,函数主体内容字符串)
其中函数主体字符串的功能就是返回parse生成的抽象语法树AST

渲染函数在哪被调用
mountComponen(){
//模板编译完成,实例挂载之前调用生命周期函数
callHook(vm,'beforeMount')

初始化 updateComponent函数
在非生产环境下config.performance为true,
初始化 updateComponent时有进行性能追踪的相关代码
(进行性能追踪4个场景:
组件【初始化】时
【编译】时模板转渲染函数时
通过【渲染】函数生成虚拟DOM时
【打补丁】,虚拟DOM转真实Dom时)
vm.update(vm._render(),hydrating)
vm._render() === vm.$options.render() //生成虚拟节点vnode
vm.update() 把vm._render()生成的虚拟节点渲染成真实的DOM
updateComponent 用作参数生成Watcher,即数据发生变化时,可以重新渲染DOM节点

}

vm.$option.render 渲染函数生成

1.生成
vm.$option.render = render 构造器函数调用parse,generate生成函数主体字符串,
再调用new Function返回函数
2.探究渲染函数this指向
initProxy
【渲染函数的作用域代理】
Proxy在目标对象之前架设一层拦截,拦截啥?
读取get;设置set;key in proxyObject 属性检测;with(){}
对一个new Proxy生成的对象进行上述操作时就会引发has钩子函数
vnode = render.call(vm._renderProxy,vm.$creatElement)

1
2
3
4
5
(function anonymous(){//一个渲染函数
//vm.renderProxy 访问变量A就会进行拦截
//调用proxy的has钩子函数 ,钩子函数中进行依赖收集,与生成的watcher进行绑定
with(this){return ...变量A...}
})

vue 对象生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//vue 对象生成
(function(global,factory){
typeof exports === 'object' && typeof module !=='undefinded' ?module.export = factory():
typeof define === 'function' && define.amd ? define(factory):
(global.Vue = factory());//window.Vue cmd AMD Comonjs
})(this,function(){
var ASSET_TYPES = [
'components',
'directives',
'filters'
]
//全局配置对象 挂载在Vue对象上,用作接口,进行自定义策略
(怎样处理自定义挂载在vue上的属性,和vue自身的同名属性,二者合并options时的处理方案)
//Vue.config.optionMergeStrategies.xxxx = function(){}
var config = {
optionMergeStrategies:Object.create(null)
}
//自定义策略
var strats = config.optionMergeStrategies
strats.data = function(parentVal,childVal,vm,key){
return function mergedInstanceDataFn(){

}
}
//默认策略
function defaultStrats(parentVal,childVal,vm){
return childVal === undefined? parentVal:childVal
}
var has = function(obj,key){
return obj != null && Object.hasOwnProperty.call(obj,key)
}
function mergeOption(parent,child,vm){
var options = {}
var key
for(key in parent){ //parent=>component directive filters 本身具备的
mergeFild(key)
}
for(key in child){ //child =>el data component 外部传参进来
if(!has(parent,key)){ //拦截重复操作
mergeFild(key)
}
}
//选项的处理celve
function mergeFild(key){
//生成最终需要数据
//合并策略 自定义策略 默认策略
console.log(key)
var strat = strats[key] || defaultStrats
options[key] = strat(parent[key],child[key],vm)
}

return options
}
function initMixin(vue){

Vue.prototype._init = function(options){
var vm = this
//合并选项 VUE.option option
vm.$options = mergeOption(Vue.options,options||{},vm)
}
}
function initGlobalAPI(Vue){
var configDef = {};
configDef.get = function(){
return config
}
configDef.set = function(val){
console.error('不要修改config')
}
//Vue.config = config 对修改config做拦截,但可以对config的属性进行操作
//Vue.config.optionMergeStrategies 可以进行扩展自定义策略
Object.defineProperty(Vue,'config',configDef)
}

function Vue(options){
if(!(this instanceof Vue)){
console.error('....')
}
this._init(options)
}
Vue.options = Object.create(null);
ASSET_TYPES.foreach(function(type){
Vue.options[type] = Object.create(null)
})
initMixin(Vue)
initGlobalAPI(Vue)initGlobalAPI(Vue)
return Vue
})