作者:东起
转发链接:https://zhuanlan.zhihu.com/p/103763164
目录
Vue常见的面试知识点汇总(下)「附答案」本篇
由于Vue常见的面试知识点汇总内容比较多,本篇分为上下两部分。小编建议从上一篇看起
⑤ 服务器端SSR渲染
除了上面的方案以外,另一种方案也不容小视
我们先说说通常项目中是如何加载页面数据:Vue组件生命周期中请求异步接口,在mounted之前应该都可以,据我了解绝大部分同学是在mounted的时候执行异步请求。但是我们可以把页面需要的请求放到Vue-Router的守卫中执行,意思是在路由beforeEnter之前就可以请求待加载页面中所有组件需要的数据,此时待加载页面的Vue组件还没开始渲染,而Vue组件开始渲染的时候我们就可以用Vuex里面的数据了。
以上方法的实现思路:
图意:每个页面(Page)中都会有很多个Vue组件,可以在Vue组件中添加自定义属性fetchData,fetchData里面可以执行异步请求(图中执行Vuex的Action),但是我们怎么获取到所有组件的fetchData方法并执行呢?如图所示,在router.beforeResolve守卫中,我们看看router.beforeResolve的定义,所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用,意思是即使页面中有异步组件,它会等待异步组件解析之后执行,并且解析守卫在beforeEnter之前执行。那我们怎么在解析守卫中获取到待加载页面的所有组件呢?通过router.getMatchedComponents方法。
这样我们就可以在解析守卫中获取到所有待加载组件的fetchData方法并执行,这样无疑会在组件开始渲染之后获取到所有数据,提高页面加载速度。
很多人可能有个疑问,如果异步请求放在beforeCreate和created不是一样吗?答案是否定的,因为这种方式可以将异步请求放到beforeCreate之前!
22、你了解vue的diff算法吗?
推荐网站:https://www.cnblogs.com/wind-lanyan/p/9061684.html
23、vue能监听到数组变化的方法有哪些?为什么这些方法能监听到呢?
Vue.js观察数组变化主要通过以下7个方法(push、pop、shift、unshift、splice、sort、reverse)
大家知道,通过Object.defineProperty()劫持数组为其设置getter和setter后,调用的数组的push、splice、pop等方法改变数组元素时并不会触发数组的setter,继而数组的数据变化并不是响应式的,但是vue实际开发中却是实时响应的,是因为vue重写了数组的push、splice、pop等方法
从源码中可以看出,ob.dep.notify()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中
24、说说你对proxy的理解?
Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。
var target = {
name: 'zhangsan',
age:20,
sex:'男'
}
var logHandler = {
get(target, key) {
console.log(`${key}被读取`)
return target[key]
},
set(target, key, value) {
console.log(`${key}被设置为${value}`)
target[key] = value
}
}
var demo = new Proxy(target, logHandler)
demo.name //name被读取
var proxy = new Proxy(target, handler);
Proxy对象的所有用法,都是上面的这种形式。不同的只是handle参数的写法。其中new Proxy用来生成Proxy实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。
我们可以将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用。
var object = { proxy: new Proxy(target, handler) };
Proxy对象也可以作为其它对象的原型对象。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
上面代码中,proxy对象是obj的原型对象,obj本身并没有time属性,所以根据原型链,会在proxy对象上读取属性,从而被拦截。
同一个拦截函数,可以设置多个操作。
var handler = {
get: function (target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function (target, thisBinding, args) {
return args[0];
},
construct: function (target, args) {
return { value: args[1] };
}
};
var fproxy = new Proxy(function (x, y) {
return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
25、怎么缓存当前的组件?缓存后怎么更新?
<keep-alive>
<router-view></router-view>
</keep-alive>
<!-- 这里是需要keepalive的 -->
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<!-- 这里不会被keepalive -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
{
path: '',
name: '',
component: ,
meta: {keepAlive: true} // 这个是需要keepalive的
},
{
path: '',
name: '',
component: ,
meta: {keepAlive: false} // 这是不会被keepalive的
}
如果缓存的组件想要清空数据或者执行初始化方法,在加载组件的时候调用activated钩子函数,如下:
activated: function () {
this.data = '';
}
26、axios怎么解决跨域的问题?
使用axios直接进行跨域访问不可行,我们需要配置代理
代理可以解决的原因:
因为客户端请求服务端的数据是存在跨域问题的,而服务器和服务器之间可以相互请求数据,是没有跨域的概念(如果服务器没有设置禁止跨域的权限问题),也就是说,我们可以配置一个代理的服务器可以请求另一个服务器中的数据,然后把请求出来的数据返回到我们的代理服务器中,代理服务器再返回数据给我们的客户端,这样我们就可以实现跨域访问数据
1.配置BaseUrl
import axios from 'axios'
Vue.prototype.$axios = axios
axios.defaults.baseURL = '/api' //关键代码
2.配置代理
在config文件夹下的index.js文件中的proxyTable字段中,作如下处理:
proxyTable: {
'/api': {
target:'http://api.douban.com/v2', // 你请求的第三方接口
changeOrigin:true,
// 在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,
//这样服务端和服务端进行数据的交互就不会有跨域问题
pathRewrite:{ // 路径重写,
'^/api': ''
// 替换target中的请求地址,也就是说以后你在请求http://api.douban.com/v2/XXXXX
//这个地址的时候直接写成/api即可。
}
}
}
1. 在具体使用axios的地方,修改url如下即可
axios.get("/movie/top250").then((res) => {
res = res.data
if (res.errno === ERR_OK) {
this.themeList=res.data;
}
}).catch((error) => {
console.warn(error)
})
原理:
因为我们给url加上了前缀/api,我们访问/movie/top250就当于访问了:localhost:8080/api/movie/top250(其中localhost:8080是默认的IP和端口)。
在index.js中的proxyTable中拦截了/api,并把/api及其前面的所有替换成了target中的内容,因此实际访问Url是http://api.douban.com/v2/movie/top250。
至此,纯前端配置代理解决axios跨域得到解决
27、怎么实现路由懒加载呢?
第一种(最常用):
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
第二种:
const router = new Router({
routes: [
{
path: '/index',
component: (resolve) => {
require(['../components/index'], resolve) // 这里是你的模块 不用import去引入了
}
}
]
})
第三种(官方推荐):
// r就是resolve
const list = r => require.ensure([], () => r(require('../components/list/list')), 'list');
// 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载
const router = new Router({
routes: [
{
path: '/list/blog',
component: list,
name: 'blog'
}
]
})
28、怎样动态加载路由?
一、思路
① 在vue-router对象中首先初始化公共路由,比如(首页,404,login)等
② 用户登陆成功后,根据用户的角色信息,获取对应权限菜单信息menuList,并将后台返回的menuList转换成我们需要的router数据结构
③ 通过router.addRouter(routes)方法,同时我们可以将转后的路由信息保存于vuex,这样我们可以在我们的SideBar组件中获取我们的全部路由信息,并且渲染我们的左侧菜单栏,让动态路由实现。
二、实现
① 初始化公共路由
//只显示主要代码
export const routes= [
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{ path: '/404', component: () => import('@/views/404'), hidden: true }
]
export default new Router({
scrollBehavior: () => ({ y: 0 }),
routes: routes
})
② 登陆成功后,获取菜单信息 menuList,并转换成router数组的结构
router.beforeEach((to, from, next) => {
NProgress.start()//进度条包 npm安装
if (getToken()) {
/*有 token,已经登录成功*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => { // 拉取user_info
const roles = res.roles
store.dispatch("GetMenu").then(data => {
initMenu(router, data);
});
next()
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
/* 无 token*/
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next()
} else {
next('/login') // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
③ 动态加载路由
import store from '../store'
export const initMenu = (router, menu) => {
if (menu.length === 0) {
return
}
let menus = formatRoutes(menu);
let unfound = { path: '*', redirect: '/404', hidden: true }
menus.push(unfound) //404组件最后添加
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
export const formatRoutes = (aMenu) => {
const aRouter = []
aMenu.forEach(oMenu => {
const {
path,
component,
name,
icon,
childrens
} = oMenu
if (!validatenull(component)) {
let filePath;
const oRouter = {
path: path,
component(resolve) {
let componentPath = ''
if (component === 'Layout') {
require(['../views/layout/Layout'], resolve)
return
} else {
componentPath = component
}
require([`../${componentPath}.vue`], resolve)
},
name: name,
icon: icon,
children: validatenull(childrens) ? [] : formatRoutes(childrens)
}
aRouter.push(oRouter)
}
})
return aRouter
}
④ 渲染菜单
<template>
<el-scrollbar wrapClass="scrollbar-wrapper">
<el-menu
mode="vertical"
:show-timeout="200"
:default-active="$route.path"
:collapse="isCollapse"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<sidebar-item v-for="route in permission_routers" :key="route.name" :item="route" :base-path="route.path"></sidebar-item>
</el-menu>
</el-scrollbar>
</template>
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
import { validatenull } from "@/utils/validate";
import { initMenu } from "@/utils/util";
export default {
components: { SidebarItem },
created() {
},
computed: {
...mapGetters([
'permission_routers',
'sidebar',
'addRouters'
]),
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
就这样我们动态加载路由就是实现了,关键点就是router.addRoute方法
⑤ 防坑
点击刷新的时候页面空白 控制台也不报错?
点击刷新,vue-router会重新初始化,那么我们之前的动态addRoute就不存在了,此时访问一个不存在的页面,所以我们的sidebar组件也就不会被访问,那么也无法获取菜单信息,就导致页面空白。所以我们需要把加载菜单信息这一步放在router的全局守卫beforeEach中就可以了。
export const initMenu = (router, menu) => {
if (menu.length === 0) {
return
}
let menus = formatRoutes(menu);
// 最后添加
let unfound = { path: '*', redirect: '/404', hidden: true }
menus.push(unfound)
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
//404组件一定要放在动态路由组件的最后,不然你刷新动态加载的页面,会跳转到404页面的
29、切换到新路由时,页面要滚动到顶部或保持原先的滚动位置怎么做呢?
当创建一个 Router 实例,可以提供一个 scrollBehavior 方法:
注意: 这个功能只在 HTML5 history 模式下可用。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
对于所有路由导航,简单地让页面滚动到顶部。
返回 savedPosition,在按下 后退/前进 按钮时,在滚动条位置,就会像浏览器的原生表现那样:
scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
模拟『滚动到锚点』的行为
scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}
还可以利用路由元信息更细颗粒度地控制滚动。
routes: [
{ path: '/', component: Home, meta: { scrollToTop: true }},
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar, meta: { scrollToTop: true }}
]
const scrollBehavior = (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition
} else {
const position = {}
if (to.hash) {
position.selector = to.hash
}
if (to.matched.some(m => m.meta.scrollToTop)) {
position.x = 0
position.y = 0
}
return position
}
}
还可以在main.js入口文件配合vue-router写这个
router.afterEach((to,from,next) => {
window.scrollTo(0,0);
});
30、vue-router如何响应路由参数的变化?
当使用路由参数时,比如:
{path:’/list/:id’component:Foo}
从 /list/aside导航到 /list/foo,原来的组件实例会被复用。
因为两个路由都渲染同个组件Foo,比起销毁再创建,复用则更加高效。
不过,这也意味着组件的生命周期钩子不会再被调用。
如果跳转到相同的路由还会报以下错误
这个时候我们需要重写push方法,在src/router/index.js 里面import VueRouter from 'vue-router'下面写入下面方法即可
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error=> error)
}
如何响应不同的数据呢?
① 复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
② 使用beforeRouteUpdate
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
注意:
(1)从同一个组件跳转到同一个组件。
(2)生命周期钩子created和mounted都不会调用。
31、vue模板中为什么以_、$开始的变量无法渲染?
名字以 _ 或 $开始的属性不会被 Vue 实例代理,因为它们可能与 Vue 的内置属性与 API 方法冲突。用 vm.$data._property 访问它们。
32、vue中,如何监听一个对象内部的变化?
方法①:对整个obj深层监听
watch:{
obj:{
handler(newValue,oldValue){
console.log('obj changed')
},
deep: true,//深度遍历
immediate: true
//默认第一次绑定的时候不会触发watch监听,值为true时可以在最初绑定的时候执行
}
}
方法② :指定key
watch: {
"dataobj.name": {
handler(newValue, oldValue) {
console.log("obj changed");
}
}
}
方法③:computed
computed(){
ar(){
return this.obj.name
}
}
33、v-for循环时为什么要加key?
key的作用主要是为了高效的更新虚拟DOM,是因为Virtual DOM 使用Diff算法实现的原因。
当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默认情况下也是遵循以上原则。
比如一下这个情况
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置去插入新的节点。
34、$nextTick用过吗,有什么作用?
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
解决的问题:有些时候在改变数据后立即要对dom进行操作,此时获取到的dom仍是获取到的是数据刷新前的dom,无法满足需要,这个时候就用到了$nextTick。
35、vue和react的区别是什么?
① React严格上只针对MVC的view层,Vue则是MVVM模式
② virtual DOM不一样,vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树.而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
③ 组件写法不一样, React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js'; Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,jd写在同一个文件;
④ 数据绑定: vue实现了数据的双向绑定,react数据流动是单向的
⑤ state对象在react应用中不可变的,需要使用setState方法更新状态;在vue中,state对象不是必须的,数据由data属性在vue对象中管理
推荐Vue学习资料文章:
《Deno将停止使用TypeScript,并公布五项具体理由》
《为什么Vue3.0不再使用defineProperty实现数据监听?》
《如何写出优秀后台管理系统?11个经典模版拿去不谢「干货」》
《一个由 Vue 作者尤雨溪开发的 web 开发工具—vite》
《提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器》
《大厂Code Review总结Vue开发规范经验「值得学习」》
《带你了解 vue-next(Vue 3.0)之 炉火纯青「实践」》
《「干货」Vue+高德地图实现页面点击绘制多边形及多边形切割拆分》
《细品pdf.js实践解决含水印、电子签章问题「Vue篇」》
《Vue仿蘑菇街商城项目(vue+koa+mongodb)》
《基于 electron-vue 开发的音乐播放器「实践」》
《「实践」Vue项目中标配编辑器插件Vue-Quill-Editor》
《「干货」Deno TCP Echo Server 是怎么运行的?》
《「实践」基于Apify+node+react/vue搭建一个有点意思的爬虫平台》
《「实践」深入对比 Vue 3.0 Composition API 和 React Hooks》
《前端网红框架的插件机制全梳理(axios、koa、redux、vuex)》
《深入学习Vue的data、computed、watch来实现最精简响应式系统》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(一)》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(二)》
《教你部署搭建一个Vue-cli4+Webpack移动端框架「实践」》
《尤大大细品VuePress搭建技术网站与个人博客「实践」》
《是什么导致尤大大选择放弃Webpack?【vite 原理解析】》
《带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】》
《带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】》
《一篇文章教你并列比较React.js和Vue.js的语法【实践】》
《深入浅出通过vue-cli3构建一个SSR应用程序【实践】》
《聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总》
《【新消息】Vue 3.0 Beta 版本发布,你还学的动么?》
《Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5》
《深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】》
《手把手教你深入浅出vue-cli3升级vue-cli4的方法》
《Vue 3.0 Beta 和React 开发者分别杠上了》
《手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件》
《Vue3 尝鲜》
《2020 年,Vue 受欢迎程度是否会超过 React?》
《手把手教你Vue解析pdf(base64)转图片【实践】》
《手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】》
《深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现》
《手把手教你D3.js 实现数据可视化极速上手到Vue应用》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】》
作者:东起
转发链接:https://zhuanlan.zhihu.com/p/103763164
本文暂时没有评论,来添加一个吧(●'◡'●)