作者:东起
转发链接:https://zhuanlan.zhihu.com/p/103763164
Vue常见的面试知识点汇总(上)「附答案」
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之前!
推荐网站:https://www.cnblogs.com/wind-lanyan/p/9061684.html
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()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中
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) // 1new fproxy(1, 2) // {value: 2}fproxy.prototype === Object.prototype // truefproxy.foo === "Hello, foo" // true
{ path: '', name: '', component: , meta: {keepAlive: true} // 这个是需要keepalive的},{ path: '', name: '', component: , meta: {keepAlive: false} // 这是不会被keepalive的}
如果缓存的组件想要清空数据或者执行初始化方法,在加载组件的时候调用activated钩子函数,如下:
activated: function () { this.data = '';}
使用axios直接进行跨域访问不可行,我们需要配置代理
代理可以解决的原因:
因为客户端请求服务端的数据是存在跨域问题的,而服务器和服务器之间可以相互请求数据,是没有跨域的概念(如果服务器没有设置禁止跨域的权限问题),也就是说,我们可以配置一个代理的服务器可以请求另一个服务器中的数据,然后把请求出来的数据返回到我们的代理服务器中,代理服务器再返回数据给我们的客户端,这样我们就可以实现跨域访问数据
1.配置BaseUrl
import axios from 'axios'Vue.prototype.$axios = axiosaxios.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跨域得到解决
第一种(最常用):
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就是resolveconst list = r => require.ensure([], () => r(require('../components/list/list')), 'list');// 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载 const router = new Router({ routes: [ { path: '/list/blog', component: list, name: 'blog' } ]})
一、思路
① 在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}
④ 渲染菜单
就这样我们动态加载路由就是实现了,关键点就是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页面的
当创建一个 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);});
当使用路由参数时,比如:
{path:’/list/:id’component:Foo}
从 /list/aside导航到 /list/foo,原来的组件实例会被复用。
因为两个路由都渲染同个组件Foo,比起销毁再创建,复用则更加高效。
不过,这也意味着组件的生命周期钩子不会再被调用。
如果跳转到相同的路由还会报以下错误
这个时候我们需要重写push方法,在src/router/index.js 里面import VueRouter from 'vue-router'下面写入下面方法即可
const routerPush = VueRouter.prototype.pushVueRouter.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都不会调用。
名字以 _ 或 $开始的属性不会被 Vue 实例代理,因为它们可能与 Vue 的内置属性与 API 方法冲突。用 vm.$data._property 访问它们。
方法①:对整个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 }}
key的作用主要是为了高效的更新虚拟DOM,是因为Virtual DOM 使用Diff算法实现的原因。
当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默认情况下也是遵循以上原则。
比如一下这个情况
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置去插入新的节点。
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
解决的问题:有些时候在改变数据后立即要对dom进行操作,此时获取到的dom仍是获取到的是数据刷新前的dom,无法满足需要,这个时候就用到了$nextTick。
① 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对象中管理
《Kbone原理详解与小程序技术选型》
《为什么我不再用Vue,改用React?》
《让Jenkins自动部署你的Vue项目「实践」》
《20个免费的设计资源 UI套件背景图标CSS框架》
《Deno将停止使用TypeScript,并公布五项具体理由》
《前端骨架屏都是如何生成的》
《Vue原来可以这样写开发效率杠杠的》
《用vue简单写一个音乐播放组件「附源码」》
《为什么Vue3.0不再使用defineProperty实现数据监听?》
《「干货」学会这些Vue小技巧,可以早点下班和女神约会》
《探索 Vue-Multiselect》
《细品30张脑图带你从零开始学Vue》
《Vue后台项目中遇到的技术难点以及解决方案》
《手把手教你Electron + Vue实战教程(五)》
《手把手教你Electron + Vue实战教程(四)》
《手把手教你Electron + Vue实战教程(三)》
《手把手教你Electron + Vue实战教程(二)》
《手把手教你Electron + Vue实战教程(一)》
《收集22种开源Vue模板和主题框架「干货」》
《如何写出优秀后台管理系统?11个经典模版拿去不谢「干货」》
《手把手教你实现一个Vue自定义指令懒加载》
《基于 Vue 和高德地图实现地图组件「实践」》
《一个由 Vue 作者尤雨溪开发的 web 开发工具—vite》
《是什么让我爱上了Vue.js》
《1.1万字深入细品Vue3.0源码响应式系统笔记「上」》
《1.1万字深入细品Vue3.0源码响应式系统笔记「下」》
《「实践」Vue 数据更新7 种情况汇总及延伸解决总结》
《尤大大细说Vue3 的诞生之路「译」》
《提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器》
《大厂Code Review总结Vue开发规范经验「值得学习」》
《Vue3 插件开发详解尝鲜版「值得收藏」》
《带你五步学会Vue SSR》
《记一次Vue3.0技术干货分享会》
《Vue 3.x 如何有惊无险地快速入门「进阶篇」》
《「干货」微信支付前后端流程整理(Vue+Node)》
《带你了解 vue-next(Vue 3.0)之 炉火纯青「实践」》
《「干货」Vue+高德地图实现页面点击绘制多边形及多边形切割拆分》
《「干货」Vue+Element前端导入导出Excel》
《「实践」Deno bytes 模块全解析》
《细品pdf.js实践解决含水印、电子签章问题「Vue篇」》
《基于vue + element的后台管理系统解决方案》
《Vue仿蘑菇街商城项目(vue+koa+mongodb)》
《基于 electron-vue 开发的音乐播放器「实践」》
《「实践」Vue项目中标配编辑器插件Vue-Quill-Editor》
《基于 Vue 技术栈的微前端方案实践》
《消息队列助你成为高薪 Node.js 工程师》
《Node.js 中的 stream 模块详解》
《「干货」Deno TCP Echo Server 是怎么运行的?》
《「干货」了不起的 Deno 实战教程》
《「干货」通俗易懂的Deno 入门教程》
《Deno 正式发布,彻底弄明白和 node 的区别》
《「实践」基于Apify+node+react/vue搭建一个有点意思的爬虫平台》
《「实践」深入对比 Vue 3.0 Composition API 和 React Hooks》
《前端网红框架的插件机制全梳理(axios、koa、redux、vuex)》
《深入Vue 必学高阶组件 HOC「进阶篇」》
《深入学习Vue的data、computed、watch来实现最精简响应式系统》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(一)》
《10个实例小练习,快速入门熟练 Vue3 核心新特性(二)》
《教你部署搭建一个Vue-cli4+Webpack移动端框架「实践」》
《2020前端就业Vue框架篇「实践」》
《详解Vue3中 router 带来了哪些变化?》
《Vue项目部署及性能优化指导篇「实践」》
《Vue高性能渲染大数据Tree组件「实践」》
《尤大大细品VuePress搭建技术网站与个人博客「实践」》
《10个Vue开发技巧「实践」》
《是什么导致尤大大选择放弃Webpack?【vite 原理解析】》
《带你了解 vue-next(Vue 3.0)之 小试牛刀【实践】》
《带你了解 vue-next(Vue 3.0)之 初入茅庐【实践】》
《实践Vue 3.0做JSX(TSX)风格的组件开发》
《一篇文章教你并列比较React.js和Vue.js的语法【实践】》
《手拉手带你开启Vue3世界的鬼斧神工【实践】》
《深入浅出通过vue-cli3构建一个SSR应用程序【实践】》
《怎样为你的 Vue.js 单页应用提速》
《聊聊昨晚尤雨溪现场针对Vue3.0 Beta版本新特性知识点汇总》
《【新消息】Vue 3.0 Beta 版本发布,你还学的动么?》
《Vue真是太好了 壹万多字的Vue知识点 超详细!》
《Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5》
《深入浅出Vue3 跟着尤雨溪学 TypeScript 之 Ref 【实践】》
《手把手教你深入浅出vue-cli3升级vue-cli4的方法》
《Vue 3.0 Beta 和React 开发者分别杠上了》
《手把手教你用vue drag chart 实现一个可以拖动 / 缩放的图表组件》
《Vue3 尝鲜》
《总结Vue组件的通信》
《Vue 开源项目 TOP45》
《2020 年,Vue 受欢迎程度是否会超过 React?》
《尤雨溪:Vue 3.0的设计原则》
《使用vue实现HTML页面生成图片》
《实现全栈收银系统(Node+Vue)(上)》
《实现全栈收银系统(Node+Vue)(下)》
《vue引入原生高德地图》
《Vue合理配置WebSocket并实现群聊》
《多年vue项目实战经验汇总》
《vue之将echart封装为组件》
《基于 Vue 的两层吸顶踩坑总结》
《Vue插件总结【前端开发必备】》
《Vue 开发必须知道的 36 个技巧【近1W字】》
《构建大型 Vue.js 项目的10条建议》
《深入理解vue中的slot与slot-scope》
《手把手教你Vue解析pdf(base64)转图片【实践】》
《使用vue+node搭建前端异常监控系统》
《推荐 8 个漂亮的 vue.js 进度条组件》
《基于Vue实现拖拽升级(九宫格拖拽)》
《手摸手,带你用vue撸后台 系列二(登录权限篇)》
《手摸手,带你用vue撸后台 系列三(实战篇)》
《前端框架用vue还是react?清晰对比两者差异》
《Vue组件间通信几种方式,你用哪种?【实践】》
《浅析 React / Vue 跨端渲染原理与实现》
《10个Vue开发技巧助力成为更好的工程师》
《手把手教你Vue之父子组件间通信实践讲解【props、$ref 、$emit】》
《1W字长文+多图,带你了解vue的双向数据绑定源码实现》
《深入浅出Vue3 的响应式和以前的区别到底在哪里?【实践】》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《基于Vue/VueRouter/Vuex/Axios登录路由和接口级拦截原理与实现》
《手把手教你D3.js 实现数据可视化极速上手到Vue应用》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【上】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【中】》
《吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧【下】》
《Vue3.0权限管理实现流程【实践】》
《后台管理系统,前端Vue根据角色动态设置菜单栏和路由》
作者:东起
转发链接:https://zhuanlan.zhihu.com/p/103763164