Vue 源码解析(实例化前) - 初始化全局API(二)
栏目: JavaScript · 发布时间: 5年前
内容简介:由于数据双向绑定的内容会比较多一些,而且涉及到的知识点也比较多,所以我当时就从初始化全局API里面单独拿了一章出来,去讲解 vue 到底是如何实现的数据双向绑定,现在,接着把之前没有讲完的初始化全局API要做的事情,全都给讲完。还是那句老话,如果觉得写的哪里不对了,还希望大家多多指出,欢迎评论;如果觉得不错的话,点点关注,点点赞,谢谢大家,你们的支持,是我继续写下去的动力:pray:
由于数据双向绑定的内容会比较多一些,而且涉及到的知识点也比较多,所以我当时就从初始化全局API里面单独拿了一章出来,去讲解 vue 到底是如何实现的数据双向绑定,现在,接着把之前没有讲完的初始化全局API要做的事情,全都给讲完。
还是那句老话,如果觉得写的哪里不对了,还希望大家多多指出,欢迎评论;
如果觉得不错的话,点点关注,点点赞,谢谢大家,你们的支持,是我继续写下去的动力:pray:
正文
Vue.set = set; Vue.delete = del; Vue.nextTick = nextTick; Vue.options = Object.create(null); ASSET_TYPES.forEach(function (type) { Vue.options[type + 's'] = Object.create(null); }); Vue.options._base = Vue; extend(Vue.options.components, builtInComponents); initUse(Vue); initMixin$1(Vue); initExtend(Vue); initAssetRegisters(Vue); 复制代码
这篇文章,讲的就是 initGlobalAPI 函数剩下的这一部分。
设置对象属性(set)
Vue.set = set; 复制代码
该属性,就是用来设置对象的属性,如果属性不存在,则添加新属性并触发更改通知。
function set (target, key, val) { if (isUndef(target) || isPrimitive(target) ) { warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); } if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key); target.splice(key, 1, val); return val } if (key in target && !(key in Object.prototype)) { target[key] = val; return val } var ob = (target).__ob__; if (target._isVue || (ob && ob.vmCount)) { warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ); return val } if (!ob) { target[key] = val; return val } defineReactive(ob.value, key, val); ob.dep.notify(); return val } 复制代码
set 方法,接收三个参数, target (目标)、 key (属性)、val (值);
function isUndef (v) { return v === undefined || v === null } function isPrimitive (value) { return ( typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean' ) } 复制代码
一进来,就先检查,当前的目标是不是上面的这几种类型,如果是的话,是没有办法去做类似根据对应的 key 去做值更新的;
这里其实最主要检查的就是当前的 target 是不是对象或者数组,可是作者在这里,做了除对象和数组以外所有类型的判断,不知道这样做的目的是什么,为什么不直接判断数组和对象呢,那样需要执行的代码也会比较少,如果大家知道为什么要这么做,欢迎评论。
if (Array.isArray(target) && isValidArrayIndex(key)) { } 复制代码
检查当前的 target 是不是数组,并且 key 是不是有效的数组索引:
function isValidArrayIndex (val) { var n = parseFloat(String(val)); return n >= 0 && Math.floor(n) === n && isFinite(val) } 复制代码
这里接收到的其实就是数组的 key ,先把 key 转成字符串,然后通过 parseFloat 去格式化它;
然后去看 n 是不是大于0的正整数,并且不是无穷大的。
target.length = Math.max(target.length, key); target.splice(key, 1, val); return val 复制代码
如果是数组,并且该索引也是合法的,那么用接收到的 val 去替换当前索引为 key 的索引项;
if (key in target && !(key in Object.prototype)) { target[key] = val; return val } 复制代码
这里要判断的是如果当前的 target 是对象,并且存在当前 key , 并且当前 key 不是在 Object 的原型上的,那么就用接收到新的 val 去替换当前的值;
var ob = (target).__ob__; 复制代码
__ ob __ 属性是我们在打印 vue 的实例化对象时经常看到的,这个属性其实就是在实例化 Observer 的时候,调用 def 函数去给当前目标添加的一个属性:
// Observer部分 var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, '__ob__', this); if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else { this.walk(value); } }; // def部分 function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }); } 复制代码
在这里对 Observer 不做太多的讲解,因为现在主要讲的是 set 部分,在往下讲,可能会比较乱,大家先自己看一下,看不懂的话,在接下来讲实例化 vue 部分的时候在仔细对 vue 的每一个重要的 api 做详细的讲解。
_isVue 和 vmCount 在 Vue 源码解析(实例化前) - 数据双向绑定的实现原理 这里有做过讲解,大家不了解这两个值是干什么的,可以来这里看一下。
if (!ob) { target[key] = val; return val } 复制代码
如果当前的目标没有 __ ob __属性,那么就直接做赋值,并且返回;
defineReactive(ob.value, key, val); ob.dep.notify(); 复制代码
这里在上面的链接里面,就可以看到具体做了哪些事情,这里就不浪费篇幅去写了;
最后直接返回当前值。
这里总结一下,其实就是如果是存在值的话,直接更新值,如果不存在的话,就通过 defineReactive 去绑定一下 复制代码
删除对象属性(del)
Vue.delete = del; 复制代码
这个方法就是在删除属性并在必要时触发更改。
function del (target, key) { if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) { warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); } if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return } var ob = (target).__ob__; if (target._isVue || (ob && ob.vmCount)) { process.env.NODE_ENV !== 'production' && warn( 'Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.' ); return } if (!hasOwn(target, key)) { return } delete target[key]; if (!ob) { return } ob.dep.notify(); } 复制代码
这个其实和 set 做的事情差不多,只是修改变成了删除,这里就不多讲了。
nextTick
function nextTick(cb, ctx) { var _resolve; callbacks.push(function () { if (cb) { try { cb.call(ctx); } catch (e) { handleError(e, ctx, 'nextTick'); } } else if (_resolve) { _resolve(ctx); } }); if (!pending) { pending = true; if (useMacroTask) { macroTimerFunc(); } else { microTimerFunc(); } } if (!cb && typeof Promise !== 'undefined') { return new Promise(function (resolve) { _resolve = resolve; }) } } 复制代码
callbacks 是一个全局的消息频道,在每次调用 nextTick的时候,去添加一个 function ,这个 function 里面最主要做的就是一个判断 cb 是否存在,存在就把 cb 的 this 指向 ctx,如果 _resolve 存在的话,就直接调用它,并把 ctx 传给它。
if (!pending) { pending = true; if (useMacroTask) { macroTimerFunc(); } else { microTimerFunc(); } } 复制代码
pending 是一个全局的状态,初始化是 false ,在第一次调用它的时候,直接改为 true ,并且要检查当前执行的是宏任务还是微任务,在执行到任务栈后,pending 变为 false,具体什么是宏任务,什么是微任务,看这里: js事件循环机制(event loop)
if (!cb && typeof Promise !== 'undefined') { return new Promise(function (resolve) { _resolve = resolve; }) } 复制代码
这里代码很简单,这里就不做太多解释了,大家不懂了评论把。:joy:
Vue.options = Object.create(null); 复制代码
给 Vue 的 options 添加一个原子。
ASSET_TYPES.forEach(function (type) { Vue.options[type + 's'] = Object.create(null); }); 复制代码
给 options 添加 n 个属性,每个属性都是一个原子:
var ASSET_TYPES = [ 'component', 'directive', 'filter' ]; 复制代码
这是 ASSET_TYPES 所有的数组项;
Vue.options._base = Vue; 复制代码
这用于标识“基础”构造函数以扩展所有普通对象;
extend(Vue.options.components, builtInComponents); 复制代码
extend 在第一章已经讲过了,在这里: Vue 源码解析(实例化前) - 初始化全局API(一)
使用插件 initUse
initUse(Vue); 复制代码
这个方法,其实实现的就是一个 use 的方法,用来添加插件的:
function initUse(Vue) { Vue.use = function (plugin) { var installedPlugins = (this._installedPlugins || (this._installedPlugins = [])); if (installedPlugins.indexOf(plugin) > -1) { return this } var args = toArray(arguments, 1); args.unshift(this); if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args); } else if (typeof plugin === 'function') { plugin.apply(null, args); } installedPlugins.push(plugin); return this }; } 复制代码
在这里,就是给 Vue 构造函数,在实例化前绑定哪些插件,所有的插件,都在 installedPlugins 里面。
初始化合并 initMixin$1
function initMixin$1(Vue) { Vue.mixin = function (mixin) { this.options = mergeOptions(this.options, mixin); return this }; } 复制代码
mergeOptions 方法的实现在这里: Vue 源码解析(实例化前) - 初始化全局API(一)
初始化继承 initExtend
Vue.extend = function (extendOptions) { extendOptions = extendOptions || {}; var Super = this; var SuperId = Super.cid; var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}); if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } var name = extendOptions.name || Super.options.name; if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name); } var Sub = function VueComponent (options) { this._init(options); }; Sub.prototype = Object.create(Super.prototype); Sub.prototype.constructor = Sub; Sub.cid = cid++; Sub.options = mergeOptions( Super.options, extendOptions ); Sub['super'] = Super; // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) { initProps$1(Sub); } if (Sub.options.computed) { initComputed$1(Sub); } // allow further extension/mixin/plugin usage Sub.extend = Super.extend; Sub.mixin = Super.mixin; Sub.use = Super.use; // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type]; }); // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub; } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options; Sub.extendOptions = extendOptions; Sub.sealedOptions = extend({}, Sub.options); // cache constructor cachedCtors[SuperId] = Sub; return Sub }; } 复制代码
这里,就是 extend 的方法的实现,其实最主要就是输出了一个函数,继承了 Vue 构造函数的所有属性和方法
extendOptions = extendOptions || {}; var Super = this; var SuperId = Super.cid; var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}); if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } 复制代码
检查 extendOptions 是否存在,如果存在 extendOptions 上是否存在 _Ctor 并且存在 SuperId ,如果存在直接就返回 cachedCtors[SuperId];
var name = extendOptions.name || Super.options.name; if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name); } 复制代码
这里检查的就是 name 的格式,是否是 xxx-xxx 这种格式的,在第一章里面有详细解释;
if (Sub.options.props) { initProps$1(Sub); } function initProps$1 (Comp) { var props = Comp.options.props; for (var key in props) { proxy(Comp.prototype, "_props", key); } } 复制代码
这里做了初始化属性的遍历,给所有属性都绑定了 Object.defineProperty;
var sharedPropertyDefinition = { enumerable: true, configurable: true, get: noop, set: noop }; function proxy (target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); } 复制代码
这是 proxy 函数的实现过程。
if (Sub.options.computed) { initComputed$1(Sub); } function initComputed$1 (Comp) { var computed = Comp.options.computed; for (var key in computed) { defineComputed(Comp.prototype, key, computed[key]); } } function defineComputed ( target, key, userDef ) { var shouldCache = !isServerRendering(); if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : userDef; sharedPropertyDefinition.set = noop; } else { sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache !== false ? createComputedGetter(key) : userDef.get : noop; sharedPropertyDefinition.set = userDef.set ? userDef.set : noop; } if (process.env.NODE_ENV !== 'production' && sharedPropertyDefinition.set === noop) { sharedPropertyDefinition.set = function () { warn( ("Computed property \"" + key + "\" was assigned to but it has no setter."), this ); }; } Object.defineProperty(target, key, sharedPropertyDefinition); } 复制代码
这段代码是对 computed 做的处理,也是绑定了 Object.defineProperty,这种类似的代码处理条件,在前两章已经讲了很多了,大家这里看一下其实就差不多了,如果不明白,就去看看前两章把。
Sub.extend = Super.extend; Sub.mixin = Super.mixin; Sub.use = Super.use; ASSET_TYPES.forEach(function (type) { Sub[type] = Super[type]; }); if (name) { Sub.options.components[name] = Sub; } Sub.superOptions = Super.options; Sub.extendOptions = extendOptions; Sub.sealedOptions = extend({}, Sub.options); // cache constructor cachedCtors[SuperId] = Sub; return Sub 复制代码
这里其实就是让继承 Vue 的子级方法,可以做 Vue 可以做的事情。
初始化资源注册 initAssetRegisters
function initAssetRegisters(Vue) { ASSET_TYPES.forEach(function (type) { Vue[type] = function ( id, definition ) { if (!definition) { return this.options[type + 's'][id] } else { if (type === 'component') { validateComponentName(id); } if (type === 'component' && isPlainObject(definition)) { definition.name = definition.name || id; definition = this.options._base.extend(definition); } if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition }; } this.options[type + 's'][id] = definition; return definition } }; }); } 复制代码
这个方法,就是把 ASSET_TYPES 所有的数组项当作 Vue 的方法名来定义,这里就只有注册,没有做太多的事情,最后返回处理后的 definition。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 源码解析(实例化前) - 初始化全局API(三)
- vue 源码解析(实例化前) - 初始化全局 API(最终章)
- C++ 的一大误区——深入解释直接初始化与复制初始化的区别
- 初始化监听端口
- 类初始化导致死锁
- nodejs源码—初始化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。