此篇主要手写 Vue2.0 源码-全局 api 原理
上一篇咱们主要介绍了 Vue 计算属性原理 知道了计算属性缓存的特点是怎么实现的 到目前为止整个 Vue 源码核心内容 咱们已经基本手写了一遍 那么此篇来梳理下 Vue 的全局 api
适用人群:
1.想要深入理解 vue 源码更好的进行日常业务开发
2.想要在简历写上精通 vue 框架源码(再也不怕面试官的连环夺命问 哈哈)
3.没时间去看官方源码或者初看源码觉得难以理解的同学
// src/global-api/index.js // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. Vue.util = { warn, extend, mergeOptions, defineReactive, }; 复制代码
Vue.util 是 Vue 内部的工具方法 不推荐业务组件去使用 因为可能随着版本发生变动 如果咱们不开发第三方 Vue 插件确实使用会比较少
export function set(target: Array<any> | Object, key: any, val: any): any { // 如果是数组 直接调用我们重写的splice方法 可以刷新视图 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; } const ob = (target: any).__ob__; // 如果对象本身就不是响应式 不需要将其定义成响应式属性 if (!ob) { target[key] = val; return val; } // 利用defineReactive 实际就是Object.defineProperty 将新增的属性定义成响应式的 defineReactive(ob.value, key, val); ob.dep.notify(); // 通知视图更新 return val; } 复制代码
export function del(target: Array<any> | Object, key: any) { // 如果是数组依旧调用splice方法 if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1); return; } const ob = (target: any).__ob__; // 如果对象本身就没有这个属性 什么都不做 if (!hasOwn(target, key)) { return; } // 直接使用delete 删除这个属性 delete target[key]; // 如果对象本身就不是响应式 直接返回 if (!ob) { return; } ob.dep.notify(); //通知视图更新 } 复制代码
这两个 api 其实在实际业务场景使用还是很多的 set 方法用来新增响应式数据 delete 方法用来删除响应式数据 因为 Vue 整个响应式过程是依赖 Object.defineProperty 这一底层 api 的 但是这个 api 只能对当前已经声明过的对象属性进行劫持 所以新增的属性不是响应式数据 另外直接修改数组下标也不会引发视图更新 这个是考虑到性能原因 所以我们需要使用KaTeX parse error: Expected 'EOF', got '和' at position 5: set 和̲delete 来进行操作 对响应式原理不熟悉的可以看手写 Vue2.0 源码(一)-响应式数据原理
let callbacks = []; //回调函数 let pending = false; function flushCallbacks() { pending = false; //把标志还原为false // 依次执行回调 for (let i = 0; i < callbacks.length; i++) { callbacks[i](); } } let timerFunc; //先采用微任务并按照优先级优雅降级的方式实现异步刷新 if (typeof Promise !== "undefined") { // 如果支持promise const p = Promise.resolve(); timerFunc = () => { p.then(flushCallbacks); }; } else if (typeof MutationObserver !== "undefined") { // MutationObserver 主要是监听dom变化 也是一个异步方法 let counter = 1; const observer = new MutationObserver(flushCallbacks); const textNode = document.createTextNode(String(counter)); observer.observe(textNode, { characterData: true, }); timerFunc = () => { counter = (counter + 1) % 2; textNode.data = String(counter); }; } else if (typeof setImmediate !== "undefined") { // 如果前面都不支持 判断setImmediate timerFunc = () => { setImmediate(flushCallbacks); }; } else { // 最后降级采用setTimeout timerFunc = () => { setTimeout(flushCallbacks, 0); }; } export function nextTick(cb) { // 除了渲染watcher 还有用户自己手动调用的nextTick 一起被收集到数组 callbacks.push(cb); if (!pending) { // 如果多次调用nextTick 只会执行一次异步 等异步队列清空之后再把标志变为false pending = true; timerFunc(); } } 复制代码
nextTick 是 Vue 实现异步更新的核心 此 api 在实际业务使用频次也很高 一般用作在数据改变之后立马要获取 dom 节点相关的属性 那么就可以把这样的方法放在 nextTick 中去实现 异步更新原理可以看手写 Vue2.0 源码(五)-异步更新原理
Vue.observable = <T>(obj: T): T => { observe(obj); return obj; }; 复制代码
核心就是调用 observe 方法将传入的数据变成响应式对象 可用于制造全局变量在组件共享数据 具体 observe 方法可以看响应式数据原理-对象的数据劫持
Vue.options = Object.create(null); ASSET_TYPES.forEach((type) => { Vue.options[type + "s"] = Object.create(null); }); // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue; extend(Vue.options.components, builtInComponents); //内置组件 复制代码
Vue.options 是存放组件 指令和过滤器的容器 并且 Vue.options._base 指向 Vue 构造函数
Vue.use = function (plugin: Function | Object) { const installedPlugins = this._installedPlugins || (this._installedPlugins = []); if (installedPlugins.indexOf(plugin) > -1) { // 如果安装过这个插件直接返回 return this; } const args = toArray(arguments, 1); // 获取参数 args.unshift(this); //在参数中增加Vue构造函数 if (typeof plugin.install === "function") { plugin.install.apply(plugin, args); // 执行install方法 } else if (typeof plugin === "function") { plugin.apply(null, args); // 没有install方法直接把传入的插件执行 } // 记录安装的插件 installedPlugins.push(plugin); return this; }; 复制代码
Vue.use 主要用于插件的注册 调用插件的 install 方法 并且把自身 Vue 传到插件的 install 方法 这样可以避免第三方插件强依赖 Vue
export function initMixin(Vue: GlobalAPI) { Vue.mixin = function (mixin: Object) { this.options = mergeOptions(this.options, mixin); //只要调用mergeOptions来合并选项 return this; }; } /** * Merge two option objects into a new one. * Core utility used in both instantiation and inheritance. */ export function mergeOptions( parent: Object, child: Object, vm?: Component ): Object { if (!child._base) { // 这个代表是组件 需要先把自己定义的extends和mixins与父级属性进行合并 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) { //真正合并字段的方法 const strat = strats[key] || defaultStrat; //strats代表合并策略 会优先查找对应的合并策略 找不到就用默认的合并策略 options[key] = strat(parent[key], child[key], vm, key); } return options; } 复制代码
Vue.mixin 是全局混入方法 一般用作提取全局的公共方法和属性 想深入了解这块的可以看手写 Vue2.0 源码(七)-Mixin 混入原理
Vue.extend = function (extendOptions: Object): Function { const Sub = function VueComponent(options) { // 创建子类的构造函数 并且调用初始化方法 this._init(options); }; Sub.prototype = Object.create(Super.prototype); // 子类原型指向父类 Sub.prototype.constructor = Sub; //constructor指向自己 Sub.options = mergeOptions( //合并自己的options和父类的options Super.options, extendOptions ); return Sub; }; 复制代码
Vue.extend 被称为组件构造器 Vue 的组件创建就是依赖于此 api 其实就是利用原型继承的方式创建继承自 Vue 的子类 对组件初始化和渲染感兴趣的可以看手写 Vue2.0 源码(八)-组件原理
export function initAssetRegisters(Vue: GlobalAPI) { var ASSET_TYPES = ["component", "directive", "filter"]; /** * Create asset registration methods. */ ASSET_TYPES.forEach((type) => { Vue[type] = function ( id: string, definition: Function | Object ): Function | Object | void { if (!definition) { return this.options[type + "s"][id]; } else { 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; //把组件 指令 过滤器 放到Vue.options中 return definition; } }; }); } 复制代码
定义 Vue.component Vue.directive Vue.filter 三大 api 并且格式化用户传入内容 最后把结果放到 Vue.options 中
至此 Vue 的 全局 api 原理已经完结 基本上很多代码咱们之前 Vue 系列的原理都有写过 很多核心 api 大家在日常开发过程中使用的比较频繁 同时也是面试的常见考点 大家可以看着思维导图自己动手写一遍核心代码哈 遇到不懂或者有争议的地方欢迎评论留言
作者:Big shark@LX
链接:https://juejin.cn/post/6959016804349902884
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。