首先请求服务器,获取当前用户的权限数据,比如请求 this.$http.get("rights/list");
获取到权限数据之后,在列表中使用v-if v-if-else的组合来展示不同的内容
首页
权限管理
权限列表
一级
二级
三级
属于发布订阅模式,在vue中使用observer和definereactive两个方法的结合对数据进行递归劫持,然后通过watch这个类来对属性进行订阅,Dep类用于解耦合,当数据变更的时候先触发数据的set方法,然后调用Dep.notiify通知视图更新
vue性能瓶颈的几种情况
一次渲染大量的数据的时候,存在大量数据并且都是复杂类型的时候,会导致vue对数据的劫持时间和渲染时间变长, js 连续执行时间过长,会导致页面长时间无法交互,而且渲染时间太慢,用户一次交互反馈的时间过长。
优化方案:可以使用
requestAnimation
这个方法,将数据进行分割,分批次渲染,减少了 js 的连续运行时间,并且加快了渲染时间,利用加长总运行时间换取了渲染时间,用户既能快速得到反馈,而且不会因为过长时间的 js 运行而无法与页面交互。
当页面中存在大量数据,只是修改了一小部分导致页面也会导致页面卡顿,因为vue的更新以组件为粒度进行更新的,只要修改了当前组件中所使用的数据,组件就会整个去进行更新,造成大量的时间浪费
优化方案:将不同的模块划分成不同的组件,这样有效降低虚拟dom的diff运算时间过长的问题,比如将大量数据的模块单独放一个组件,其它放一个组件,由于vue是以组件为粒度更新,修改其它组件的情况下不会导致table的重新diff,提升页面响应速度高达几百倍
动态插槽作用域或者静态插槽的更新
使用插槽作用域来替换这两种操作方式,一样能提升性能,因为使用
插槽作用域
之后,插槽内容会被封装到一个函数中,被子组件渲染,而不是在父组件
如何获取dom?在Vue中提供了一种特别的方式来获取dom,即给dom加上个ref属性,那么就可以通过this.$refs.名字来获取到该dom元素。
如何操作dom、更新dom?通过refs.名字就可以拿到对应的真实dom,然后就可以用原生JS进行操作和更新。当然vue框架本身就是不需要dom操作的,通过修改相应的数据并再配合指令、模板语法就可以轻松的操作和更新dom。
在Vue2.x中,双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变。核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法。
Vue3.x则是用ES6的语法Proxy对象来实现的。
Object.defineProperty()的缺点:
只能监听对象(Object),不能监听数组的变化,无法触发push, pop, shift, unshift,splice, sort, reverse。
必须遍历对象的每个属性
只能劫持当前对象属性,如果想深度劫持,必须深层遍历嵌套的对象。
Proxy的优点:
Proxy 可以直接监听对象而非属性。
Proxy 可以直接监听数组的变化。
Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的。
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改。
Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。
let arr = [];
let proxy = new Proxy(arr, {
get: function(obj, prop){
return obj[prop];
},
set: function(obj, prop, value){
obj[prop] = value; //可以被监听到变化
return true;
}
});
setTimeout(()=>{
proxy.push(1);
}, 2000)
MVVM是Model-View-ViewModel的简写。它本质上就是MVC(Model-View-Controller)的改进版。在开发过程中,由于需求的变更或添加,项目的复杂度越来越高,代码量越来越大,此时我们会发现MVC维护起来有些吃力,尤其Controller控制层非常的厚重,非常的庞大,难以维护。
所以有人想到把Controller的数据和逻辑处理部分从中抽离出来,用一个专门的对象去管理,这个对象就是ViewModel。ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。
由于实现了双向绑定,ViewModel 的内容会实时展现在 View 层,这是激动人心的,因为前端开发者再也不必低效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要处理和维护 ViewModel,更新数据视图就会自动得到相应更新,真正实现数据驱动开发。
在前后端完全分离的情况下,Vue项目中实现token验证大致思路如下:
1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码
2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token
3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面
4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面
5、每次调后端接口,都要在请求头中加token
6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401
7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面
当你设置 vm.message = 'new message',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。例如:
{{message}}
var vm = new Vue({
el: '#example',
data: {
message: 'old message'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent // 'old message'
Vue.nextTick(function () {
vm.$el.textContent // 'new message'
})
首先Vue 在更新 DOM 时是异步执行的,也就是说数据变了,DOM不会立即改变,那么我们是如何知道DOM什么时候会改变呢?也就是说如何知道异步后的触发时机呢?
可以通过nextTick方法,这个方法在源码内,先监听是否具备Promise.then,利用promise来监听,如果当前环境不支持promise,那么就降级采用MutationObserver,如果MutationObserver不支持的话,那么就降级采用setImmediate,如果setImmediate不支持的话,那么就使用setTimeout(fn, 0)。
所以说nextTick和setTimeout区别总结就是:nextTick会先尝试使用promise、MutationObserver、setImmediate这些技术去监听,如果都不支持才会采用setTimeout
起初我们在使用JS/JQuery时,不可避免的会大量操作DOM,而DOM的变化又会引发回流或重绘,从而降低页面渲染性能。那么怎样来减少对DOM的操作呢?此时虚拟DOM应用而生,所以虚拟DOM出现的主要目的就是为了减少频繁操作DOM而引起回流重绘所引发的性能问题的!
虚拟DOM(Virtual Dom),起始本质上就是一个JS对象,当数据发生变化时,我们不直接操作真实DOM,因为很昂贵,我们去操作这个JS对象,就不会触发大量回流重绘操作,再加上diff算法,可以找到两次虚拟DOM之间改变的部分,从而最小量的去一次性更新真实DOM,而不是频繁操作DOM,性能得到了大大的提升。
虚拟DOM还有一个好处,可以渲染到 DOM 以外的平台,实现 SSR、同构渲染这些高级特性,Weex 等框架应用的就是这一特性。
父向子组件传值,可以利用prop方式。
子向父组件传值,可以利用自定义事件$emit方式。
多层级组件传值,可以使用provide/inject
无关系的组件传值,利用vuex状态管理
父 -> 子: 通过 Prop 向子组件传递数据,子组件通过props属性来接收。
Vue.component('blog-post', {
props: ['title'],
template: '{{ title }}
' //获取父组件的值
})
子 -> 父: 父组件自定义事件,子组件利用$emit来完成。
Vue.component('blog-post', {
props: ['title'],
template: '{{ title }}
'
})
这类问题 首先分类 表明了解的比较多 具体就没说完 或者漏了 面试官也不会计较很多
组件通信的四大类 父与子 子与父 子与子 跨层级
在细说各种方式 加入自己的理解
1、props和$emit
父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件
2、$attrs和$listeners
3、中央事件总线 bus
上面两种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系呢?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。
4、provide和inject
父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。
5、v-model
父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值
6、$parent和$children
7、boradcast和dispatch
8、vuex处理组件之间的数据交互 如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。
关于这个可以的key的作用 首先表明 key 不是一定要有的 不写可以代码也可以跑 但是建议加上
然后指出可以用的地方 key在v-for循环可以用用 在表单元素中也可以用key 减少缓存
一般说key 只要说配合v-for的使用
key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速
diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异能讲清楚diff算法就继续讲
diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.
准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug. 快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复杂度仅仅为O(1)
讲完以后 还要补充一点自己的看法
建议使用主键比如id
Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实的DOM上
下面就是一个真实DOM映射到虚拟DOM的例子:
实现虚拟DOM的过程
这种问题 是开放的 多说就是都是对的 可以讲差异 也可以讲新增 的知识点
比如说 常用的api特别好用
ref、toRefs、toRef、isRef
ref 用于定义响应式变量、快捷DOM访问。
基本语法:const a = ref(1) // {value:1}
基础使用: 一般用于定义 String、Number、Boolean 这种基于数据类型,外在表现统一使用 .value 访问。
补充:ref 还可以访问 DOM对象或者组件实例对象,可做DOM操作。
toRef、toRefs 用于把一个object的变量,变成响应式的变量。
基本语法:const msg = toRef(obj, key)
// 把obj[key]变成响应式的
基本语法:const { msg } = toRefs(obj)
// 把整个obj都变成响应式的
unref 返回一个变量的值
基本语法:const x = unref(x)
// 如果x是ref变量则返回x.value,如果x不是ref变量则直接返回x。
isRef 用于判断一个变量是不是ref响应式变量
基本语法:const bol = isRef(x)
function useUpdBoxStyle() {
const el = ref(null)
const updateStyle = color => {
el.value.style.color = color
}
return [el, updateStyle]
}
shallowRef、triggerRef
shallowRef 用于性能优化,只对对象的第一层进行proxy
基本语法:const obj = shallowRef({a:1,b:{c:{d:{e:2}}}})
triggerRef 用于手动触发那些shallowRef的变量进行更新视图
基本语法:triggerRef(obj)
// 当obj.value.b.c.d发生变化,triggerRef(obj) 强制更新视图。
customRef 自定义ref,把ref变量拆成get/set的写法
基本语法:customRef((track, trigger) =>({get,set})
function useObj() {
const obj = { a: 1, b: { c: { d: 2 }}}
const obj1 = ref(obj)
const obj2 = shallowRef(obj)
// console.log('obj1', obj1)
// console.log('obj2', obj2)
const changeObj = (obj, newD) => {
// obj1.value.b.c.d = 100
obj.value.b.c.d = newD
triggerRef(obj)
}
return [[obj1, obj2], changeObj]
reactive、readonly
reactive 用于定义响应式变量(引用数据类型)
基本语法:const arr = reactive([]) // {value: []}
ref和reactive是什么关系呢?ref背后是使用reactive来实现的。
shallowReactive 用于性能优化,只对对象的第一层进行proxy
基本语法:const c = shallowReactive({a:{b:{c:100}}})
// 只对这个对象的第一层进行proxy
readonly 把响应式变量变成“只读的”,如果修改就报警告。
基本语法:const user = readonly({name:1,age:2})
isReadonly 用于判断一个变量是否是readonly的,返回布尔值
基本语法:const bol = isReadonly(x)
isProxy 用于判断一个变量是否是响应式的,返回布尔值
基本语法:const bol = isProxy(x)
isReactive 用于判断一个变量是否是reactive响应式变量,返回布尔值
基本语法:const bol = isReactive(x)
function useUser() {
const user = readonly(reactive({name:'list',age:30}))
console.log('user', user)
// setTimeout(()=>user.age=40, 2000)
const x = 1
const y = readonly({a:1,b:{c:3}})
console.log('是否被proxy拦截过', isProxy(user), isProxy(x), isProxy(y.b))
return user
}
toRaw、markRaw
toRaw 用于返回一个响应式变量的原始值
基本语法:const a3 = toRow(reactive(a1))
// a1===a3是true
markRaw 用于把一个普通变量标记成“不可proxy”的
基本语法:const b2 = markRaw(b1)
// b2是无法被reactive的
function useRaw() {
const a1 = { title: 100 }
const a2 = reactive(a1)
const a3 = toRaw(a2)
console.log('toRow(a2)===a1', a3===a1)
console.log('a2===a1', a2===a1)
return [a1,a2,a3]
}
computed、watch、watchEffect
computed 用于对响应式变量进行二次计算,当它依赖的响应式变量发生变化时会重新计算
基本语法:const c = computed(()=>c1.value*c2.value)
// 只读
基本语法:const c = computed({get:()=>c1.value*c2.value,set:(newVal)=>c1.value=newVal})
// 可写可读
watch 用于监听响应式变量的变化,组件初始化它不执行
基本语法:const stop = watch(x, (new,old)=>{})
// 调用stop()可以停止监听
基本语法:const stop = watch([x,y], ([newX,newY],[oldX,oldY])=>{})
watchEffect 用于监听响应式变量的变化,组件初始化会执行
基本语法:const stop = watchEffect(()=>ajax({cate,page,size}))
export default function useWatchComputed() {
const c1 = ref(10)
const c2 = ref(20)
const c3 = computed(()=>c1.value*c2.value) // 只读
// 可读也可写
const c4 = computed({
get: ()=>c1.value*c2.value,
set: (newVal)=>{
c1.value = parseInt(newVal) / c2.value
}
})
const stop1 = watch(c4, (newC4, oldC4)=>console.log('c4变了', newC4, oldC4))
const stop2 = watch([c1,c2], ([newC1,newC2],[oldC1,oldC2])=>{
console.log('[c1,c2] 新值:', [newC1, newC2])
console.log('[c1,c2] 旧值:', [oldC1, oldC2])
})
const stop3 = watchEffect(()=>{console.log('watch effect', c1.value, c2.value)})
const update = (c,v) => c.value = v
return [[c1,c2,c3,c4],[stop1,stop2,stop3,update]]
}
2:也可以说亮点
性能比vue2.x快1.2~2倍
支持tree-shaking,按需编译,体积比vue2.x更小
支持组合API
更好的支持TS
更先进的组
3.更可以说性能
1.diff算法更快
vue2.0是需要全局去比较每个节点的,若发现有节点发生变化后,就去更新该节点
vue3.0是在创建虚拟dom中,会根据DOM的的内容会不会发生内容变化,添加静态标记, 谁有flag!比较谁。
2、静态提升
vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染 vue3中对于不参与更新的元素,会做静态提升,只被创建一次,在渲染时直接复用即可
3、事件侦听缓存
默认情况下,onclick为动态绑定,所以每次都会追踪它的变化,但是因为是同一函数,没有必要追踪变化,直接缓存复用即可
在之前会添加静态标记8 会把点击事件当做动态属性 会进行diff算法比较, 但是在事件监听缓存之后就没有静态标记了,就会进行缓存复用
这种原理性问题 不要直接说不清楚 不了解
先讲下使用
v-model本质上是一个语法糖,可以看成是value + input 方法的语法糖。可以通过model的prop属性和event事件来进行自定义。
2、v-model是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性, 也会在更新data
绑定属性时候,更新页面上输入控件的值。
然后再来讲细节
vue的双向绑定是由数据劫持结合发布者-订阅者模式实现的,那么什么是数据劫持?vue是如何进行数据劫持的?说白了就是通过Object.defineProperty()来劫持对象属性的setter和getter操作,在数据变动时做你想要做的事情
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发生变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)
3:最后补一下 vue2.0里面用Object.defineProperty 3.0里面用new Proxy 一个监听每个属性 一个监听整个对象
像这种问题其实问的不是特别详情 面试者可能不懂题目的意思 但是我们要学会揣摩 面试官的问题
如果不知道 你就直说vue的组件通信 在讲iframe的页面获取v
vue组件内嵌一个iframe,现在想要在iframe内获取父组件内信息,采用的是H5新特性PostMessage来解决跨域问题
采用postMessage内涵两个API:
onMessage:消息监听
postMessage:消息发送
代码和例子
clearMap(){
let map = document.getElementsByName("map")[0].contentWindow
map.postMessage("clearMap","*")
}
iframe内:
window.addEventListener('message', function (evt) {
if (evt.data == 'clearMap'){
clearMap()
}
//event.data获取传过来的数据
});
这种问题一样的 先回答经常用的一些指定 比如 v-for v-if v-model v-show等等之类的 指令分为全局和局部的
然后在回答自定义指令
通过directive来自定义指令,自定义指令分为全局指令和局部指令,自定义指令也有几个的钩子函数,常用的有bind和update,当 bind 和 update 时触发相同行为,而不关心其它的钩子时可以简写。一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右。
这个问题很有意思 因为平时我们一般问题异步和同步指的是 数据请求 同步和异步问题
这里加上了组件 还有修改data 这里给大家写个例子
{{num}}
以此可以说明
数据更新是同步的 但是视图更新是异步的
解决这个问题需要使用 $nextTick 解决视图异步更新的问题
首先看到 .sync 我们需要知道这是个修饰器 类似修饰器还有 .stop .prevent 之类
其实这个修饰符就是vue封装了 子组件要修改父组件传过来的动态值的语法糖,省去了父组件需要写的方法,但是子组件emit时要加上update
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源
代码解释
// 这里父组件,要给子组件传一个title的值
// 子组件
{{ title }}
这里关键就是emit里的参数要写成'update'+ ':' +'要修改的props'
以前是用的this.$emit("自定义方法")
这个问题其实也是属于组件通信 常见的组件通信有 父传子 子传父 子传子 以及跨层级
这个多组件嵌套通信其实就是跨层级的另一种问法
多组件通信
方法一:props 一层 一层的传递
方法二:依赖注入 provide 声明 inject接收
方法三:利用公共的bus = new Vue() bus.$on 声明 bus.$emit() 调用
方法四:使用vuex 全局的状态管理
当前组件