mergeOptions() 方法
文件位置:src\core\util\options.js
主要就是将两个选项对象合并为一个新对象,也是实例化和继承的核心.
export function mergeOptions ( parent: Object, child: Object, vm?: Component ): Object { if (process.env.NODE_ENV !== 'production') { checkComponents(child) } // 如果 child 是函数,就送函数上获取配置项 if (typeof child === 'function') { child = child.options } // 对选项配置进行标准化 normalizeProps(child, vm) normalizeInject(child, vm) normalizeDirectives(child) // 在子选项上进行 extends (扩展) 和 mixins (混合) // 并且必须是原始配置对象,也就没有经过合并的配置对象 // 而每个被合并过的配置对象上都存在 _base 属性 if (!child._base) { // 组件的 extents 和 Vue.extend 是一样的,extents 为了便于扩展单文件组件 // var CompA = { ... } , var CompB = { extends: CompA, ... }, B 继承了 A if (child.extends) { parent = mergeOptions(parent, child.extends, vm) } if (child.mixins) { for (let i = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } } const options = {} let key for (key in parent) { mergeField(key) } for (key in child) { if (!hasOwn(parent, key)) { mergeField(key) } } function mergeField (key) { // 选项合并策略 // 默认策略为:如果 child 有值,就会覆盖 parent 值,否则使用 parent 值 const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key) } return options } 复制代码
Vue.extend() 方法
文件位置:src\core\global-api\extend.js
使用基础 Vue 构造器,创建一个 子类,参数是一个包含组件选项的对象,data 选项是特例,因为在 Vue.extend() 中它必须是函数.
export function initExtend (Vue: GlobalAPI) { /** * Each instance constructor, including Vue, has a unique * cid. This enables us to create wrapped "child * constructors" for prototypal inheritance and cache them. */ Vue.cid = 0 let cid = 1 /** * Class inheritance * 使用 Vue.extend,创建一个子类,参数是一个包含组件选项的对象 */ Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} const Super = this const SuperId = Super.cid const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) // 缓存:如果使用同一个混入配置项,如果缓存中存在已存在,就直接使用缓存的值 if (cachedCtors[SuperId]) { return cachedCtors[SuperId] } // 校验组件名称 const name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(name) } /* 核心:定义一个 Vue 子类,本质上和 Vue 构造函数一样 function Vue (options) { this._init(options) } */ const 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. // 将 props 代理到子类上,在子类中可以直接通过 this.props 形式访问 if (Sub.options.props) { initProps(Sub) } // 将 computed 代理到子类上,在子类中可以直接通过 this.computed 形式访问 if (Sub.options.computed) { initComputed(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 // 组件递归自调用的原理 // { name:'comp', components: { Comp } } 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 } } 复制代码
initAssetRegisters() 方法
文件位置:src\core\global-api\assets.js
这个方法就是负责初始化 Vue.component()、Vue.directive()、Vue.filter() 方法,因为这三个 api 实现上比较特殊,但是原理又很相似,所以就统一放在 initAssetRegisters 中进行初始化.
export function initAssetRegisters (Vue: GlobalAPI) { /** * Create asset registration methods. * 统一初始化 Vue.component, Vue.directive, Vue.filter */ ASSET_TYPES.forEach(type => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { // 如果对应的 definition 没有传递,直接返回 if (!definition) { return this.options[type + 's'][id] } else { /* istanbul ignore if */ // 校验组件名字 if (process.env.NODE_ENV !== 'production' && type === 'component') { validateComponentName(id) } // 如果是组件 if (type === 'component' && isPlainObject(definition)) { // 设置组件的 name,如果是配置对象就先取 options.name,不存在就取传入的第一个值 definition.name = definition.name || id // 通过 Vue.extend 方法,基于 definition 扩展一个新的组件子类,直接 new definition() 实例化一个组件 definition = this.options._base.extend(definition) } // 如果是指令 if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } // 将每个配置项都放到根组件的对应配置项中 // 如:{components:{ id: comonent },directives:{id: directive} ,filters:{ id: filter },... } this.options[type + 's'][id] = definition return definition } } }) } 复制代码
总结
Vue.use(plugin) 的作用是什么?
- Vue.use 是用来安装 Vue 插件的
- 如果插件是一个 对象,必须提供 install 方法
- 如果插件是一个 函数,它会被作为 install 方法
- 当 install 方法调用时,会将 Vue 作为参数传入
- 该方法需要在调用 new Vue() 之前被调用
- 当 install 方法被同一个插件 多次调用,插件将只会被安装一次
Vue.mixin(options) 的作用是什么?
- Vue.mixin 本质上就是调用 mergeOptions( parent: Object, child: Object, vm?: Component),作用就是将两个选项对象合并为一个新对象
- 首先对 props、inject、directives 选项进行标准化处理
- 处理 options 上的 extends 和 mixins,最终将它们合并到 全局配置 上
- options 配置和 全局配置 进行合并发生选项冲突时,options 配置会覆盖 全局配置
PS:当使用组件配置项
mixins进行混入配置项时
- 没有冲突就正常进行合并选项
- 除了 生命周期 钩子发生冲突时,会在组件对应生命周期钩子前被调用,其他如 methods、components 和 directives 等选项中发生冲突时以 组件 数据 优先
Vue.component(compName, Comp) 的作用是什么?
Vue.component 用于注册全局组件.
本质上就是将 当前组件配置 注册到全局配置的 components 选项上($root.options.components),然后各个子组件在生成 vnode 时会将全局的 components 选项合并到局部的 components 配置项上,自动实现局部注册组件.
- Vue.component(compName) 表示获取 compName 的组件构造函数
- 若 Comp 是组件配置对象,则使用 Vue.extend 方法得到组件构造函数,否则直接进行下一步
- 在全局配置上添加当前组件信息,
this.options.components = { compName:CompConstructor, xxx }
Vue.directive('my-directive', definition) 的作用是什么?
Vue.directive 用于注册或获取全局指令,在每个子组件在生成 vnode 时会将全局的 directives 选项合并到局部的 directives 选项中,如 this.options.directives = { directive:{xxx} }
- 如果 definition 为空,则会获取指定指令的配置对象
- 如果 definition 是一个函数,则会在 bind 和 update 时调用这个函数,相当于配置对象
{ bind: definition, update: definition }
Vue.filter('my-filter', definition) 的作用是什么?
Vue.filter 用于注册或获取全局过滤器,在每个子组件在生成 vnode 时会将全局的 filters 选项合并到局部的 filters 选项中,如 this.options.filters = { filters:{xxx} }
- 如果 definition 为空,则获取 my-filter 过滤器的回调函数
- 如果 definition 为存在,则注册
this.options.filters['my-filter'] = definition
Vue.extend(extendOptions) 的作用是什么?
Vue.extend 基于 Vue 创建一个子类,通过 mergeOptions(Super.options, extendOptions) 生成新的配置项作为该子类默认的全局配置,因此子类也可以通过 extend() 方法得到自己的子类。
- 定义子类构造函数,和基类 Vue 一样也是调用 this._init(options)
- 将 Vue.options 和 extendOptions 合并,如果选项冲突,则 extendOptions 会覆盖 Vue.options 中对应的内容,相当于进行预设
- 给子类定义和 Vue 一样的全局 API,比如
Sub.extend = Super.extend - 向外返回子类 Sub
Vue.set(target, key, val) 和 Vue.delete(target, key) 的作用是什么?
Vue.set(target, key, val) —— 新增或更新对应属性或元素值
- 向响应式对象中添加一个新的 property,对新属性设置 getter 和 setter,读取时进行依赖收集,设置时进行依赖更新通知
- 如果 target 是数组,则 key 需要为数组中对应的 index,本质上是通过重写的 splice 方法进行更新数组元素
- 如果 target 是对象,则直接更新对应属性数据
- 如果 target 不是响应式对象,对应操作会成功,但是不会具备响应式
- 不能向 Vue 实例或者 $data 动态添加根级别的响应式数据Vue.delete(target, key) —— 删除对象的 property
- 如果 target 是响应式对象,确保删除对应属性后能触发更新视图
- 如果 target 是数组,还是通过重写的 splice 方法进行删除元素
- 如果 target 是对象,则通过 delete target[key] + ob.dep.notify() 实现删除并更新视图