这两天的把 1w 多行的 vue 过了一遍,看了一下把剩下在实例化 vue 构造函数前作的全部事情,作了一个总结,写下了这个最终章,文中有哪些问题,还但愿你们能够指出。javascript
var VNode = function VNode( tag, data, children, text, elm, context, componentOptions, asyncFactory ) {
this.tag = tag;
this.data = data;
this.children = children;
this.text = text;
this.elm = elm;
this.ns = undefined;
this.context = context;
this.fnContext = undefined;
this.fnOptions = undefined;
this.fnScopeId = undefined;
this.key = data && data.key;
this.componentOptions = componentOptions;
this.componentInstance = undefined;
this.parent = undefined;
this.raw = false;
this.isStatic = false;
this.isRootInsert = true;
this.isComment = false;
this.isCloned = false;
this.isOnce = false;
this.asyncFactory = asyncFactory;
this.asyncMeta = undefined;
this.isAsyncPlaceholder = false;
};
var prototypeAccessors = { child: { configurable: true } };
prototypeAccessors.child.get = function () {
return this.componentInstance
};
Object.defineProperties(VNode.prototype, prototypeAccessors);
复制代码
经过 Object.defineProperties
为 VNode
的原型绑定了对象 prototypeAccessors
,prototypeAccessors
设置 child
是可修改的状态。vue
initMixin(Vue);
复制代码
在初始化的时候,给 initMixin
传入了 Vue
构造函数:java
function initMixin(Vue) {
Vue.prototype._init = function (options) {
var vm = this;
vm._uid = uid$3++;
var startTag, endTag;
if (config.performance && mark) {
startTag = "vue-perf-start:" + (vm._uid);
endTag = "vue-perf-end:" + (vm._uid);
mark(startTag);
}
vm._isVue = true;
// 合并选项
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
{
initProxy(vm);
}
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // 在data/props以前解决注入
initState(vm);
initProvide(vm); // 解决后提供的data/props
callHook(vm, 'created');
if (config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(("vue " + (vm._name) + " init"), startTag, endTag);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
复制代码
Vue.prototype._init = function (options) {}
复制代码
在初始化的时候,为 Vue
在 原型上,添加了个 _init
方法,这个方法在实例化 vue
构造函数的时候会被调用:express
function Vue(options) {
if (!(this instanceof Vue)
{
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
复制代码
这里对 _init
不作太多的解释,你们看一下 _init
都作了什么,等到接下来说解实例化后 vue
的时候,会对这里作详细的解释,包括生命周期的实现。api
stateMixin(Vue);
复制代码
在处理完 initMixin
后,接着对 state
作了 mixin
的处理,给 stateMixin
传入了 Vue
构造函数。数组
function stateMixin(Vue) {
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
{
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
);
};
propsDef.set = function () {
warn("$props is readonly.", this);
};
}
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
Vue.prototype.$watch = function ( expOrFn, cb, options ) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn() {
watcher.teardown();
}
};
}
复制代码
官方给该方法的解释是:缓存
在使用object.defineproperty时,流在某种程度上存在直接声明的定义对象的问题,所以咱们必须在此处按程序构建该对象。
复制代码
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
{
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
);
};
propsDef.set = function () {
warn("$props is readonly.", this);
};
}
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
复制代码
一开始,声明了两个对象 dataDef
和 propsDef
,并分别添加了 set
和 get
, 在咱们操做 vm.$data
的时候,返回的就是 this._data
,$props
也是如此;bash
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
复制代码
这里就是给 vue
实例绑定了 set
和 del
方法,这两个方法在以前的章节将结果,连接在 Vue 源码解析(实例化前) - 初始化全局API(二)async
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn() {
watcher.teardown();
}
};
复制代码
这里,就是咱们在作 vue
项目当中,常用的 api
之一,这里就是它的具体实现。ide
具体的用法,和参数的意思,能够看官方的 api
文档,那里对参数的解释和用法写的很是清楚,我这里就讲一下怎么实现的,具体意思,你们看 api
文档就好。
官方api文档路径:vm.$watch( expOrFn, callback, [options] )
在一开始,把当前的 this
指针存储在一个 vm
当变量当中;
检测若是当前的 cb
是对象的话,返回一个 createWatcher
方法:
function createWatcher( vm, expOrFn, handler, options ) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}
复制代码
这是 createWatcher
的实现。
其实就是对在调用 vm.$watch
时接受到参数,包括当前的 vue
实例,而后仍是第一步要检查 handler
是否是对象,这里的 handler
就是以前的 cb
;
若是是对象的话,就用 handler
覆盖 options
, handler.handler
去看成当前的 handler
去使用;
若是 handler
是字符串的话,就去把当前 vue
实例的该属性,看成 handler
去使用;
最后,返回一个新的 vm.$watch
。
options = options || {};
options.user = true;
复制代码
检查接收的参数 options
是否存在,不存在就设置一个空对象;
设置的 options.user
为 true
;
var watcher = new Watcher(vm, expOrFn, cb, options);
复制代码
在设置完 options.user
后,就实例化了 Watcher
这个构造函数,这里是很是核心的一块内容,但愿你们能够仔细看看,这一块看成一个大分类来说,先把下面的两行给讲了:
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn() {
watcher.teardown();
}
复制代码
若是设置了 options.immediate
为 true
,那么把当前 cb
的 this
指向 vue
的实例化构造函数,并把 watcher.value
传给 cb
;
最后返回一个函数,调用 watcher.teardown
。
var Watcher = function Watcher(
vm,
expOrFn,
cb,
options,
isRenderWatcher
) {
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
vm._watchers.push(this);
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$1;
this.active = true;
this.dirty = this.lazy;
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
this.value = this.lazy
? undefined
: this.get();
};
Watcher.prototype.get = function get() {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
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(this);
}
}
};
Watcher.prototype.cleanupDeps = function cleanupDeps() {
var i = this.deps.length;
while (i--) {
var dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
var tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
};
Watcher.prototype.update = function update() {
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
Watcher.prototype.run = function run() {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
isObject(value) ||
this.deep
) {
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.evaluate = function evaluate() {
this.value = this.get();
this.dirty = false;
};
Watcher.prototype.depend = function depend() {
var i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
};
Watcher.prototype.teardown = function teardown() {
if (this.active) {
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this);
}
var i = this.deps.length;
while (i--) {
this.deps[i].removeSub(this);
}
this.active = false;
}
};
复制代码
这是有关 Watcher
构造函数全部实现的代码。
this.vm = vm;
if (isRenderWatcher) {
vm._watcher = this;
}
复制代码
当前的 watcher
对象的 vm
属性指向的是 vue
实例化对象;
若是 isRenderWatcher
为 true
时, vue
的 _watcher
指向当前 this
;
vm._watchers.push(this);
复制代码
给 vue
实例化对象的 _watchers
数组添加一个数组项,就是当前的 watcher
实例化对象;
if (options) {
this.deep = !!options.deep;
this.user = !!options.user;
this.lazy = !!options.lazy;
this.sync = !!options.sync;
this.before = options.before;
} else {
this.deep = this.user = this.lazy = this.sync = false;
}
this.cb = cb;
this.id = ++uid$1;
this.active = true;
this.dirty = this.lazy;
this.deps = [];
this.newDeps = [];
this.depIds = new _Set();
this.newDepIds = new _Set();
this.expression = expOrFn.toString();
复制代码
这里就是 watcher
初始化的一些属性值;
var bailRE = /[^\w.$]/;
function parsePath(path) {
if (bailRE.test(path)) {
return
}
var segments = path.split('.');
return function (obj) {
for (var i = 0; i < segments.length; i++) {
if (!obj) { return }
obj = obj[segments[i]];
}
return obj
}
}
if (typeof expOrFn === 'function') {
this.getter = expOrFn;
} else {
this.getter = parsePath(expOrFn);
if (!this.getter) {
this.getter = noop;
warn(
"Failed watching path: \"" + expOrFn + "\" " +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
);
}
}
复制代码
若是接收到的 expOrFn
是个函数的话,当前 this
的 getter
就指向它;
不然,经过 parsePath
去格式化当前的路径;
若是 expOrFn
并非已单词字符结尾的,就直接返回,设置一个空的 noop
函数给当前实例的 getter
属性;
把 expOrFn
进行切割,遍历切割后的 expOrFn
,并把切割后的每一个数组项看成要返回的函数的接收到的 obj
的属性。
this.value = this.lazy ? undefined : this.get();
复制代码
若是 lazy
是 true
的话,this.value
的就是 undefinend
,不然就是调用 this.get
方法
function pushTarget(target) {
targetStack.push(target);
Dep.target = target;
}
function popTarget() {
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1];
}
Watcher.prototype.get = function get() {
pushTarget(this);
var value;
var vm = this.vm;
try {
value = this.getter.call(vm, vm);
} catch (e) {
if (this.user) {
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value);
}
popTarget();
this.cleanupDeps();
}
return value
};
复制代码
在 Vue 源码解析(实例化前) - 响应式数据的实现原理 讲解 Dep
构造函数的时候,涉及到过,这里开始仔细讲解,具体调用的地方,能够去以前的章节查看。
get
在一开始的时候,讲调用了 pushTarget
方法,并把当前 watcher
实例化对象传过去;
pushTarget
方法,就是给 targetStack
数组添加一个数组项,就是当前的 watcher
实例化对象;
把当前的 watcher
实例化对象 指向 Dep.target
;
this.getter
的 this
指向 vue
的实例化对象,并调用它,把当前的值去作获取返回到 value
;
若是设置了 this.deep
为 true
,就表明用户想要发现对象内部值的变化,这个时候调用 traverse
函数,目的是递归遍历一个对象以唤起全部转换的getter,以便将对象内的每一个嵌套属性收集为“深度”依赖项,把最后的结果更新到 value
;
popTarget
把在 targetStack
数组中的最后一个删除,并把 Dep.target
指向删除后的数组的最后一个数组项。
在这里,其实就是给在获取当前数据时的
watcher
在一开始作了存储 (targetStack
),在全部值的展现和处理作完之后,在清空了存储 (targetStack
)
this.cleanupDeps();
复制代码
清除依赖项集合,接下来说。
return value
复制代码
最后返回 value
。
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(this);
}
}
};
复制代码
检查队列是否存在当前的 id
,该 id
其实就是 Dep
的实例化对象的 id
,把它添加到对应的队列里面去;
这里其实比较简单明了,就不作太复杂的解释了,你们看一眼就明白了。
Watcher.prototype.cleanupDeps = function cleanupDeps() {
var i = this.deps.length;
while (i--) {
var dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
var tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
};
复制代码
把当前 Watcher
监听队列里的 Watcher
对象从后往前清空,在把一些属性初始化。
Watcher.prototype.update = function update() {
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
复制代码
若是是懒更新的话,设置 dirty
为 true
;
若是是同步更新的话,直接调用 run
方法;
不然,调用 queueWatcher
方法。
// 将观察者推入观察者队列。
// 具备重复ID的工做将被跳过,除非在刷新队列时将其推送。
function queueWatcher(watcher) {
var id = watcher.id;
if (has[id] == null) {
has[id] = true;
if (!flushing) {
queue.push(watcher);
} else {
// 若是已经刷新,则根据其ID拼接观察程序
// 若是已经超过了它的ID,它将当即运行。
var i = queue.length - 1;
while (i > index && queue[i].id > watcher.id) {
i--;
}
queue.splice(i + 1, 0, watcher);
}
// 刷新队列
if (!waiting) {
waiting = true;
if (!config.async) {
flushSchedulerQueue();
return
}
nextTick(flushSchedulerQueue);
}
}
}
复制代码
若是设置的 config.async
是同步的,那么就刷新两个队列并运行 Watcher
结束当前方法;
不然的话,执行 nextTick
后执行 flushSchedulerQueue
。
var MAX_UPDATE_COUNT = 100;
var queue = [];
var activatedChildren = [];
var has = {};
var circular = {};
var waiting = false;
var flushing = false;
var index = 0;
// 重置计划程序的状态
function resetSchedulerState() {
index = queue.length = activatedChildren.length = 0;
has = {};
{
circular = {};
}
waiting = flushing = false;
}
function flushSchedulerQueue() {
flushing = true;
var watcher, id;
//在刷新以前排队队列。
//这能够确保:
// 1.组件从父级更新为子级。 (由于父母老是在孩子面前建立)
// 2.组件的用户观察者在其渲染观察者以前运行(由于在渲染观察者以前建立用户观察者)
// 3.若是在父组件的观察程序运行期间销毁了组件,能够跳过其观察者。
queue.sort(function (a, b) { return a.id - b.id; });
//不要缓存长度,由于可能会推送更多的观察程序
//当咱们运行现有的观察程序时
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
if (watcher.before) {
watcher.before();
}
id = watcher.id;
has[id] = null;
watcher.run();
//在开发构建中,检查并中止循环更新。
if (has[id] != null) {
circular[id] = (circular[id] || 0) + 1;
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? ("in watcher with expression \"" + (watcher.expression) + "\"")
: "in a component render function."
),
watcher.vm
);
break
}
}
}
// 重置状态前保留发布队列的副本
var activatedQueue = activatedChildren.slice();
var updatedQueue = queue.slice();
resetSchedulerState();
// 调用组件更新和激活的钩子
callActivatedHooks(activatedQueue);
callUpdatedHooks(updatedQueue);
if (devtools && config.devtools) {
devtools.emit('flush');
}
}
function callUpdatedHooks(queue) {
var i = queue.length;
while (i--) {
var watcher = queue[i];
var vm = watcher.vm;
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated');
}
}
}
function callActivatedHooks(queue) {
for (var i = 0; i < queue.length; i++) {
queue[i]._inactive = true;
activateChildComponent(queue[i], true /* true */);
}
}
复制代码
Watcher.prototype.run = function run() {
if (this.active) {
var value = this.get();
if (value !== this.value || isObject(value) || this.deep) {
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);
}
}
}
};
复制代码
只有 active
是 true
的时候,执行 run
才有效果,由于 active
在调用 teardown
方法的时候会变成 false
;
检查新值和旧值是否相同,若是不相同的话,把新值和旧值传递给 cb
回调,并把 this
指向 vue
实例。
这样可能你们理解的会更容易点。
Watcher.prototype.evaluate = function evaluate() {
this.value = this.get();
this.dirty = false;
};
复制代码
评估观察者的价值,这只适用于懒惰的观察者。
Watcher.prototype.depend = function depend() {
var i = this.deps.length;
while (i--) {
this.deps[i].depend();
}
};
复制代码
这里就是真正的实现通知依赖的部分。
Watcher.prototype.teardown = function teardown() {
if (this.active) {
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this);
}
var i = this.deps.length;
while (i--) {
this.deps[i].removeSub(this);
}
this.active = false;
}
};
复制代码
清空全部依赖,从后往前。
原本是准备这一章把 Vue
构造函数实例化前要作的全部事情都写完,发如今 state
的 mixin
时候,涉及到了 watcher
,可是发现了,就先讲解了,以后仍是有一样量的内容,因此仍是准备单拿出来一讲,篇幅太长了对你们学习和吸取并不友好。
接下来的一章,会讲到:
events
的 minxin
:$on
、 $once
、 $off
、 $emit
;
lifecycle
的 minxin
:updated
、 $forceUpdate
、 $destroy
;
render
的 minxin
: $nextTick
、 render
下一章,就是 vue
源码解析(实例化前) - 初始化全局 API(最终章)了,文中有写的不对的,还但愿你们能够积极指出。