vue框架篇
vue的核心是什么
vue是一套构建用户界面的MVVM框架。vue的核心值关注视图层。
核心思想:
- 数据驱动(视图的内容随着数据的改变而改变)
- 组件化(可以增加代码的复用性,可维护性,可测试性,提高开发效率,方便重复使用,体现了高内聚低耦合)
vue单页面的优缺点
优点:
- 数据驱动视图,对真实dom进行抽象出virtual dom(本质就是一个js对象),并配合diff算法,响应式和观察者、异步队列等手段以最小代价更新dom,渲染页面。
- 组件化,组件用单文件的形式进行代码的组织编写,使得我们可以在一个文件里编写html/css(scoped属性配置css隔离)/js,并且配合vue-loader之后,支持更强大的预处理器等功能
- 强大且丰富的API提供一系列的api能满足业务开发中各类需求
- 由于采用虚拟dom,让vue ssr(服务器端渲染) 先天不足
- 生命周期钩子函数,选项式的代码组织方式,写熟了还是蛮顺畅的,但是仍然有优化空间(vue3 composition-api)
- 生态好,社区活跃
缺点:
- 由于底层基于Object.defineProperty实现响应式,而这个api本身不支持IE8及以下浏览器
- csr的先天不足,首屏性能问题(白屏)
- 由于百度等搜索引擎爬虫无法爬取js中的内容,故spa先天就对SEO优化不好。
请详细说下你对vue生命周期的理解?
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。总结起来八个字:创建 加载 更新 销毁
创建前/后: 在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el为undefined,还未初始化。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在
vue中组件如何通讯
父子组件:props
和this.$emit
(自定义事件名,要发送的数据)
自定义事件:event.$on
event.$off
event.$emit
Vue 实例自带api,可以定义一个全局vue实例。
还有 vuex插件
通讯。
请简述vue的单向数据流
父级prop
的更新会向下流动到子组件中,每次父组件发生更新,子组件所有的prop
都会刷新为最新的值
数据从父组件传递给子组件,只能单向绑定,子组件内部不能直接修改父组件传递过来的数据(可以使用data和computed解决)
vue双向绑定的原理
双向绑定就是:数据变化更新视图,视图变化更新数据
vue数据双向绑定是通过数据劫持和观察者模式来实现的。
数据劫持: object.defineproperty 它的目的是:当给属性赋值的时候,程序可以感知到,就可以控制改变属性值
观察者模式: 当属性发生改变的时候,使用该数据的地方也发生改变
双向数据绑定v-model的实现原理
- input 元素的 value = this.name
- 绑定input事件this.name = $event.target.value
- data更新触发re-render
vue中常用的修饰符有哪些
// 输入框修饰符:
.lazy 改变后触发,光标离开input输入框的时候值才会改变
.number 将输出字符串转为number类型
.trim 自动过滤用户输入的收尾空格
// 事件修饰符
.stop 阻止点击事件冒泡,相当于原生js中的event.stopPropagation()
.prevent 防止执行预设的行为,相当于原生js中的event.preventDefault()
.capture 添加事件侦听器时使用事件捕获模式,就是谁有改事件修饰符,就先触发谁。
.self 只会触发自己范围内的事件,不包括子元素
.once 只执行一次
// 键盘修饰符
.enter 回车键
.tab 制表键
.esc 返回键
.space 空格键
.up 向上键
.down 向下键
.left 向左键
.right 向右键
// 系统修饰符
.ctrl
.alt
.shift
.meta
vue中指令有哪些?
- v-for: 循环数组,对象,字符串,数字
- v-on: 绑定事件监听
- v-bind: 动态绑定一个或者多个属性
- v-model: 表单控件或者组件上创建双向绑定
- v-if v-else v-else-if 条件渲染
- v-show: 根据表达式真假,切换元素的display
- v-html: 更新元素的innerHtml
- v-text: 更新元素的textcontent
- v-pre: 跳过这个元素和子元素的编译过程
- v-clock:这个指令保持在元素上知道关联实例结束编译
- v-once 只渲染一次
v-if 和 v-show 的区别
v-if
是根本不会渲染DOM节点。
v-show
是基于css的display属性值对DOM进行显示隐藏的。
v-if 与 v-for 优先级
首先不要把v-if与v-for用在同一个元素上,原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
vue循环的key作用
- 必须用key,且不能是index和random
- diff算法中通过tag和key来判断,是否是sameNode
- 减少渲染次数,提升渲染性能。
v-on 可以绑定多个方法吗?
可以,如果绑定多个事件,可以用键值对的形式(事件类型:事件名)
如果绑定是多个相同事件,直接使用逗号分隔就行。
如何定义一个过滤器
过滤器本质就是一个有参数返回值的方法
new Vue({
filters: {
myCurrency: function(myInput){
return 处理后的数据
}
}
})
// 使用方法
<span>{{ 表达式 | 过滤器 }}</span>
过滤器高级用法:可以指定参数,告诉过滤器按照参数进行数据的过滤
什么是计算属性
计算属性是用来声明时的描述一个值依赖了其他的值,当它依赖的这个值发生改变时,就更新 DOM
当在模板中把数据绑定到一个计算属性上时, vue 会在它依赖的任何值导致该计算属性改变时更新 DOM
每个计算属性都包括一个 getter 和 setter,读取时触发 getter,修改时触发 setter
计算属性与watch区别
Computed watch 区别就是 computed 的缓存功能,当无关数据数
据改变时,不会重新计算,直接使用缓存中的值。计算属性是用来声明
式的描述一个值依赖了其他的值,当所依赖的值后者变量发生变化时,
计算属性也跟着改变,
Watch 监听的是在 data 中定义的变量, 当该变量变化时, 会触发 watch
中的方法
watch怎么深度监听对象变化
new Vue({
el: "#first",
data: { msg: {name: 'zz'} },
watch:{
msg: {
handler(newMsg, oldMsg){
console.log(newMsg);
},
immediate: true,
deep: true
}
}
})
何时需要使用beforeDestory
- 解除自定义事件 event.$off
- 清除定时器
- 解绑自定义的DOM事件,如window.onscroll等
在vue项目中如何引入第三方库(比如jQuery)?有哪些方法可以做到?
1.绝对路径直接引入。
在index.html中用`script`引入
<script src="./static/jquery-1.12.4.js"></script>
然后在webpack中配置external
externals:{'jquery': 'jQuery'}
在组件中使用时import
import $ from 'jquery'
2.在webpack中配置alias
resolve: {
extensions: ['.js','.vue','.json'],
alias: {
'@': resolve('src),
'jquery': resolve('static/jquery-1.12.4.js')
}
}
然后在组件中import
3.在webpack中配置plugins
plugins: [new webpack.ProvidePlugin({ $: 'jquery' })]
全局使用,但在使用eslint情况下会报错,需要在使用了$的代码前添加 /*eslint-disable*/ 来去掉eslint的检查。
vue 高级特性
自定义v-model
谈谈$nextTick的作用
Vue 是异步渲染,data改变之后,DOM不会立刻渲染,$nextTick会在DOM渲染之后被触发,以获取最新的DOM节点。
slot特性
- 基本使用
- 作用域插槽(不常用,但是需要知道)
- 具名插槽
动态、异步组件
用法::is = "component-name"
异步组件: 使用时,再去加载组件 ()=> import(./components/tabs)
keep-alive
- 缓存组件,频繁切换组件,不需要重复渲染。适用于Vue常见性能优化
mixin的优缺点
- 多个组件有相同的逻辑,抽离出来
mixin 并不是完美的解决方案,会有一些问题。vue3 提出的Composition API 旨在解决这些问题。
- 变量来源不明确,不利于阅读
- 多mixin可能造成命名冲突
- mixin和组件可能出现多对多的关系,复杂度较高。
Vue原理
- 面试官为何要考察原理,又用不到?
知其然知其所以然,各行各业通用的道理
了解原理,才能应用的更好(竞争激烈,择优录取)
大厂造轮子(有钱有资源,业务定制,技术KPI)
- 面试中如何考察Vue原理?以何种方式?
考察重点,而不是考察细节。掌握好2/8原则。
和使用相关联的原理,例如:vdom、模板渲染
整体流程是否全面?热门技术是否有深度?
响应式原理(数据驱动视图)
- 核心API,Object.defineProperty(vue3使用Proxy,但是proxy兼容性不好,且无法polyfill)
Object.defineProperty实现响应式
- 监听对象,监听数组
- 复杂对象,深度监听
// 更新视图方法
function updateView(){
console.log('视图更新');
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype;
// 创建新对象,原型指向oldArrayProperty,再扩展新方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push','pop','shift','unshift','splice'].forEach(methodName=>{
arrProto[methodName] = function(){
// 更新视图
updateView()
// 调用原生方法
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments);
}
})
// 重新定义属性,监听起来
function defineReactive(target, key, value){
// 深度监听
observer(value);
// 核心API
Object.defineProperty(target, key, {
get(){
return value
},
set(newValue){
if(newValue !== value){
// 深度监听
observer(newValue)
// 设置新值
// 注意value一直在闭包中,此处设置完之后,再get时获取的是最新的值
value = newValue
// 触发更新视图
updateView()
}
}
})
}
// 监听对象属性
function observer(target){
if(typeof target !== 'object' || target === null){
// 不是对象或数组
return target;
}
// 深度监听数组
if(Array.isArray(target)){
target.__proto__ = arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for(let key in target){
defineReactive(target, key, target[key])
}
}
const data = {
name: 'krik',
age: 18,
info: {
address: '豫'
},
nums: [10,20,30]
}
// 监听数据
observer(data)
// 测试
data.name = 'ime'
data.age = 19
data.x = '100' // 新增属性,监听不到,所以有Vue.set
delete data.name // 删除属性,监听不到,所以有Vue.delete
data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
几个缺点:
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性/删除属性(Vue.set 和 Vue.delete)
- 无法监听数组,需要特殊处理
虚拟DOM和diff算法
虚拟DOM库: (虚拟DOM库)[https://github.com/snabbdom/snabbdom]
- 用JS模拟DOM结构
- 新旧vnode对比,得出最小的更新范围,最后更新DOM
- 数据驱动视图模式下,有效控制DOM操作
diff算法:是vdom中最核心,最关键的部分
diff算法能在日常使用vue react 中提现出来(如key)
diff算法实现代码细节
diff即对比,是一个广泛的概念,如linux diff命令,git diff等
两个js对象也可以做diff, 如https://github.com/cujojs/jiff
两棵树做diff,如这里的vdom diff
- 树diff的时间复杂度 O(n^3)
- 第一,遍历tree1; 第二,遍历tree2,第三,排序
- 问题:1000个节点,要计算1亿次,算法不可用。
前端大佬针对前面的问题,提出了个解决办法:优化时间复杂度到O(n)
- 只比较同一层级,不跨级比较
- tag 不相同,则直接删除重建,不再深度比较
- tag和key两者都相同,则认为是相同节点,不做深度比较
snabbdom 源码解读(细节不重要,了解流程即可):
- patchVnode
- addVnodes 和 removeVnodes
- updateChildren
vdom核心概念很重要:h, vnode, patch, diff, key等
vdom存在的价值:数据驱动视图,控制DOM操作
模板编译
vue的模板不是html,有指令,插值,JS表达式,它到底是什么?
面试官不会直接问,但是会通过"组件渲染和更新过程"考察
前置知识:JS的with语法
// 使用with,能改变{}内自由变量的查找方式
// 将{}内自由变量,当做obj的属性来查找,找不到就会报错
// with 要慎用,它打破了作用域规则,易读性变差
const obj = {a: 1, b: 2}
console.log(obj.c) // undefined
with(obj){
console.log(a)
console.log(b)
console.log(c) // 会报错 !!!
}
vue template complier 将模板编译成render函数,执行render函数生成vnode
基于vnode再执行patch和diff
使用webpack vue-loader, 会在开发环境下编译模板(重要)
描述组件渲染/更新过程
- 响应式原理(数据驱动视图)
- 模板编译:模板到render函数,再到vnode
- vnode diff算法
初次渲染过程=》更新过程=》异步渲染
初次渲染过程:解析模板为render函数(或在开发环境已完成,vue-loader),触发响应式,监听data属性getter和setter
执行render函数生成vnode,patch(elem,vnode)
更新过程:修改data,触发setter(此前getter中已被监听),重新执行render函数生成newVnode,patch(vnode,newValue)
前端路由原理
稍微复杂一点的SPA,都需要路由。vue-loader也是vue全家桶的标配之一。属于"和日常使用相关联的原理",面试常考。
路由模式:hash模式和history模式
// http://127.0.0.1:8081/01-hash.html?a=100&b=200#/aaa
location.protocol // http:
location.hostname // 127.0.0.1
location.host // 127.0.0.1:8081
location.port //8081
location.pathname // /01-hash.html
location.search // ?a=100&b=200
location.hash // #/aaa
hash 变化会触发网页跳转,即浏览器的前进、后退
hash 变化不会刷新页面,SPA必须的特点
hash 永远不会提交到server端,全权由前端去控制
前端实现hash路由
- window.onhashchange
window.onhashchange = (event)=>{
console.log('old url:',event.oldURL);
console.log('new url:',event.newURL);
console.log('hash:', location.hash);
}
document.addEventListener('DOMContentLoaded', ()=>{
console.log('hash:', location.hash);
})
document.getElementById('#change-hash-btn').addEventListener('click', ()=>{
location.href = '#/detail/id'
})
前端实现history路由
- history.pushState
- window.onpopstate
// 页面初次加载,获取path
document.addEventListener('DOMContentLoaded', ()=>{
console.log('load', location.pathname);
})
// 打开一个新的路由
// 【注意】用pushState方式,浏览器不会刷新页面
document.getElementById('change-hash-btn').addEventListener('click', ()=>{
const state = { name: 'page1' }
console.log('切换路由到', 'page1');
history.pushState(state, '', 'page1')
})
// 监听浏览器前进后退
window.onpopstate = (event) => {
console.log('onpopstate', event.state, location.pathname);
}
两者选择
to B 的系统推荐使用hash,简单易用,对URL规范不敏感。
to C 的系统,可以考虑选择H5 history, 但需要服务端支持
能选择简单的,就别用复杂的,要考虑成本和收益。
vue路由篇
vue路由的实现
前端路由就是更新视图,但不请求页面。
利用锚点完成切换,页面不会刷新,官网推荐用vue-router.js来引入路由模块
定义路由,使用component进行路由映射组件,用name导航到对应路由。
创建router实例,传入routes
配置,创建和挂载根实例,用router-link
设置路由跳转。
vue中路由跳转方式(声明式/编程式)
Vue 中路由跳转有两种,分别是声明式和编程式
用 js 方式进行跳转的叫编程式导航 this.$router.push()方法
用 router-link 进行跳转的叫声明式 router-view 路由出口,路由模板显示的位置
路由中 name 属性有什么作用?
在 router-link 中使用 name 导航到对应路由
使用 name 导航的同时,给子路由传递参数
Route和router的区别
- router是
VueRouter
的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性。 - route是个跳转的路由对象,每个路由都会有一个route对象,是一个局部对象,可以获取对应的name,path,params,query等
说说vue路由传参的两种方式(params和query)区别
动态路由也可以叫路由传参,就是根据不同的选择在同一个组件渲染不同的内容。
用法上:query用path引入,params用name引入,接收参数都是类似的,分别是this. r o u t e . q u e r y . n a m e 和 t h i s . route.query.name和this. route.query.name和this.route.params.name
url展示上:params类似于post,query类似于get,也就是安全问题,params传值相对更安全点,query通过url传参,刷新页面还在,params刷新页面就不在了。
vue的路由钩子函数/路由守卫有哪些?
- 全局守卫:beforeEach(to,from,next)和afterEach(to, from)
- 路由独享守卫:beforeEnter
- 组件内的守卫:路由进入/更新/离开之前 =》beforeRouterEnter/update/leave
对vue中keep-alive的理解
概念:keep-alive 是 vue的内置组件,当它动态包裹组件时,会缓存不活动的组件实例,它自身不会渲染成一个DOM元素也不会出现在父组件链中
作用:在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验。
生命周期函数:Activated在keep-alive组件激活时调用,deactivated在组件keep-alive组件停用时调用。
vuex 状态管理篇
vuex是什么?怎么使用?在那种场景下使用。
Vuex是一个专为vue.js应用程序开发的状态管理模式,通过创建一个集中的数据存储,方便程序中的所有组件进行访问,简单来说,vuex是vue的状态管理工具。
vuex有五个属性 state
,getters
,mutations
,actions
,modules
state
就是数据源存放地,对应一般的vue对象的data
,state
里面存放的数据是响应式的,state
数据发生改变,对应这个数据的组件也会发生改变,用this.$store.state.xxx调用。
getters
相当于store
的计算属性,主要是对state
中数据的过滤,用this.$store.getters.xxx调用。
mutations
处理数据逻辑的方法全部放在mutations
中,当触发事件想改变state
数据的时候使用mutations
,用this.$store.commit调用。给这个方法添加一个参数,就是mutation
的载荷(payload)
actions
异步操作数据,但是是通过mutation
来操作,用this.$store.dispatch来触发,actions
也支持载荷
使用场景:组件之间的状态,登录状态,加入购物车,音乐播放
Vuex引入流程:下载vuex
在src下创建 store 以及 index.js
引入 vue 和 vuex, 使用 vuex ,导出实例对象
在 main.js 中引入,在.vue 文件中使用
vuex 使用流程:
在 vue 组件里面,通过 dispatch 来触发 actions 提交修改数据的操作,
然后通过 actions 的 commit 触发 mutations 来修改数据,mutations
接收到 commit 的请求,就会自动通过 mutate 来修改 state,最后由
store 触发每一个调用它的组件的更新
vuex怎么请求异步数据
- 首先在
state
中创建变量 - 然后在
action
中调用封装好的axios
请求,异步接受数据,commit
提交给mutations
来改变state的状态,将从action中获取的值赋值给state
vuex中 action
如何提交给 mutation
的
action
函数接收一个与store
实例具有相同方法和属性的context
对象
可以调用context.commit 提交一个mutation
或者通过context.state和context.getters获取state和getters
vuex的state特性是?
state就是数据源的存放地,state里面的数据是响应式的,state中的数据改变,对应这个数据的组件也会发生改变。
state通过mapstate把全局的state和getters映射到当前组件的计算属性中
vuex的 Getter 特性是?
Getter可以对state进行计算操作,它就是store的计算属性。
Getter可以在多个组件之间复用,如果一个状态只在一个组件内使用,可以不用getters
vuex的 Mutation 特性是?
更改vuex store中修改状态的唯一办法就是提交mutation,可以在回调函数中修改store中的状态
vuex的actions特性是?
Action类似于mutation,不同的是action提交的是mutation,不是直接变更状态,可以包含任意异步操作。
vuex的优势
优点: 解决了非父子组件的通信,减少了ajax请求次数,有些可以直接从state中获取
缺点:刷新浏览器,vuex中的state会重新变为初始状态,解决办法是使用vuex插件vuex-persistedstate(数据持久化)
vue3.x版本
说说你对proxy的理解
vue的数据劫持有两个缺点:
- 无法监听通过索引修改数组的值的变化
- 无法监听对象的值的变化,所以vue2.x中才会有$set属性的存在
proxy是es6中推出的新的api,可以弥补以上两个缺点,所以vue3.x版本用proxy替换object.defineproperty
vue3.0 如何变得更快的?(底层,源码)
- diff方法优化
- vue2.x中的虚拟dom进行全量的对比,而在vue3.0中新增了静态标记(patchFlag):在与上次虚拟结点进行对比的时候,值对比带有patchflag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容化,hoistStatic 静态提升
- vue2.x:无论元素是否参与更新,每次都会重新创建
- vue3.x: 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。
- cacheHandlers 事件监听器缓存
默认情况下onclick会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。