Vue 源码解析(实例化前) - 初始化全局API(三)
栏目: JavaScript · 发布时间: 5年前
内容简介:这两天的把 1w 多行的 vue 过了一遍,看了一下把剩下在实例化 vue 构造函数前做的所有事情,做了一个总结,写下了这个最终章,文中有哪些问题,还希望大家可以指出。通过在初始化的时候,给
这两天的把 1w 多行的 vue 过了一遍,看了一下把剩下在实例化 vue 构造函数前做的所有事情,做了一个总结,写下了这个最终章,文中有哪些问题,还希望大家可以指出。
正文
为VNode原型添加 child 属性监听
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
是可修改的状态。
初始化Mixin
initMixin(Vue); 复制代码
在初始化的时候,给 initMixin
传入了 Vue
构造函数:
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
构造函数的时候会被调用:
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
的时候,会对这里做详细的解释,包括生命周期的实现。
state 的 mixin
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
也是如此;
Vue.prototype.$set = set; Vue.prototype.$delete = del; 复制代码
这里就是给 vue
实例绑定了 set
和 del
方法,这两个方法在之前的章节将结果,链接在 Vue 源码解析(实例化前) - 初始化全局API(二)
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
之一,这里就是它的具体实现。
具体的用法,和参数的意思,可以看官方的 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
。
Watcher 实现
Watcher 构造函数
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
构造函数所有实现的代码。
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
方法
Watcher 获取值
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 添加队列
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 清空队列
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 更新
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 运行
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 评估
Watcher.prototype.evaluate = function evaluate() { this.value = this.get(); this.dirty = false; }; 复制代码
评估观察者的价值,这只适用于懒惰的观察者。
Watcher 依赖
Watcher.prototype.depend = function depend() { var i = this.deps.length; while (i--) { this.deps[i].depend(); } }; 复制代码
这里就是真正的实现通知依赖的部分。
Watcher 卸载
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(最终章)了,文中有写的不对的,还希望大家可以积极指出。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 源码解析(实例化前) - 初始化全局API(二)
- vue 源码解析(实例化前) - 初始化全局 API(最终章)
- C++ 的一大误区——深入解释直接初始化与复制初始化的区别
- 初始化监听端口
- 类初始化导致死锁
- nodejs源码—初始化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C程序设计(第四版)
谭浩强 / 清华大学出版社 / 2010-6-1 / 33.00元
由谭浩强教授著、清华大学出版社出版的《C程序设计》是一本公认的学习C语言程序设计的经典教材。根据C语言的发展和计算机教学的需要,作者在《C程序设计(第三版)》的基础上进行了修订。 《C程序设计(第4版)》按照C语言的新标准C99进行介绍,所有程序都符合C99的规定,使编写程序更加规范;对C语言和程序设计的基本概念和要点讲解透彻,全面而深入;按照作者提出的“提出问题―解决问题―归纳分析”三部曲......一起来看看 《C程序设计(第四版)》 这本书的介绍吧!