为啥data是一个函数
各个组件实例维护各自的数据,父子组件中的data中的响应式数据作用域独立,互不污染
Vue.use实现逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export function initUse (Vue: GlobalAPI) { 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) 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.minxin
通过 Vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| export default function initMixin(Vue){ Vue.mixin = function (mixin) { this.options=mergeOptions(this.options,mixin) }; } };
export const LIFECYCLE_HOOKS = [ "beforeCreate", "created", "beforeMount", "mounted", "beforeUpdate", "updated", "beforeDestroy", "destroyed", ];
const strats = {};
export function mergeOptions(parent, child) { const options = {}; for (let k in parent) { mergeFiled(k); } for (let k in child) { if (!parent.hasOwnProperty(k)) { mergeFiled(k); } }
function mergeFiled(k) { if (strats[k]) { options[k] = strats[k](parent[k], child[k]); } else { options[k] = child[k] ? child[k] : parent[k]; } } return options; }
|
Vue.extend原理
Vue.extend: {Object} options
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象,
实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
data 选项是特例,需要注意在 Vue.extend() 中它必须是函数
创建一个Sub类,这个类通过寄生组合继承方式继承父类Super,主要是用来实现一个编程式组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div id="mount-point"></div> // 创建构造器 var Profile = Vue.extend({ template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>', data: function () { return { firstName: 'Walter', lastName: 'White', alias: 'Heisenberg' } } }) // 创建 Profile 实例,并挂载到一个元素上。 new Profile().$mount('#mount-point')
|
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function initExtend (Vue) { Vue.cid = 0; var cid = 1; 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; 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; if (Sub.options.props) { initProps$1(Sub); } if (Sub.options.computed) { initComputed$1(Sub); } Sub.extend = Super.extend; Sub.mixin = Super.mixin; Sub.use = Super.use; Sub.superOptions = Super.options; Sub.extendOptions = extendOptions; Sub.sealedOptions = extend({}, Sub.options); cachedCtors[SuperId] = Sub; return Sub }; }
|
生命周期钩子是如何实现的
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好,收集管理好(内部采用数组的方式存储)
然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export function callHook(vm, hook) { const handlers = vm.$options[hook]; if (handlers) { for (let i = 0; i < handlers.length; i++) { handlers[i].call(vm); } } }
Vue.prototype._init = function (options) { const vm = this; vm.$options = mergeOptions(vm.constructor.options, options); callHook(vm, "beforeCreate"); initState(vm); callHook(vm, "created"); if (vm.$options.el) { vm.$mount(vm.$options.el); } };
|
Vue.$set实现逻辑
了解 Vue 响应式原理的同学都知道在两种情况下修改数据 Vue 是不会触发视图更新的
1.在实例创建之后添加新的属性到实例上(给响应式对象新增属性)
2.直接更改数组下标来修改数组的值
Vue.set 或者说是$set 原理如下
因为响应式数据 我们给对象和数组本身都增加了ob属性,代表的是 Observer 实例。当给对象新增不存在的属性 首先会把新的属性进行响应式跟踪 然后会触发对象ob的 dep 收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组,如果不是数组的话,对象本身的属性的话直接添加,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| export function set(target: Array | Object, key: any, val: any): any { 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(ob.value, key, val); ob.dep.notify(); return val; }
|
v-for为啥优先级比v-if高,vue3.0中哪个优先级高
vue2.0中v-for比v-if优先级高,因为解析时先解析 v-for 再解析 v-if。如果遇到需要同时使用时可以考虑写成计算属性的方式。
在 vue 2.x 中,在一个元素上同时使用 v-if 和 v-for 时, v-for 会优先作用。
在 vue 3.x 中, v-if 总是优先于 v-for 生效。
vue自定义指令以及相关原理
- vue-directive
五个生命周期说下
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
- bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
- componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
- unbind:只调用一次,指令与元素解绑时调用。
自定义指令原理
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
vueRouter相关
导航守卫
“导航”表示路由正在发生改变。
正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:
全局的,
单个路由独享的,
组件级的
全局前置
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。
每个守卫方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
1 2 3 4 5
| const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => { })
|
全局解析守卫
在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
全局后置钩子
1 2 3
| router.afterEach((to, from) => { })
|
路由独享的守卫
你可以在路由配置上直接定义 beforeEnter 守卫:
1 2 3 4 5 6 7 8 9 10 11
| const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { } } ] })
|
组件内的守卫
在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const Foo = { template: `...`, beforeRouteEnter(to, from, next) { }, beforeRouteUpdate(to, from, next) { }, beforeRouteLeave(to, from, next) { } }
|
vue-router 路由钩子函数是什么 执行顺序是什么
路由钩子的执行流程, 钩子函数种类有:全局守卫、路由独享守卫、组件守卫
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局前置的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter(路由独享)
- 解析异步路由组件
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+ )。
- 导航被确认。
- 调用全局后置的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
vuex相关
请你设计一个vuex库,数据状态管理器
参考