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
) 添加实例化的watcher
watcher
的实例自身有个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
是model
view
viewModel
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
是model
view
controller
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
切换时的actived
deactivate
被执行
<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
树中添加或者删除dom
v-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
不缓存获取组件实例
key
key
生成规则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
形式的封装和重用model
modelView
view
层分离 是开发者只需要操作数据就能完成操作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
会收集相应的watcher
v-for
里面不能嵌套v-if
(因为v-for
优先级比v-if
高,所以哪怕渲染一小部分的用户的元素,也得在每次重渲染的时候遍历整个列表)spa
页面采用keep-alive
缓存组件key
保证唯一使用路由懒加载 异步组件
防抖节流
第三方模块按需导入
懒加载
SEO
优化
- 预渲染
ssr
打包优化
压缩
uglify
代码tree shaking
/scope Hoisting
cdn
加载第三方模块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
- 策略模式
- 外观模式