function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
设置情景分析
假设我们的实际场景为下面的脚本, 我们下面来分析一下
var data = {
a:1,
b:2
};
new Vue({
el:'#app',
template: `\
<section>\
<div>{{a+b}}</div>\
<p>静态文本<a href="www.koala.com">考拉地址</a></p>\
</section>\
`,
data(){
return data;
}
});
_init 入口函数
Vue.prototype._init = function (options) {
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
// 注意:beforeCreate阶段完成后,我们的options被merge到了vm.$options属性上,此时是获取不到this.xxx数据的,如果我们要获取data数据需要this.$options.data()来获取
initInjections(vm);
// 对data进行了一些Observe 执行了defineReactive 标记1
initState();
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
/* istanbul ignore if */
if ("development" !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(("vue " + (vm._name) + " init"), startTag, endTag);
}
if (vm.$options.el) {
// 对模板进行了compile 生成了render function, 调用render function 生成了vmdom
vm.$mount(vm.$options.el);
}
}
阶段一 initState()
function initState (vm) {
vm._watchers = [];
var 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);
}
}
Watcher.prototype.get = function get () {
pushTarget(this);
var value;
var vm = this.vm;
try {
//getter对应new Watcher时我们传入的第二个参数 new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)即updateComponent
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
function pushTarget (_target) {
if (Dep.target) { targetStack.push(Dep.target); }
Dep.target = _target;
}
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
Vue.prototype._render = function () {
var vm = this;
var ref = vm.$options;
var render = ref.render;
var _parentVnode = ref._parentVnode;
...
vm.$vnode = _parentVnode;
// render self
var vnode;
try {
// 重点看这里,其实就是执行了我们的render function
vnode = render.call(vm._renderProxy, vm.$createElement);
} catch (e) {
...
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if ("development" !== 'production' && Array.isArray(vnode)) {
warn(
'Multiple root nodes returned from render function. Render function ' +
'should return a single root node.',
vm
);
}
vnode = createEmptyVNode();
}
// set parent
vnode.parent = _parentVnode;
return vnode
};
}
vnode = render.call(vm._renderProxy, vm.$createElement);这个方法的调用,相当于执行了我们之前得到的render function。并且我们知道render function 里面有with(this) 此时我们把this指向了vm, 所以按照我们的例子,s(a+b) 在执行的时候会读取到vm.a 和 vm.b 就分别进入了a 和 b 的getter。再回头看下getter函数。
所以注意这里就是Watcher 与 Dep 关联的地方,可以认为compile 是Watcher 与 Dep之间的桥梁。
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
// 此时Dep.target 指向this, 因为watch实例生成时调用了pushTarget(this)
if (Dep.target) {
// 看下dep.depend 方法
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function() {...}
}
Dep.prototype.depend = function depend () {
if (Dep.target) {
// 调用的是watcher的addDep方法
Dep.target.addDep(this);
}
};
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
// 这里调用了dep的addSub方法
dep.addSub(this);
}
}
};
Dep.prototype.addSub = function addSub (sub) {
this.subs.push(sub);
};
vm.render()方法最终执行完会生成VNode, 即完成了从render function —> VNode 的过程,我们看下此时vnode的样子, 大概像下面这样
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if ("development" !== 'production' && customSetter) {
customSetter();
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
// 进入dep.notify 通知watcher
dep.notify();
}
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
for (var i = 0, l = subs.length; i < l; i++) {
subs[i].update();
}
};
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
// 进入这里 这个方法最终会进入watcher.prototype.run 方法
queueWatcher(this);
}
};
Watcher.prototype.run = function run () {
if (this.active) {
// 这里会调用this.get, 之前有讲过this.get里面会调用updateComponent,所以又会走到
// vm._update(vm._render(), hydrating);这个方法重新更新视图了
var value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
}
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};
Watcher.prototype.get = function get () {
// 这里有个关键点
pushTarget(this);
var value;
var vm = this.vm;
try {
// 注意this.getter = expFunction
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
Vue2中Dep, Observer 与Watcher 之间的关系(不含patch部分)
最简单的理解
为什么要引入依赖收集
例子1:
text3在实际模板中并没有被用到,然而当text3的数据被修改的时候(this.text3 = 'test')的时候,同样会触发text3的setter,按照原先想法,这会导致重新执行渲染,这显然不正确。
例子2:
当globalData.a 发生变化或者当globalData.b发生变化时,我们的视图都需要更新,所以我们要收集这个视图依赖于数据a 和 数据b。因此我们需要依赖收集。
前置知识
vue官网在线模板编译
https://cn.vuejs.org/v2/guide/render-function.html#%E6%A8%A1%E6%9D%BF%E7%BC%96%E8%AF%91
ASTNode 类型(Abstract Syntax Tree)
render 函数一些函数定义
VNode 结构
真实DOM有什么问题,为什么要去使用虚拟DOM
每个DOM上的属性多达 228 个,而这些属性有 90% 多对我们来说都是无用的。VNode 就是简化版的真实 DOM 元素,保留了我们要的属性,并新增了一些在 diff 过程中需要使用的属性,例如 isStatic。 【总结】Virtual DOM 就是一个js对象,用它来更轻量地描述DOM
入口文件查找
然后--> /src/core/index.js --> /src/core/instance/index.js 最终在instance/index.js里面找到Vue的构造函数
设置情景分析
假设我们的实际场景为下面的脚本, 我们下面来分析一下
_init 入口函数
阶段一 initState()
initState方法里面有调用initData,在initData方法里面最后调用了observe(data, true)。那我们来看下observe 方法
阶段二 进入$mount() 这个方法里面比较重要
initState() 阶段完成以后,后面会执行到mount() 方法,这个方法比较关键我们一起来看下。
我们先来看一下$mount方法,我们发现一开始有一段赋值,其实就是先存下来Vue上的公共mount方法,然后又重写了公共的mount方法。
compileToFunctions方法做了什么事情呢,我先大体的介绍一下。它的最终目的是让template字符串模板——>render function 函数。compile这个编译过程在Vue2会经历3个阶段:
compile(template, options);会进入baseCompile方法, 我们来看下baseCompile方法里面的细节
阶段二(一) 生成ast
大致理解一下html-parser吧, html-parser 会按照下面几步进行html的解析
2.对ast进行预处理(preTransforms) 对ast的预处理在weex中才会有,我们直接跳过。 3、 解析v-pre、v-if、v-for、v-once、slot、key、ref等指令。 4、 对ast的class 和 style中的属性进行处理 5、 解析v-bind、v-on以及普通属性 6、 根节点或v-else块等处理 7、 模板元素父子关系的建立 8、 对ast后处理(postTransforms)
最终生成的ast长下面这样:
阶段二 optimize() 静态结点标记
源码位置: src/compiler/optimizer.js
看一下isStatic方法
然后我们的ast 会变成下面这样,标记了是否为静态结点和是否为静态根结点, 增加了一个static属性
阶段二(三) generate() 生成render function
源码位置:src/compiler/codegen/index.js 拿到ast结构以后,进入generate函数 var code = generate(ast, options);
重点看下genElement 方法
最后生成的就是下面这个对象
之后调用了 createFunction 方法其实就是new Function('string')。所以其实最终我们得到的是 render function
然后compile结束后,我们得到render function 之后,开始执行$mount的公用方法(源码位置src/platforms/web/runtime/index.js) 其实就是调用了mountComponent方法
阶段四 执行renderFunction 得到VNode
阶段五 进入Watcher 类
watcher 构造函数最后调用了this.get() , 首先调用了pushTarget(this)方法。这个方法把Dep.target设为this(即当前watcher实例)。然后执行了this.getter.call(vm, vm);这个this.getter 就是 this.getter = expOrFn; 即我们传入new Watcher 里面的第二个参数,updateComponent。updateComponent做了什么呢?它先执行了vm.render,然后执行了 vm. update
阶段六 调用render()生成VDom
下面进入了updateComponent方法,会先执行vm._render(), 我们来看下vm._render()调用后得到了什么。
vnode = render.call(vm._renderProxy, vm.$createElement);这个方法的调用,相当于执行了我们之前得到的render function。并且我们知道render function 里面有with(this) 此时我们把this指向了vm, 所以按照我们的例子, s(a+b) 在执行的时候会读取到vm.a 和 vm.b 就分别进入了a 和 b 的getter。再回头看下getter函数。
所以注意这里就是Watcher 与 Dep 关联的地方,可以认为compile 是Watcher 与 Dep之间的桥梁。
vm.render()方法最终执行完会生成VNode, 即完成了从render function —> VNode 的过程,我们看下此时vnode的样子, 大概像下面这样
得到VNode 之后,调用vm.update 方法从VNode 生成DOM。update方法内部重点调用了patch方法,看下面。因为patch内容也比较复杂,所以此次并不讲解内部相关的具体流程,会大致看下dom创建的过程。patch方法内部大致会涉及下面3部分的处理:
最后我们的结点生成并挂载了vnode.elm 上,打出来看下
接下来就会触发 insert (parent, elm, ref$$1) parent 是body, elm是上面得到的dom, 第三个参数没研究,此时elm就插入到了parent中。接下来又会回到mountComponent 接下去的方法
当data更新
当我们data的值发生了变化的时候,会进入setter函数。
所以我们此时就理解了整个Observer,Dep 与Watcher之间的关系
参考文献
1.Vue2.0 源码阅读:模板渲染 2.compile—优化静态内容 3.深入vue2.0底层思想——模板渲染 4.Vue2 源码漫游(二) 5.Vitual DOM 的内部工作原理