Vue基础
# Vue基础
# 介绍一下Vue原理特别重要
创建实例 -> 遍历data的属性 -> 用(object.defineProperty/proxy)转化为getter/setter)-> 在实例中的watcher收集data的属性 -> 当data的属性改变触发setter的时候通知watcher -> watch会将关联的组件都更新一遍

# 介绍一下Vue双向绑定的原理特别重要
采用了数据劫持和发布订阅的方式
数据劫持的笼统意思
observe的数据对象进行递归遍历,然后添加上getter和setter,当值改变的时候会触发setter,就能监听到数据的变化了
发布订阅的意思
compile解析模板数据,然后将模板上面的变量替换成数据,然后初始化渲染视图,将每个指令对应的节点绑定更新函数 添加监听数据的订阅者 ,数据变动 -> 收到通知 -> 更新视图
而watcher订阅者就是连接observe和compile的桥梁
在属性订阅器中(
dep) 添加实例化的watcherwatcher的实例自身有个update方法属性变动的时候调用
dep.notify()调用节点绑定的更新函数
update()触发
compile绑定的函数

总结:
Vue- 记录传入的选项,设置
$data/$el - 把
data的成员注入到Vue实例 - 负责调用
Observer实现数据响应式处理(数据劫持) - 负责调用
Compiler编译指令/插值表达式等
- 记录传入的选项,设置
Observer- 数据劫持
- 负责把
data中的成员转换成getter/setter - 负责把多层属性转换成
getter/setter - 如果给属性赋值为新对象,把新对象的成员设置为
getter/setter
- 负责把
- 添加
Dep和Watcher的依赖关系 - 数据变化发送通知
- 数据劫持
Compiler- 负责编译模板,解析指令/插值表达式
- 负责页面的首次渲染过程
- 当数据变化后重新渲染
Dep- 收集依赖,添加订阅者(
watcher) - 通知所有订阅者
- 收集依赖,添加订阅者(
Watcher- 自身实例化的时候往
dep对象中添加自己 - 当数据变化
dep通知所有的Watcher实例更新视图
- 自身实例化的时候往
# 介绍一下什么是MVVM、MVC、MVP 特别重要
mvvm是modelviewviewModel
model层代表数据模型 数据和业务逻辑都在model层view代表ui视图 负责数据的展示viewModel监听model数据的改变并控制视图更新,处理交互
model和view无直接连接 而是通过viewModel进行联系,model和viewModel数据双向绑定,view层交互改变的数据 model层也会改变,model层数据改变也会触发view层的更新,这样程序猿们就可以专心在viewModel层编写业务逻辑,不用关心数据的传递
但是Vue 并没有完全遵循 MVVM 思想呢?
- 严格的
MVVM要求View不能和Model直接通信,而Vue提供了$refs这个属性,让Model可以直接操作View,违反了这一规定,所以说Vue没有完全遵循MVVM。
mvc是modelviewcontroller
view代表ui视图model储存页面的数据controller层负责用户与应用的响应操作
view和model层应用了观察者模式,model层数据变化会通知view进行更新,用户操作 controller触发,调用model修改 然后再通知view进行更新
mvp跟mvc大致相同
mvc中的model和view层都耦合在一起,当逻辑复杂时候会造成代码混乱,通过mvp的presenter层,将model和view绑定在一起,实现同步更新,实现对view和model层的解耦
# 说一下computed和watch的区别特别重要
computed计算属性:依赖其他属性值,不支持异步,并且有缓存,只有当依赖的属性值发生改变且有属性值引用它时才会重新计算computed的值(例如购物车的价格就可以用computed)
watch监听器:只是充当观察作用,支持异步,无缓存,类似某种数据监听回调,当监听的数据变化执行回调进行后续操作(适用于当数据变化需要执行异步方法或开销比较大的操作)
# 说一下slot都有哪些用法 原理是什么
默认插槽:没有指定name的时候 会指定默认插槽,一个组件只能有一个
具名插槽:指定了name属性的slot,一个组件可以有多个具名插槽
作用域插槽:可以是匿名插槽也可以是作用域插槽,可以将子组件内部的数据传递给父组件
原理:当子组件实例化时,获取父组件传入
slot标签的内容,存在vm.$slot,默认插槽为vm.$slot.default具名插槽为vm.$slot.xxx,当组件执行渲染函数的时候,遇到slot,使用$slot内容替换,此时可以为插槽传递数据,则叫作用域插槽
# 介绍一下保存页面状态的方法
前组件会被卸载
将当前组件的
state通过JSON.stringify()储存在LocalStorage/SessionStorage优点:兼容性好,不需要额外使用库和工具
缺点:相当于
JSON.stringify()的缺点(Date对象Reg对象会出错) 组件回退或者前进会出bug
通过
react-router的Link组件的prop —— to可以实现路由间传递参数 通过history.location.state拿到状态进行传参优点 简单快捷 不污染
LocalStorage/SessionStorage解决JSON.stringify()的缺点缺点 当前组件可以跳转至多个组件 在跳转组件要多次写相同逻辑
前组件不会被卸载
单页面渲染 要写换的组件作为子组件全屏渲染
优点 代码少 状态传递不出错
缺点 需要传递额外
prop到子组件 无法利用路由定位
利用
keep-alive切换时的activeddeactivate被执行
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</kepp-alive>
// router.js
{
path: '/',
name: 'xxx',
component: ()=>import('../src/views/xxx.vue'),
meta:{
keepAlive: true // 需要被缓存
}
},
2
3
4
5
6
7
8
9
10
11
12
13
- 缺点很明显,当页面刷新的时候
Vue中的数据会丢失
# v-if 和 v-show的区别特别重要
- 手段:
v-if是动态向dom树中添加或者删除domv-show是设置display来隐藏 - 编译过程:
v-if有一个局部编译和卸载的过程,切换过程合适地销毁和重建内部的事件监听和子组件v-show只是基于css切换 - 编译条件:
v-if是惰性的 初始条件为假则什么都不做,只有第一次条件为真的时候才局部编译v-show一开始就编译然后被缓存 - 性能消耗:
v-if更高的切换消耗v-show有更高的初始渲染消耗 - 使用场景:
v-if适合运营条件不太改变的场景v-show适合频繁切换
# v-if原理和v-show原理
v-show
不管初始条件是什么,元素总是会被渲染
我们看一下在vue中是如何实现的
代码很好理解,有transition就执行transition,没有就直接设置display属性
// https://github.com/vuejs/vue-next/blob/3cd30c5245da0733f9eb6f29d220f39c46518162/packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {
beforeMount(el, { value }, { transition }) {
el._vod = el.style.display === 'none' ? '' : el.style.display
if (transition && value) {
transition.beforeEnter(el)
} else {
setDisplay(el, value)
}
},
mounted(el, { value }, { transition }) {
if (transition && value) {
transition.enter(el)
}
},
updated(el, { value, oldValue }, { transition }) {
// ...
},
beforeUnmount(el, { value }) {
setDisplay(el, value)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
v-if
v-if在实现上比v-show要复杂的多,因为还有else else-if 等条件需要处理,这里我们也只摘抄源码中处理 v-if 的一小部分
返回一个node节点,render函数通过表达式的值来决定是否生成DOM
// https://github.com/vuejs/vue-next/blob/cdc9f336fd/packages/compiler-core/src/transforms/vIf.ts
export const transformIf = createStructuralDirectiveTransform(
/^(if|else|else-if)$/,
(node, dir, context) => {
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
// ...
return () => {
if (isRoot) {
ifNode.codegenNode = createCodegenNodeForBranch(
branch,
key,
context
) as IfConditionalExpression
} else {
// attach this branch's codegen node to the v-if root.
const parentCondition = getParentCondition(ifNode.codegenNode!)
parentCondition.alternate = createCodegenNodeForBranch(
branch,
key + ifNode.branches.length - 1,
context
)
}
}
})
}
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# data为什么是一个函数而不是对象?特别重要
如果data是对象时,当组件多次被实例化时,引用的是同一个data对象,会导致不同实例操作数据,会相互影响,所以要写成函数形式,数据以返回值的形式定义,每次复用组件时,会返回新的data,每个组件都有自己私有数据空间,各自维护数据,不干扰其他组件正常运行
# 为什么访问data属性不需要带data?
vue中访问属性代理this.data.xxx转换this.xxx的实现
/** 将 某一个对象的属性 访问 映射到 对象的某一个属性成员上 */
function proxy( target, prop, key ) {
Object.defineProperty( target, key, {
enumerable: true,
configurable: true,
get () {
return target[ prop ][ key ];
},
set ( newVal ) {
target[ prop ][ key ] = newVal;
}
} );
}
2
3
4
5
6
7
8
9
10
11
12
13
# v-model的原理重要
我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text和textarea元素使用value属性和input事件;checkbox和radio使用checked属性和change事件;select字段将value作为prop并将change作为事件。
以 input 表单元素为例:
<input v-model='something'>
// 相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
2
3
4
5
# 对keep-alive的理解 如何实现 具体是如何缓存的?
理解
当组件需要切换是,保存一些组件 防止多次渲染,用keep-alive包裹
有三个属性:
include字符串或正则表达式 只有名称匹配的组件会被匹配exclude字符串或正则表达式,任何名称匹配的组件都不会被缓存max数字 最多可以缓存多少组件实例
流程:
判断组件的
name不在include或在exclude中,直接返回vNode不缓存获取组件实例
keykey生成规则cid + "::" tag仅靠cid不够,因为相同构造函数都可以注册为不同本地组件
如果组件已缓存 就从缓存对象中获取组件实例给
vNode,不存在则添加进缓存对象里面超过最大缓存数量,就用
LRU清除数组内第一个组件
keep-alive中的LRU算法(least recently used)
通过cache数组存放所有组件的vNode 当cache中原有的组件被使用的时候,会将该组件的key从keys数组中删去,然后push到keys数组最后,以便超出max后删除最不常用的组件
# $nextTick原理特别重要
原理
利用JS原生的Promise MutationObserver setImmediate setTimeout的原生JS模拟对应的微/宏任务,为了利用js这种异步回调任务队列来实现Vue中自己的异步回调队列
Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中1次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用
执行时机:
在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM
引入原因:
- 如果是同步更新 那么会频繁触发
UI/DOM的渲染,可以减少无用渲染 - 由于
VirtualDOM的引入 每一次状态改变 信号都会发给DOM进行更新 每次更新状态都会需要更多的计算 这种无用功会浪费更多性能 Vue采用MVVM,也就是开发者只需modelView层进行业务逻辑,利用数据驱动视图的方式,但是避免不了还是要操作dom,有时候DOM1数据发生变化,此时DOM2需要从DOM1中获取数据,却发现DOM2视图并没有更新,这时候就需要用到nextTick- 还有的情况是在
Vue生命周期里面,created钩子进行DOM的操作要放在nextTick中,因为执行created钩子时,DOM还未渲染 无法操作DOM
# Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?
不会立即同步执行渲染 而是按照一定的策略进行DOM异步更新,当侦听到数据变化的时候,Vue会开启一个队列,并缓冲同一事件循环中发生的所有数据变更,如果同一个watcher被多次触发,只会被推入队列中一次,这时候去除重复数据对避免不必要的计算和DOM操作,在下一个时间循环Tick中刷新队列并执行(已去重)的工作
# Vue模板渲染的原理是什么?重要
vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。
模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。
parse阶段:使用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为抽象语法树AST。optimize阶段:遍历AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能。generate阶段:将最终的AST转化为render函数字符串。
# 子组件可以直接改变父组件的数据吗
子组件不可以改变父组件的数据
主要是为了维护父子组件的单向数据流,为了意外的改变父组件的状态,使得应用的数据流变得难得理解,只能通过$emit派发一个自定义事件,父组件接收到后,由父组件修改
# Vue和React的理解和异同特别重要
相似:
都将路由和全局状态管理交给相关库
都用了
Virtual Dom提高重绘性能都有
props允许组件数据传递都提倡组件化
不同:
数据流:
Vue默认支持数据双向绑定,React提倡单向数据流Virtual DOM:Vue会跟踪每一个组件的依赖关系,不需要重新渲染整棵树 而React每当应用改变 全部子组件都会重新渲染,可以通过PureComponent/shouldComponentUpdate来控制模板编写:
Vue鼓励常规的HTML模板编写React推荐用JS的语法扩展 --JSX监听数据变化的原理:
Vue通过getter/setter以及函数劫持,精确知道数据的变化React通过比较引用的方式进行的,不优化的话会导致大量不必要的vDOM重新渲染高阶组件:
Vue通过mixins扩展React通过HOC来扩展
# Vue的优点特别重要
轻量级框架:只关注
modelView层双向数据绑定: 在数据操作方面更为简单
组件化:实现了
html形式的封装和重用modelmodelViewview层分离 是开发者只需要操作数据就能完成操作virtualDOM不用原生的DOM操作节点
# assets和static的区别
相同点:都用来存放静态资源
不同点:
static里面的资源文件不经过压缩直接进入打包的目录,而asset里面的文件则经过压缩同static一起上传到服务器上,所以static里面的文件相对assets大一些,我们可以将项目中的js文件放在assets中,项目的iconfont等第三方资源文件放在static中,因为他们已经被处理过了
# Vue如何监听对象和数组的某个属性改变重要
this.$set(target, key, value)splice()、push()、pop()、shift()、unshift()、sort()、reverse()
Vue.$set原理
如果是修改数组索引,直接用数组的
splice方法触发如果是对象,判断属性是否存在 是否为响应式 ,通过调用
defineReactive(就是Vue初始化对象,给对象采用(Object.defineProperty动态添加getter/setter)
# 对ssr的理解重要
ssr是服务端渲染,也就是Vue在客户端将标签渲染成HTML放在服务端,服务端直接返回给客户端
ssr优势:更好的SEO、首屏速度变快
ssr缺点:服务端渲染只支持beforeCreate和Created两个钩子 更多的服务端负载
# Vue的性能优化特别重要
编码阶段
减少
data中的数据,因为data的数据会增加getter和setter会收集相应的watcherv-for里面不能嵌套v-if(因为v-for优先级比v-if高,所以哪怕渲染一小部分的用户的元素,也得在每次重渲染的时候遍历整个列表)spa页面采用keep-alive缓存组件key保证唯一使用路由懒加载 异步组件
防抖节流
第三方模块按需导入
懒加载
SEO优化
- 预渲染
ssr
打包优化
压缩
uglify代码tree shaking/scope Hoistingcdn加载第三方模块splitChunk抽离公共文件sourceMap优化
用户体验
骨架屏
PWA服务端开启
gzip压缩
# 对SPA的理解 优缺点是什么
概念
SPA是single-page application是仅在web应用初始化的时候加载相应的html css和js ,一旦页面加载完成SPA不会因为用户的操作而进行页面的重新加载或跳转,取而代之的是html内容的变换,ui和用户的交互
优点:
- 用户体验好 内容改变不会重新加载页面,避免了不必要的跳转和重复渲染 对服务器压力小
缺点:
初次加载耗时长,需要在加载页面的时候将
js和css统一加载 部分页面按需加载前进后退路由管理:不能使用浏览器的前进后退功能
SEO难度大,所有内容都在一个页面中动态替换
# template和jsx区别
对于runtime来说只要组件存在render函数即可
vue中利用vue-loader编译.vue文件 内部依赖的vue-template-compiler将template预编译成render函数react利用babel-plugin-transform-vue-jsx这个jsx语法糖解析器就可以了
所以两者都是render 的表现形式 但是jsx更具有灵活性在复杂组件中有优势 而template在代码结构上符合视图和逻辑分离,更简单 更好维护
# Vue初始化页面为什么会闪动
在vue初始化之前,由于div是不归vue管的 所以在还没解析的情况可能会出现{{message}}的情况
解决方法
在根元素标签上加上
v-cloak,添加css样式[v-cloak] {display: none;}在根元素加上
style="display: none;" :style="{display: 'block'}"
# mixin和mixins区别重要
mixin用于全局混入,会影响到每个组件实例
Vue.mixin({
beforeCreate() {
// ...逻辑
// 这种方式会影响到每个组件的 beforeCreate 钩子函数
}})
2
3
4
5
可以全局混入封装好的axios等
mixins用于扩展组件的方式,如果多个组件有相同业务逻辑,可以将逻辑剥离开,通过mixins混入代码,比如上拉下拉加载数据等,另外mixins混入函数会先于组件的钩子函数,当重名的时候,组件的同名函数会覆盖mixins函数
# MVVM优缺点特别重要
优点:
低耦合。视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;可重用性。你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑。独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计
缺点:
bug难以调试,使用双向绑定 当页面异常是,可能是view层也可能是model层的代码问题,另外数据绑定的声明是写在view的模板中,没办法断点debug一个大模块的
model层也很大,当长期持有,不释放内存会花费更多内存对于大型的图形应用程序,视图较多,
viewmodel的构建和维护成本较高
# vue中使用了哪些设计模式?特别重要
- 单例模式:
new多次,只有一个实例
- 工厂模式:传入参数就可以创建实例(虚拟节点的创建)
- 发布订阅模式:
eventBus - 观察者模式:
watch和dep - 代理模式:
_data属性、proxy、防抖、节流 - 中介者模式:
vuex - 策略模式
- 外观模式