一 vue
vue的优点?
- 渐进式
- 组件化开发
- 虚拟dom
- 响应式数据
- 单页面路由
- 数据与试图分开
用户体检好,内容改变的时候不需要加载整个页面,避免不必要的重复渲染和跳转
基于上面一点 single 相对于服务器压力小
前后端职责分离,架构清晰
vue的缺点?
- 单页面不利于SEO
- 首屏加载时间长
- 不兼容IE
初始加载耗时, seo难度较大: 由于所有的内容都在一个页面中动态替换显示 所有 seo有着天然的劣势
为什么说Vue是一个渐进式框架?
Vue的全家桶,你可以选择不用,或者只选几样去用,比如不用vuex
根据场景,官方提供了方便的框架供你使用
Vue跟React的异同点?
相同点:
- 都是单向数据流
- 都使用了虚拟DOM的技术
- 都支持SSR
- 组件化开发
不同点:
- 前者template,后者JSX
- 数据改变,前者响应式,后者手动setState
- React单向绑定,Vue双向绑定
- React状态管理工具Redux、Mobx,Vue状态管理工具Vuex
MVVM是什么?和MVC有何区别呢?
MVC
- Model:负责从数据库中取数据
- View:负责展示数据
- Controller:用户交互的地方,例如点击事件
MVVM
- VM:就是View-Model,数据双向绑定
- Model:取数据的地方
- View:展示数据的地方
组件中的data是个函数并且返回一个对象呢?
组件是用来复用的, js 的对象是引用关系,如果组件中的data是一个对象,这样作用域没有隔离,子组件中的data属性值会互相影响
使用过哪些Vue的修饰符呢?
- .lazy:输入框失焦时才会更新v-model的值
- .trim:讲v-model绑定的值首位空格给去掉
- .number:将v-medol绑定的值转数字
- .stop:阻止事件冒泡
- .capture:事件的捕获
- .self:点击事件绑定本身才触发
- .once:事件只触发一次
- .prevent:阻止默认事件
- .native:绑定事件在自定义组件上时,确保能执行
- .left、.middle、.right:鼠标左中右键的触发
- passive:相当于给移动端滚动事件加一个.lazy
- camel:确保变量名会被识别成驼峰命名
- .sync:简化子修改父值的步骤
这样理解vue单向数据流?
父组件传输props给子组件时,子组件只能使用不能修改,这是为了组件之间更好的去解耦.比如有一个父组件,传props给10个子组件,如果某一个子组件修改了,那么会影响其它9个子组件跟着刷新,所以不推荐子组件修改props
组件之间的通信方式都有哪些,用过eventBus,eventbus的是想是什么
- 父组件传值给子组件数据props
- 子组件传值给父组件,通过$emit事件对父组件进行传值
- 父组件和之组件通过children进行获取实例数据
- 二次封装时经常使用listener进行传值
- 使用$refs 获取实例组件,进行数据获取
- 使用vuex 进行状态管理
- 使用eventBus 进行跨组件传值 进而传递数据(发布订阅模式)
- 使用provide 和inject官方不建议 一般用户UI组件开发
- 浏览器本地缓存 例如localStorage
- 路由传值
写个自定义 v-model?
v-model实际是:value + @input的语法糖
<input v-model="inputValue" />
<input
:value="inputValue"
@input="inputValue = $event.target.value"
/>
attrs 和 $listener 有了解吗?
常用于对组件进行二次封装时,比如A -> B -> C,B可以直接将爷爷组件的所有数据或者事件直接传给孙子
Vue 生命周期有哪些,都是做什么的,updated 什么情况下会触发,beforeCreate 的时候能拿到 Vue 实例么,组件销毁的时候调用的是哪个 API
- beforeVCreated:实例了Vue但是还没有进行数据初始化与响应式处理
- created 数据已经被初始化和响应式处理,这里可以访问和修改数据
- beforeMount render函数在这里调用,生成虚拟DOM,但是还没有转真实DOM并替代到el
- mounted 真实DOM挂载完毕
- beforeUpdated 数据更新后,新的虚拟DOM生成,但还没更旧虚拟DOM对比打补丁
- update 新旧虚拟DOM对比打补丁后,进行真实DOM的更新
- activated 被keep-alive 缓存的组件被激活时调用
- deactivated 被keep-alive缓存的组件停用时调用
- beforeDestroy 实例销毁之前调用,在这一步,依然可以访问数据
- destroyd 实例销毁后调用 vue实例的所有指令都被解绑,实例的监听器被移除,所有子实例也都被销毁
- errorCapured 捕获子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播。
什么情况下会触发组件销毁,销毁的时候会卸载自定义事件和原生事件么?
组件销毁时会自动卸载组件本身绑定的事件,但是仅限于组件本身。例如一些定时器、全局对象的事件绑定、eventBus则不会自动解绑,需要手动解绑。
自定义指令写过么,自定义指令都有哪些钩子?
vue2
- bind 指令绑定到指定元素时触发,只触发一次
- inserted 指令元素插入到DOM时触发,只触发一次
- update:VNode更新时触发,会触发多次
- unbind 指令解绑时触发,只触发一次
vue3
- created:指定元素的attribute或事件监听器被应用之前被调用
- beforeMount:指令绑定到指定元素上触发
- mounted:指定元素被挂载时触发
- beforeUpdate:在更新包含组件的VNode之前触发
- updated:在包含组件的VNode及其VNode更新后调用
- beforeUnMount:在卸载指定元素的父组件之前触发
- unmounted:指令解绑时触发
Vue2 的数据响应式有两个缺陷,你知道是哪两个缺陷么,为什么会有这样的缺陷,如何解决?
- 对象新增属性或修改新增的属性时,无法触发视图更新,需要使用 Vue.set,对象删除属性时需要使用Vue.delete才能触发更新
- 数组直接通过下标修改元素无法触发视图更新,需要使用数组的方法splice、push等等
vue 如何实现对数组的监听,为什么vue没有对数组下标修改做劫持?
vue2是通过重写了数组原型上的方法来达到对数组的修改的监听,vue2没有对数组下标做劫持,是出于心梗的考虑,因为通常数组元素都是非常多的,可能成百上千,如果每个元素都进行劫持,则非常耗费性能。
路由的几种模式?
- hash 哈希模式 根据hash值的更改进行组件切换,而不刷新页面
- history 历史模式 依赖于HTML5 的pushState和replaceState
- abstract:适用于Node
路由的钩子函数?
全局钩子
beforeEach:跳转路由前
- to:将要跳转进入的路由对象
- from:将要离开的路由对象
- next:执行跳转的方法
afterEach:路由跳转后
- to: 将要跳转进入的路由对象
路由独享钩子
routes: [
{
path: '/xxx',
component: xxx,
beforeEnter: (to, from, next) => {
}
}
]
组件内路由钩子
- beforeRouteEnter(to, from, next):跳转路由渲染组件时触发
- beforeRouteUpdate(to, from, next):跳转路由且组件被复用时触发
- beforeRouteLeave(to, from, next):跳转路由且离开组件时触发
使用过哪些Vue的内部指令呢?
- v-text:元素的textContent
- v-html:元素的innerHTML
- v-show:通过样式display改变显隐
- v-if:通过操作DOM改变显隐
- v-else:配合v-if
- v-else-id:配合v-else
- v-for:循环渲染
- v-on:绑定事件,缩写@
- v-bind:绑定变量,缩写:
- v-model:双向绑定
- v-slot:插槽
- v-once:只渲染一次
- v-pre:跳过元素编译
- v-cloak:隐藏双括号,有值再显示
v-if和v-show有何区别?
- v-if 通过操作dom来控制显隐,适用于偶尔显隐的情况
- v-show 通过改变样式display属性控制显隐,适用于频繁显隐的情况
computed和watch有何区别?
computed
计算属性将会混入到Vue的实例当中,所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例。
- 监控自定义变量,这个变量不可以和data 和props里面的变量重复;
- computed属性的属性值是函数默认走get方法(必须有返回值)属性都有一个get和set方法;
- 支持缓存,依赖多个变量计算出一个变量,只有所依赖的数据发生变化才会重新计算,否则会取缓存中的数据;
- 不支持异步 在computed里面操作异步无效;
watch
- 监听data、props里面数据的变化;
- 不支持缓存,每次都会重新计算;
- 支持异步,监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
一般情况下,computed的是 多对一 watch 是一对多
为什么v-if和v-for不建议用在同一个标签上?
v-for的优先级高于v-if每项都通过v-for渲染出来后再去通过v-if判断显隐,做了很多无用功
uex的有哪些属性?用处是什么?
- state 定义初始状态
- getter 从store取数据
- mutation 更改store中的状态 只能同步操作
- action 用于提交mutation,而不直接更改状态,可异步操作
-
module:store的模块拆分
不需要响应式的数据应该怎么处理?
- 定义在data的return之外
- 使用Object.freeze进行数据冻结
watch有哪些属性,分别有什么用?
- immediate: 初次加载时立即执行
- deep 是否进行深度监听
- handler 监听的回调函数
父子组件生命周期顺序?
1.父beforeCreate --> 2.父created --> 3.父beforeMount --->
4.子beforeCreate --> 5.子created ---> 6.子beforeMount ---->
7.子mounted ----> 8.父mounted
对象新属性无法更新视图,删除属性无法更新视图,为什么?怎么办?
- 原因:Object.defineProperty没有对对象的新属性进行劫持
- 解决:
1.新增属性:Vue.set(target, key, value)
2.删除属性:Vue.delete(target, key)
直接arr[index] = xxx无法更新视图怎么办?为什么?怎么办?
- 原因:Vue出于性能考虑没有对数组下标进行劫持,而是通过改写数组原型方法
- 解决:
- splice:arr.splice(index, 1, value)
- Vue.set(target, index, value)
nextTick的原理?
维护一个数组,每次调用时讲回调函数压入这个数组,然后优先选择微任务,在微任务回调中去执行数组中的所有回调,同时维护一个布尔值,确保每一次队列进行一次执行数组所有回调
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) {
callbacks.push(cb);
if (!pending) {
pending = true;
timerFunc();
}
}
审查元素时发现data-v-xxxxx,这是啥?
样式模块化scoped的效果,在本组件的标签都会带上data-v-xxx的属性,然后通过属性选择器实现样式模块化的效果
provide 和inject是响应式的吗?
引用数据类型是响应式,基础数据类型不响应式
自定义v-model
export default {
model: {
event: 'change',
prop: 'checked'
}
}
为什么不建议用index做key,为什么不建议用随机数做key?
举个例子:
<div v-for="(item, index) in list" :key="index">{{item.name}}</div>
list: [
{ name: '小明', id: '123' },
{ name: '小红', id: '124' },
{ name: '小花', id: '125' }
]
渲染为
<div key="0">小明</div>
<div key="1">小红</div>
<div key="2">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' })
渲染为
<div key="0">小林</div>
<div key="1">小明</div>
<div key="2">小红</div>
<div key="3">小花</div>
新旧对比
<div key="0">小明</div> <div key="0">小林</div>
<div key="1">小红</div> <div key="1">小明</div>
<div key="2">小花</div> <div key="2">小红</div>
<div key="3">小花</div>
可以看出,如果用index做key的话,其实是更新了原有的三项,并新增了小花,虽然达到了渲染目的,但是损耗性能
现在我们使用id来做key,渲染为
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' }),渲染为
<div key="122">小林</div>
<div key="123">小明</div>
<div key="124">小红</div>
<div key="125">小花</div>
新旧对比
<div key="122">小林</div>
<div key="123">小明</div> <div key="123">小明</div>
<div key="124">小红</div> <div key="124">小红</div>
<div key="125">小花</div> <div key="125">小花</div>
可以看出,原有的三项都不变,只是新增了小林这个人,这才是最理想的结果
插槽的使用以及原理?
普通插槽 普通插槽slot会被当做子元素进行解析,最终会被解析成一个_t函数,他接收的第一个参数为插槽的名称,默认是default,也就是_t('default'),执行此函数进行最终元素的渲染,如果是具名插槽,则传对应的插槽名 作用域插槽 插槽会被封装成一个函数放置在scopeSlotes对象中,解析时_t第二个参数接收子组件的数据,并进行渲染
说说nextTick的用处?
修改数据时不能马上得到最新的DOM信息,所以需要使用nextTick,在nectTick回调中可以获取最新DOM信息
vue的hook的使用?
同一组件中使用
这是我们常用的使用定时器的方式
export default{
data(){
timer:null
},
mounted(){
this.timer = setInterval(()=>{
//具体执行内容
console.log('1');
},1000);
}
beforeDestory(){
clearInterval(this.timer);
this.timer = null;
}
}
上面做法不好的地方在于:得全局多定义一个timer变量,可以使用hook这么做:
export default{
methods:{
fn(){
let timer = setInterval(()=>{
//具体执行代码
console.log('1');
},1000);
this.$once('hook:beforeDestroy',()=>{
clearInterval(timer);
timer = null;
})
}
}
}
父子组件使用
如果子组件需要在mounted时触发父组件的某一个函数,平时都会这么写:
//父组件
<rl-child @childMounted="childMountedHandle"
/>
method () {
childMountedHandle() {
// do something...
}
},
// 子组件
mounted () {
this.$emit('childMounted')
},
使用hook的话可以更方便:
//父组件
<rl-child @hook:mounted="childMountedHandle"
/>
method () {
childMountedHandle() {
// do something...
}
},
三 ES6
Symbol 有了解吗,迭代器有了解吗,哪些是可迭代的?
是ES6的特性,具体使用场景有:
symbol
- 充当对象的属性名,实现私有属性
- 充当变量,实现单独变量
- 用来定义类里的私有属性
迭代
迭代器:Iterator,可迭代对象有Array、Set、Map,想将不可迭代对象变成可迭代对象,可以设置Symbol.iterator属性
const t = {
name: '林三心',
age: 12
}
t[Symbol.iterator] = function () {
let index = 0,
self = this,
keys = Object.keys(this)
return {
next() {
if (index < keys.length) {
return {
value: self[keys[index++]],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
for (let value of t) {
console.log(value)
}
用Set获取两个数组的交集,如何做?
合集
const heji = (arr1, arr2) => {
return [...new Set(arr1.concat(arr2))]
}
交集
const jiaoji = (arr1, arr2) => {
const temp = new Set(arr1)
return Array.from(new Set(arr2)).filter(item => {
return temp.has(item)
})
}
差集
const chaji = (arr1, arr2) => {
const temp1 = new Set(arr1)
const temp2 = new Set(arr2)
const res = []
for (let item of temp1) {
!temp2.has(item) && res.push(item)
}
return res
}
实现 Promise.all?
Promise.sx_all = (promises) => {
return new Promise((resolve, reject) => {
const result = []
let count = 0
for (let i = 0; i < promises.length; i++) {
const promise = Promise.resolve(promises[i])
promise.then(res => {
result[i] = res
count++
if (count === promises.length) {
resolve(result)
}
}).catch(err => {
reject(err)
})
}
})
}
animation 和 transition 的区别?
- animation需配合@keyframe,而transition不需要
- animation可以出发多次,transition只能触发一次
- animation可以设置多个帧,而transition只有两帧
- 前者可能会引起多次重回回流,后者会比较少