1,Object.defineProperty和Proxy区别 定义、优缺点、区别
Object.defineProperty:vue2中实现数据监听/响应式的核心API。
Object.defineProperty遍历监听(observer)vue对象中的所有属性:data,props,computed。给每个属性增加getter和setter操作,当属性发生变化时更新视图。
缺点:
1,只对对象进行劫持,递归到底,一次性计算量大。
2,无法监听数组。vue重写覆盖数组原生方法。
3,无法监听新增/删除属性,要调用vue.set() 、 vue.delete()方法。
proxy:vue3中实现数据监听/响应式的核心API。
1,对目标对象操作之前设置了拦截,通过操作对象的代理来间接操作对象。
2,可监听数组变化
3,可监听新增/删除属性。
优点:
proxy深度监听性能更好:只在get时才进行下一级监听(意思是获取到哪一层,那层才会触发响应式。例如访问到a,不会再去访问a里面更深层内容)
区别:两者是否需要key属性。
Object.defineProperty:
必须要知道拦截的key(如,{count:1}的count)是什么,所以对新增属性无能为力。
代码 Object.defineProperty(data,'count',{get(){},set(){}})
proxy:
不需要关心具体的key,它去拦截的是 修改 data 上的任意 key 和 读取 data 上的任意 key。
兼容性,proxy无法兼容IE(包括IE11),vue检测到就降级为object.defineProperty的数据监听系统。
代码 new Proxy(data,{get(key){},set(key,value){}})
2,组件渲染更新过程 网红题—render-vnode-diff-patch
模板编译
1,转js:转换为js代码,即编译模板。
2,不是html标签语言:模板不是html标签语言,模板有插值,指令,js表达式(判断,循环。只有js才能实现判断,循环(图灵完备的)。所以模板一定会转为js代码)
3,编译为render函数:模板编译过程:模板编译为render函数,返回vnode。再执行diff和patch。
使用webpack,vue-loader,会在开发环境下编译模板。
虚拟dom和diff算法
1,将dom抽象为虚拟dom,diff算法对比新旧虚拟dom,只把变化的部分重新渲染。
2,diff算法是通过js层面的计算,返回一个patch补丁对象,之后解析这个patch对象,渲染到页面上。
因为传统的dom渲染慢,消耗性能。js在浏览器上运行快。
表示虚拟dom的js对象上有三个属性:tag(元素标签名),props(元素属性),children(元素下级-数组)
{ "tag":"div",props:{"color:":"red","id","id11"},children:[{}] }
diff算法规则
diff即比较。同层树节点进行比较。
1,只比较同意层级,不深度比较。
2,如tag不相同,则删掉重建。
3,如tag和key相同,则认为是相同节点,不在深度比较。
组件渲染/更新过程
- 初次渲染组件
1,解析模板为render函数。
2,触发响应式,监听data属性(第一次只触发getter)
3,执行render函数,生成vnode,并执行patch。
- 更新组件
1,修改data触发setter。
2,重新执行render函数,生成newVnode,
3,执行patch(vnode,newVnode),diff算法更新视图。
- 异步渲染组件
$nextTick()是异步渲染组件,汇总data的修改,一次性更新视图。好处:减少dom操作次数,提高性能。
3,webpack配置详解
含义:是js应用程序的静态模块打包器;是目前主流的前端工程化解决方案。
功能:代码压缩混淆;处理不同浏览器兼容性(如写es6代码)
常用配置:
1,webpack.config.js是配置文件。
webpack在打包构建之前,会先读取这个配置文件。生成dist文件。
用 vue-cli-service serve 启动就会用 vue.config.js;
用 webpack-dev-server 启动就会用 wepback.config.js(默认)。
- entry指定打包入口,output指定打包出口
3,如何修改http服务器地址?
devServer选项中配置proxy代理里的target指向新的http服务器地址。
4,热更新 webpack-dev-server
5,loader加载器
css,less,jpg怎么被打包输出?
webpack只会解析js文件,其他的文件需要对应的loader加载包来解析和打包。
less-loader处理less文件、css-loader处理css、babel-loader处理高级js语法。
使用webpack,vue-loader,会在开发环境下编译模板。
6,build打包 打包好处:代码压缩,性能优化。
4,webpack对性能的优化
在webpack.config.js里module选项里配置npParse(不解析哪些模块,例如jquery,elementui等)
实例:webpack.config.js 文件里 modules.export{
module:{ noParse:{ /jquery,elementui/} }
}
路由:两种模式、路由守卫、路由懒加载
5,vue-router两种模式
hash:
- hash模式下是指#之后的所有字符串,虽然它包含在url中,但不包含在http请求中,所以改变hash值不会重新加载页面。
2,hash改变触发浏览器的前进后退。
history:history模式下就是当页面刷新时,它会发送请求,把url传过去,因此,如果后端没有做处理的 话,就会因找不到资源报404。因此history模式时需要跟后端配合。
面试问题:history模式下刷新出现404如何解决?
原因:vue-router设置的路径不是真实存在的,所以刷新就返回404错误。
解决方式:需要后台配置支持。后端:修改服务器404页面配置路径情况下指向index.html页面。
前端:在vue里覆盖所有的路由情况,给出一个404页面。
补充知识:网页url的组成部分:http://127.0.0.1:8080/01-hash.html?a=100&b=20#/aaa/bbb
location.protocol(协议) : http
location.hostname(主机) :127.0.0.1
location.post(端口) : 8080
location.pathname : /01-hash.html
location.search: ?a=100&b=20
location.hash : #/aaa/bbb
6,vue-router路由守卫,动态路由、(组件和路由)懒加载
路由守卫(RBAC权限设计思想):就是路由钩子函数。作用:根据用户权限动态生成左侧菜单。
我们在vue项目里,src文件里创建permission.js权限控制。import到main.js里。
思路:router.beforeEach()结合router.addRoutes()使用。
当用户点击登录后,首先进入路由守卫,从后台获取用户的权限,在vuex中保存菜单信息,然后运用addRoutes方法动态添加路由配置,最后完成登录。
RBAC权限设计思想指:用户,角色,功能来让不同的账号登录后进入不同的页面。
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {//getToken从cookie里获取token
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
组件和路由懒加载:import,require
组件懒加载,即在需要的时候,加载该异步组件。路由懒加载,即在路由激活时,加载该路由配置下相应的异步组件。
CommonJs 规范的 require 可以实现组件和路由的懒加载,ES6 语法标准的 import 也可以实现。
组件和路由的懒加载,一般用于首屏优化,不立刻请求资源,待首屏加载完毕或者按需响应时再加载资源。可以减少首屏加载用时。
1,require实现懒加载:
{
path:”index”,
component : resolve => require( [ “@/views/index”] ),
hidden:true
}
2, import 实现懒加载
{
path:”index”,
component : ( ) => import( “@/views/index”), //返回一个‘promise‘对象
hidden:true
}
vue官方文档中使用的也是import实现路由懒加载
动态路由:两种
- params参数添加:
如index.js里添加id:{path:”/user/:id”, component:User}
然后在绑定id:<router-link :to=”/user/”+dataid tag=’button’> 用户</router-link>
获取这id:this.$route.params.id
2, query参数路由:
<router-link :to={path:”/user”,query:{id:’dataid’}}>用户</router-link>
7,MVVM
数据驱动视图—— vue:MVVM, react:setState
MVVM
1,view视图层,model数据层,viewModel通过数据绑定把视图层和数据层链接起来。
2,view层和model层完全解耦:
view层展示的是viewModel的数据,viewModel与model层进行交互。(开发者只需要关注业务逻辑,不需要手动操作dom)
8,vue组件生命周期 注释:el是挂载元素,data是数据
beforeCreate:el和data都是undefined
created:data有,el没有
说明:可以做初始化数据操作,但dom无法交互。如需要,使用$nextTick()访问dom
beforeMount:data和el都有,但还是虚拟dom
说明:此时对数据更改,不会触发updated
mounted:实例已挂载,数据渲染好
说明:可访问dom节点
beforeUpdate:虚拟dom打补丁之前
说明:更改数据,不会重新渲染
updated:虚拟dom重新渲染和patch打补丁之后调用,组成新的dom已更新。
说明:不要更新数据,会死循环
beforeDestroy:实例销毁前调用
说明:清定时器,自定义事件
destroyed:实例销毁后调用
说明:实例监听器移除,子实例销毁
9,vue组件生命周期钩子
是回调函数。创建组件实例的过程中会调用对应的钩子方法。
是一个发布订阅模式,将钩子订阅好,在对应的阶段发布。
10,vue父组件和子组件生命周期钩子执行顺序
渲染过程
父beforeCreate — 父created — 父beforeMount — 子beforeCreate — 子created — 子 beforeMount — 子mounted — 父mounted
更新过程
父beforeUpdate — 子beforeUpdate — 子updated — 父updated
销毁过程
父beforeDestroy — 子beforeDestory — 子destoryed — 父destroyed
11,ajax请求应放在哪个生命周期
放在mounted里:实例已挂载,数据渲染好。2,created里,使用$nextTick() 完成数据绑定。
因为ajax是异步获取数据,如放在mounted之前,即使ajax拿到数据,也要等渲染完成。
12,computed
缓存,data不变不会重新计算;提高性能。
13,何时使用keep-alive
- 缓存组件,不需要重复渲染。2,多个静态tab页切换。
14,作用域插槽slot占位符
子组件中放已占位符<slot></slot>(子组件中具名占位符)
父组件中给占位符填充内容
15,v-show和v-if
v-show通过css的display控制显示和隐藏
v-if是组件真正的渲染和销毁。频繁切换显示状态用v-show,否则用v-if
16,v-for中使用key
必须使用key,不要用index和random。如果是静态数据不做更新和删除操作,则可以用index。
原因:diff算法通过tag和key确认是否为sameNode。所以key的唯一性,能让diff算法更快的找到需要更新的dom。
移除key不存在的元素。减少渲染次数,提升性能。
17,何时使用beforeDestory
1,解除自定义事件 this.$off()
- 清除定时器
如果不解绑会造成内存泄漏
自定义事件:
1,子组件触发父组件的方法。
2,dom事件如window,scroll。
解除方法: this.$off()解除所有自定义事件。vue自己的事件会自己解除,但自定义事件则要手动解除。
项目实例:
康兮项目里,有个页面需展示心电图表数据。设置定时器,展示完一分钟的心电数据后再次请求。之前没有清除定时器,导致占内存占的越来越大,页面很卡。之后改为再次进行下次请求前清除定时器并在离开此页面时在beforeDestory里清除定时器。
18,组件data必须是一个函数
1,组件是一个class类,每个地方用的时候相当于这个类的实例化。实例出的data在一个闭包里,那么实例出来的数据就不会相互影响。如果data不是一个函数,那么每个组件的数据都共享了,一个地方改,其他地方跟着改。
2,vue根实例只有一个,所以没有这种情况。
19,如何将组件所有的props传递给子组件
$props 如: <User v-bind="$props" />
20,父子组件传值
父传子:
1,props
2,$parent
3, provide和inject 父组件暴露给后代组件的属性和方法
官方不推荐使用,因为数据追溯困难,不知道哪层声明了provide,哪层申明了inject。
4,.sync 父子双向传值语法糖
①,父组件在传入子组件的数据后加.sync,不需要对子组件的$emit进行接收;
②,子组件$emit传回的不再是个函数,而是 update:父组件传给子组件的变量名
好处:父组件少处理了$emit传来的函数那步。
<template>
<div class="content">
<btn :btnName.sync='num' ></btn>
</div>
</template>
export default{
props:[‘btnName’]
methods:{
changeName(){
this.$emit(‘update:btnName’,888)
}
}
}
子传父:
1,emit
- ref
其他组件传值:
1, $bus
2, vuex
21,v-model表单元素实现数据双向绑定
v-model本质上是语法糖,监听用户输入事件来更新数据。
官方文档有说到:v-model的原理有两个操作:
1,v-bind绑定value属性值
2,v-on绑定input事件监听到函数中,函数中获取最新的值赋值到绑定属性值中
<input v-model="searchText" /> 相当于:
<input :value="searchText" @input="searchText = $event.target.value" />
22,mixin抽离多组件有相同逻辑
mixin:1,js可包含任意选项;2,会混入到组件本身选项;
// 定义一个js文件,混入对象
export default {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
<script>// 其他组件使用混入对象
import mixin from './mixin'
export default {
mixins:[mixin],
data () {
return {}
}
}
</script>
mixin缺点:1,容易发生命名冲突;2,不能向mixin传参改变逻辑;
为了解决这个问题应使用vue3中通过逻辑关注点组织代码的方式,组合式api。
23,组合api和选项式api
options API:选项式api。会在data,props,methods,computed,watch等中定义属性和方法。逻辑关注点被分散在各个选项中。
例如:methods中有20多个方法,分不清哪个方法对应哪个功能。
composition API:组合式api。是一个setup函数,根据逻辑相关性组织代码。
24,vue是什么
做过很多项目,基本都了解,很熟练。
是渐进式的js框架,单页面应用框架。只关注MVC中的视图层。
1,vue的核心特征:数据驱动MVVM
2,组件、指令、vue全家桶(vue脚手架vue-cli,vueRouter,vuex,axios)
25,vuex
vuex是专门为vue应用程序开发的状态管理模式。由5部分组成:
state:数据
actions: 异步操作
mutations: 唯一改变state数据的地方
getters: 类似于计算属性,对state数据进行计算,缓存。
modules: 模块化管理store,例如user模块,shop模块...,每个模块有自己的state,getter,action,mutation。
vuex缺点:存储在xuex中的状态,刷新页面,会丢失。解决方法:安装插件 vuex-persistedState
26,vuex中action和mutation区别
- action可做异步操作,mutation不可以
2,mutation每次制作一个操作,action可以提交多个mutation
27,vue-router异步加载(懒加载)方法
- 正常方式:
import Login from "@/componts/pages/login"
export default new Router({
routes:[ {
path:"/login",
component:Login
}]
})
- 异步加载:
使用resolve异步机制,用require替代import,实现按需加载。
好处:如果项目巨大,那么首页加载将是灾难。所以需要把一些路由用异步加载的 方式进行加载。
export default new Router({
routes:[ {
path:'/login',
component: resolve=>require("[@/component/pages/login]",resolve)}]
})
27,vue路由this.route.push跳转页面不刷新解决
1,跳转前
this.$router.push( { 'path':'' ,query:{ 'id':'' }})
2,接收页面 watch 监听
watch :{
‘$route’(to,from){ //监听路由变化 }
}
28,diff算法的时间复杂度
diff算法:同层树节点进行比较。
- 只比较同一层级,不跨级比较
- tag不相同,则删掉重建,不再深度比较。
- tag和key都相同,则认为是相同节点,不再深度比较。
29,vue常见性能优化方案
v-show v-if v-for computed vue-router keep-alive 定时器 图片懒加载 webpack
- 合理使用v-show,v-if
2,v-for加key,避免与v-if同时使用
3,合理使用computed
4,使用vue-router异步加载组件
5,使用keep-alive缓存组件
6,自定义事件,DOM事件,定时器及时销毁:例如使用防抖和节流时使用lodash库,移除组件时在beforeDestroy销毁定时器。
7,Data层级不要太深
8,图片懒加载
9,webpack层面优化:① productionSourceMap:false 提升构建速度
vue.config.js 中设置productionSourceMap:false 在构建生产环境时 不要生成sourcemap文件,提高构建速度。
30,v-for优先级高于v-if,避免同时使用
31,vue注册全局组件
//在mian.js里引入Count组件
import Count from "..."
Vue.component("MyCount",Count)
- 定义组件 Count.vue
2, main.js里引入组件
- Vue.component( “组件名”,实例) 注册组件
4,任何页面都可以使用该组件
32,组件封装
面试回答:
我在用vue开发项目时,一般都会用到组件封装。
我在搭建一个项目时,创建一个views目录放页面组件,common目录放公共组件(如,head,foot),feature目录放功能组件(如 swiper,tabbar,list)。
vue.extend()不常用,用vue.component更简单
33,dict字典
场景:固定下拉菜单内容,把这个下拉菜单全部封装到dict.js字典里。再把这个字典放到vue原型上,在页面中调用。
34,elementui组件的二次封装
重复性较多的组件,可以封装起来。例如样式功能相似的表格。给表格传入不同的参数
35,vue综合面试
1,vue为什么要求组件模板只有一个根元素
①vue的VDOM算法只允许VDOM的树状解构有一个根节点。如果逻辑抽象树有多个根,那么就会产生多个入口,这对于遍历、查找、比较都不方便。
②组件的template都会转换成Vnode对象,组件的根元素对应的就是vnode对象。
2,你了解diff算法吗?
在”vue2题1“——组件渲染和更新过程里
3,在vue文件中style是必须的吗?script是必须的吗?为社么?
template是必须的,style和scipt都不是必须的。如果没有template,vue会报错。
36,scoped原理以及scoped的穿透用法
scoped原理:
scoped:css样式只能用于当前vue组件。
scoped实现原理:通过postCss实现,给组件中所有的dom元素添加唯一的动态属性。给css选择器添加一个属性选择器。这样做是使样式只作用于该属性的dom元素。
scoped缺点:如果你子组件的根元素上有一个类已经在这个父组件中定义过了,那么这个父组件的样式就会泄露到子组件中。
scoped穿透:sass和less 使用 外层 /deep/ 第三方组件样式
stylus使用 外层 >>> 第三方组件样式
scoped穿透缺点:失去了组件的封装效果。这个组件内的所有的 .title 类的样式都会被这些样式所浸染—即便是孙节点。
使用scope和module的区别?
scoped样式可能会收到全局样式影响,
module样式不会受到全局样式影响。
scoped的效果是对元素加上data属性,但是可能会受到全局样式的影响。
module经过css-loader会对类名进行编译,添加上hash值等,所以无需担心和其他页面的同样的类名冲突,也可以避免全局样式被污染。
其实两种方案都非常简单、易用,在某种程度上解决的是同样的问题。 那么你该选择哪种呢?
scoped 样式的使用不需要额外的知识,给人舒适的感觉。它所存在的局限,也正是它的使用简单的原因。它可以用于支持小型到中型的应用。
在更大的应用或更复杂的场景中,这个时候,对于 CSS 的运用,我们就会希望它更加显式,拥有更多的控制权。虽然在模板中大量使用 $style 看起来并不那么“性感”,但却更加安全和灵活,为此我们只需付出微小的代价。
module好处:
它的第一点好处就是,当我们在 HMTL 中查看这个元素时我们可以立刻知道它所属的是哪个组件;第二点好处是,一切都变成显式的了,我们拥有了彻底的控制权——不会再有什么奇怪的现象了。
scoped:
<style>//生成style样式
.button[data-v-f61kqi1] {color: red;}
</style><button class=”button” data-v-f61kqi1></button>
module:
<template><button :class="$style.button" />
</template><style module>.button {color: red}
</style>
//生成html和style样式
<style>.ComponentName__button__2Kxy {color: red;}
</style><button class=”ComponentName__button__2Kxy”></button>
37,ruoyi框架
当路由匹配到一个不存在的路由时,显示某一个组件:
{ path:"/:path(.*)",component:import('@/view/redirect.vue') }
38,vue实例方法
$mount() 外部设置el,vue的作用范围。
$destroy() 手动销毁。
$watch() 监听。
$forceUpdate() 强制更新
$delete()
$nextTick()
39,自定义指令
//全局指令
Vue.directive("red",(ele)=>{
ele.style.color = "red"
})
//组件指令
directives:{
test:{
inserted: function (el,binding) {// 指令的定义
/ /el为绑定元素,可以对其进行dom操作
console.log(binding) //一个对象,包含很多属性属性
},
bind: function (el, binding, vnode) {
el.innerHTML =binding.value
}
}
}
40,toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
在vue中,过滤器使用toFixed()对数值取两位小数操作,提示data.toFixed is not a function
解决办法:
Number(data).toFixed(n) //转成数值类型;
41,Vue.config.productionTip = false
阻止启动生产消息,常用作指令
没有Vue.config.productionTip = false这句代码,它会显示你生产模式的消息
开发环境下,Vue 会提供很多警告来帮你对付常见的错误与陷阱。
而在生产环境下,这些警告语句却没有用,反而会增加应用的体积。