一、vue
1.1 vue基本使用
- vue组件如何进行通信?
父->子组件:子组件通过props接收父组件传递的值;父组件设置refs,子组件通过this.$refs.xx访问父组件实例; 子->父组件:父级自定义事件,子级通过emit触发事件并且传递参数;
// child
this.$emit('add', good)
// parent
<Cart @add="cartAdd($event)"></Cart>
兄弟组件:通过共同的祖辈组件搭桥,$parent或$root。
// brother1
this.$parent.$on('foo', handle)
// brother2
this.$parent.$emit('foo')
祖先和后代之间:provide/inject:能够实现祖先给后代传值; 任意两个组件:vuex通过store管理数据并且通知组件状态变更、创建一个事件总线event-bus管理事件派发、监听和回调处理。将event-bus绑定在vue的原型上。
// Bus:事件派发、监听和回调管理
class Bus{
constructor(){
// {
// eventName1:[fn1,fn2],
// eventName2:[fn3,fn4],
// }
this.callbacks = {}
}
$on(name, fn){
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emit(name, args){
if(this.callbacks[name]){
this.callbacks[name].forEach(cb => cb(args))
}
}
}
// main.js
Vue.prototype.$bus = new Bus()
// child1
this.$bus.$on('foo', handle)
// child2
this.$bus.$emit('foo')
- 生命周期有哪些?父子组件生命周期顺序?
生命周期如下:- beforeCreate:实例创建前调用;
- created:实例创建后,数据初始化最好在这个阶段完成;
- beforeMount:实例挂载前;
- mounted:实例挂载完成,vm.$el可以调用,不能保证所有子组件被挂载,如果要等视图全部更新完毕可用vm.$nextTick();
- beforeUpdate:数据更新前;updated:数据更新完成;
- updated:数据更新完成。
- beforeDestroy:实例销毁之前调用,此时实例仍然可用;
- destroyed:实例销毁之后调用,此时实例的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
父子组件生命周期顺序:
加载渲染过程:父beforeCreated->父created->父bedoreMount->子beforeCreated-> created->子bedoreMount->子mounted->父mounted; 子组件更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated; 销毁过程:父beforeDestroy->子beforeDestroy->子destroyed->父destroyed。
- computed和watch
缓存 set 深度监听 引用类型 oldval - 如何自己实现v-model?在组件上使用v-model?
<input :value="val" @input="val=$event.target.value">
- vue常用的修饰符有哪些?
- 事件修饰符:.stop:阻止冒泡;.prevent:阻止默认事件;.capture:阻止事件捕获;.self:只在event.target是自身时才触发函数;.once:只触发一次函数;.native:监听根元素的原生事件。
<base-input v-on:focus.native="onFocus"></base-input>- 按键修饰符:v-on:keyup.enter、v-on:keyup.page-down等(keycode或者别名都可以)。
- v-bind 指令常用修饰符:假如父组件传给子组件的值,子组件接受之后,想要改变父组件传过来的值,就可以使用sync。.sync是vue中用于实现简单的“双向绑定”的语法糖,在平时的开发中是非常使用的。vue的prop是单向下行绑定:父级的prop的更新会向下流动到子组件中,但是反过来不行。可是有些情况,我们需要对prop进行“双向绑定”。这个时候,就可以用.sync来解决
//父组件 <vue-statisTotal :list.title="doc.title"> </vue-statisTotal> //等同于 <vue-statisTotal @update:title.sync="doc.title = $event"> </vue-statisTotal> //子组件触发 this.$emit('update:title', newTitle)- v-model 指令常用修饰符: .lazy:- 取代 input 监听 change 事件; .number:- 输入字符串转为数字; .trim:- 输入首尾空格过滤;
- $nextTick的原理是什么,使用场景是什么?
vue采用异步渲染,简单概括,$nextTick的回调函数会被添加到任务队列中,逐个判断promise.then => MutationObserver => setImmediate => setTimeout是否兼容。确保异步事件在DOM更新循环(同步事件)完成后执行延迟回调。 在 created 和 mounted 阶段,如果需要操作渲染后的视图,也要使用 nextTick 方法。 - slot是什么?
父组件向子组件插入内容,常用的有具名插槽和作用域插槽。
#具名插槽
<!--子组件:header-top-->
<slot name="header"></slot>
<!--父组件-->
<header-top>
<template v-slot:header>
<div>header content</div>
</template>
</header-top>
#作用域插槽
<!--子组件传递插槽属性user-->
<slot name="header" :user="user"></slot>
<!--父组件-->
<header-top>
<template v-slot:header="slotProps">
<div>header content</div>
</template>
</header-top>
- vue动态组件是什么?
,修改currentTabComponent的值,即可切换到对应名字的组件,结合keep-alive使用,可以实现切换tab的功能。 - 如何异步加载组件? import函数,按需加载,异步加载大组件
components:{
MyComponent:()=>{import ('./MyComponent.vue')}
}
- vue如何缓存组件?
利用keep-alive缓存组件,频繁切换,不需要重新渲染,vue常见性能优化。 - 如何抽离公共逻辑?
利用mixin,多个组件有共同的逻辑,抽离出来。数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
#全局混入
Vue.mixin({
created: function () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
#组件混入
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定义一个使用混入对象的组件
var Component = Vue.extend({
mixins: [myMixin]
})
1.2 vue原理
-
如何理解MVVM?
MVVM指的是数据驱动视图,M代表model、v代表view,VM指的是viewmodel,viewmodel利用双向数据绑定将将视图层和数据连接起来,view变化之后,viewmodel会通知model更新,而Model 数据的变化也会立即反应到View 上。 -
什么是双向绑定,原理是什么? 双向绑定指的是模型(model)和视图(view)的双向绑定;其原理采用的是数据劫持结合发布者-订阅者模式,通过observer(数据监听器)劫持监听data的属性,读取数据时会触发getter,修改数据时触发setter;每个属性对应一个new Dep(),Dep负责收集订阅者和通知订阅者更新。组件挂载过程中都会new watcher(订阅者),watcher相当于observer和compile的桥梁,watcher的第二个参数updateComponent的作用是更新和渲染节点,因此会去读取data,触发getter,调用dep收集依赖。当数据更新时,就会触发setter,调用dep.notify(),通知watcher执行update对视图进行更新。
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
}
}
- 如何监听数组的变化?
将改变数组的七个方法重写在数组原型上,当数组调用这些方法时,相当于会触发mutation方法,在mutator方法中,调用数组的原始方法,然后通知dep更新。(详情见vue源码解析二) - 什么是虚拟DOM?
虚拟DOM是对真实DOM的一种抽象描述,简单来说,可以把Virtual DOM 理解为一个简单的JS对象,并且最少包含标签名( tag)、属性(attrs)和子元素对象( children)三个属性。通过状态生成虚拟节点树,然后将虚拟节点树渲染成真正的DOM,在渲染之前,会使用新生成的虚拟节点树和上一次虚拟节点树进行对比,只渲染不同的部分。 - Vue中如何实现一个虚拟DOM?
首先构建一个VNode的类,DOM元素上的所有属性在VNode实例对象上存在对应的属性。通过编译将模板转换为渲染函数render,执行render函数,创建vnode节点树。通过patch将新旧节点对比,最后生成真正的DOM。 - 你了解Vue的diff算法吗?
执行patch函数,首先判断新老节点是否存在,都存在则进行类型比较。diff运算的核心为updatechildren。 updatechildren会将新旧vnode的首尾节点进行两两交叉对比,类型相同则执行patchNode,如果是首尾对比,需要移动真实DOM节点的位置。接下来指针向中间移动,重复对比。
如果首尾交叉对比,没有找到类型相同的节点,则在oldvnode中的其他节点找与newStartVnode,满足同类型则patchNode,并将对应DOM移到oldStartVnode的前面。也有可能找不到满足sameNode的节点,那么会调用createElm创建新的DOM节点。
当结束时oldStartIdx > oldEndIdx,说明还剩下新的vnode节点,需要将对应的DOM插入。当结束时newStartIdx > newEndIdx时,则删除多余的老节点。 - 说说你对Vue的template编译的理解?
通过parse将template模板字符串转换成AST(抽象语法树),optimize深度遍历AST树,去检测是否为静态节点,如果是静态节点则它们生成 DOM 永远不需要改变。AST通过generate得到render函数,render返回VNode。 - Vue实例挂载的过程是什么?
- 首先,mount对el做了限制,Vue 不能挂载在 body、html 这样的根节点上。
- 判断options选项中是否有render函数这个属性,有则直接调用原始的\mount方法。如果没有,则判断template是否存在,转换成render,并赋值给options。最后调用原始的$mount方法。
- 在原始的mount方法中,触发beforeMount钩子,并实例化一个watcher,在第二个参数updateComponent这个函数中调用vm._updated。该函数是首次渲染和更新渲染作用,参数为render函数(vnode),如果vm._vnode不存在则进行首次渲染。同时vnode中被劫持的数据自动收集依赖。当vnode中被劫持的数据变化时候触发对应的依赖,从而触发vm._update进行更新渲染。
- 最后触发mounted钩子函数。
1.3 高级
- 使用Vue后怎么针对搜索引擎做SEO优化?
- SSR解决了什么问题?有做过SSR吗?你是怎么做的?
1.4 axios
- axios是什么?怎样使用它?怎么解决跨域的问题?
axios 是一个基于 promise的HTTP库,先封装在使用。使用proxyTable配置解决跨域问题。
1.5 vue-router
- 怎样实现路由权限控制?
router.addRoutes(routes: Array),常用于权限控制,例如会员访问的菜单区分。 关键点:接口传过来的component为字符,并不是对象,需要映射为对象。
api.getRoutes.then(res=>{
const routesConfig = res.routes.map(route=>{
mapComponent(route);
})
this.$router.addRoutes(routesConfig)
})
let mapCom = function(com){
return {
com:()=>import(`@/view/${com}`);
}
}
let mapComponent = function(route){
const route.component = mapCom(route.component);
//注意判断嵌套的子路由
if(route.children){
route.children = route.children.map(child=>{
mapComponent(child);
})
}
}