vue2x面试前回炉重造
# vue2.x面试前回炉重造
[TOC]
# 一、Vue基本使用
# 1.插值
- 文本
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
<span>Message: {{ msg }}</span>
- HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
- Attribute属性
<div v-bind:id="dynamicId"></div>
- JavaScript表达式
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
2
3
4
5
6
7
只能是单个表达式,不可以是控制流
# 2.指令
v-bind、v-if 、v-on 等是指令
指令 (Directives) 是带有 v- 前缀的特殊 attribute。
v-bind 缩写 : v-on 缩写 @
# 3.computed计算属性
var vm = new Vue({
data: { a: 1 },
computed: {
// 仅读取
aDouble: function () {
return this.a * 2
},
// 读取和设置
aPlus: {
get: function () {
return this.a + 1
},
set: function (v) {
this.a = v - 1
}
}
}
})
vm.aPlus // => 2
vm.aPlus = 3
vm.a // => 2
vm.aDouble // => 4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
computed有缓存,data不变则不会重新计算
# 计算属性VS方法
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 data 还没有发生改变,多次访问 计算属性 会立即返回之前的计算结果,而不必再次执行函数。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
# 4.watch侦听器
# 基础用法:
<template>
<div>
<input v-model="name">
</div>
</template>
<script>
export default {
name: 'WatchDemo',
data () {
return {
name: '王子萁'
}
},
watch: {
name (oldVal, val) {
console.log('watch name', oldVal, val) // 值类型,可正常拿到
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 深度监听:
当需要监听数组内元素/对象属性变化时,基础用法中侦听将不会被触发,需要使用深度监听
<template>
<div>
<input v-model="info.city">
</div>
</template>
<script>
export default {
name: 'WatchDemo',
data () {
return {
info: {
city: '北京'
}
}
},
watch: {
info: {
// 虽然可以监听到变化,但是无法取到变化前的值,因为其实引用类型,指针相同
handler (oldVal, val) {
console.log('watch info', oldVal, val) // 引用类型,拿不到oldVal。因为指针相同
},
deep: true // 深度监听
}
}
}
</script>
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
27
注意:虽然可以监听到变化,但是无法获取变化前的值 因为其为引用类型,指针相同
# immediate(立即调用)
immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。
cityName: {
handler(newName, oldName) {
// ...
},
immediate: true
}
2
3
4
5
6
# 优化对象属性监听
设置deep: true 则可以监听到cityName.name的变化,此时会给cityName的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值,则可以做以下优化:使用字符串的形式监听对象属性:
这样只会给对象的某个特定的属性加监听器。
watch: {
'cityName.name': {
handler(newName, oldName) {
// ...
},
deep: true,
immediate: true
}
}
2
3
4
5
6
7
8
9
# 侦听器 VS 计算属性
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
# 5. Class与Style
# 绑定HTML Class
# 对象语法
// 单个类
<div v-bind:class="{ active: isActive }"></div>
// 多个类
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
2
3
4
5
6
7
上面的语法表示 active 这个 class 存在与否将取决于数据 property isActive 的 ‘真假’。
注意:avtive是字符串,css类名,非变量名
绑定的数据对象不必内联定义在模版里
<div v-bind:class="classObject"></div>
data: {
classObject: {
active: true,
'text-danger': false
}
}
2
3
4
5
6
# 数组语法
<div v-bind:class="[activeClass, errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
2
3
4
渲染为
<div class="active text-danger"></div>
# 绑定内联样式
# 对象语法
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
2
3
4
直接绑定
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
2
3
4
5
6
注意:对象直接绑定时,数据属性名采用驼峰命名,非'-'连接
# 数组语法
<div v-bind:style="[baseStyles, overridingStyles]"></div>
# 6. 条件渲染
# v-if 和 v-show 的区别
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
区别与何时使用
区别 | v-if | v-show |
---|---|---|
原理 | 是否销毁、重建 | 是否展示(display) |
渲染 | 开始如果未false不渲染,首次ture才渲染 | 无论如何都渲染 |
使用 | 条件很少改变 | 需要非常频繁地切换 |
# 7. 列表渲染
# 基本使用(数组)
<ul id="example-1">
<li v-for="(item,index) in items" :key="item.message">
{{ item.message }} - {{index}}
</li>
</ul>
2
3
4
5
# 对象
<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>
2
3
# v-for 与 v-if 一同使用
当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。
如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素(或
<template>
上。如:
<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>
2
3
4
5
6
注意:就算是只想部分项渲染节点时,官方也不推荐一起使用。
注意:key值一定要写,并且不能乱写(random或者index),推荐设定与业务相关的内容。
# 事件处理
# 事件修饰符
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
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
27
# 按键修饰符
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
<!-- 可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符 -->
<input v-on:keyup.page-down="onPageDown">
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 系统修饰符
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>
2
3
4
5
# .exact 修饰符
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
2
3
4
5
6
7
8
# 系统修饰符
.left
.right
.middle
2
3
# 表单输入绑定
# 修饰符
# .lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">
2
# .number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。
# .trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg">
# 二、Vue组件的使用
# 1. props和$emit(组件传参)
父组件通过props传递数据给子组件,子组件通过$emit给父组件触发一个事件
# props
字符串数组形式
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
指定类型形式
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
2
3
4
5
6
7
8
9
设置默认值形式
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
2
3
4
5
6
7
8
9
10
11
12
13
传入静态值和动态值
<!--静态-->
<blog-post title="My journey with Vue"></blog-post>
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
2
3
4
# 单项数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
# $emit
子组件
<input v-model="message" @change="handleInputChange"/>
/**
* 检测到输入
*/
handleInputChange () {
this.$emit('changeInput', this.message)
}
2
3
4
5
6
父组件
<InputDemo @change-input='handleChangeInput' />
handleChangeInput (message) {
// do something
console.log(message)
}
2
3
4
# 2.组件间通讯
组件间通讯:父子组件使用props和$emit 兄弟或无关组件:使用自定义事件进行通讯
# 3.自定义事件
(1)创建event.js
import Vue from 'vue'
export default new Vue()
2
(2) 组件间引入并使用event进行通讯 本触发组件
<input v-model="message" @change="handleInputChange"/>
// 引入自定义事件
import event from '@/components/ComponentsDemo/event'
/**
* 检测到输入
*/
handleInputChange () {
event.$emit('changeInput', this.message)
}
2
3
4
5
6
7
8
注意
beforeDestroy () {
// 要及时销毁,否则可能造成内存泄露
event.$off('changeInput', this.handleInputChange)
}
2
3
4
# 4.生命周期(单组件)
阶段 | 名称 | 备注 | 常见操作 |
---|---|---|---|
挂载阶段 | beforeCreate | 准备初始化实例 | |
挂载阶段 | created | 已完成初始化实例 还未进行渲染 | 带异步数据请求的方法可以放在这里 |
挂载阶段 | beforeMount | 准备进行渲染 | |
挂载阶段 | mounted | 已完成渲染 | 需要操作dom的方法放这里 |
更新阶段 | beforeUpdate | 准备进行更新渲染 | |
更新阶段 | updated | 已完成更新渲染 | 任何数据的更新,如果要做统一的业务逻辑处理 |
销毁阶段 | beforeDestroy | 准备进行销毁实例 | 销毁自定义绑定事件 销毁定时任务setTimeOut 销毁window监听事件 |
销毁阶段 | destroyed | 已完成销毁实例 |
# 5.生命周期(父子组件)
# 挂载阶段
创建实例:从外到内 渲染是:从内到外
序号 | 组件 | 名称 |
---|---|---|
1 | 父组件 | beforeCreate |
2 | 父组件 | created |
3 | 父组件 | beforeMount |
4 | 子组件 | beforeCreate |
5 | 子组件 | created |
6 | 子组件 | beforeMount |
7 | 子组件 | mounted |
8 | 父组件 | mounted |
# 更新阶段
开始更新:从外到内 完成更新:从内到外
序号 | 组件 | 名称 |
---|---|---|
1 | 父组件 | beforeUpdate |
2 | 子组件 | beforeUpdate |
3 | 子组件 | updated |
4 | 父组件 | updated |
# 销毁阶段
同更新 开始销毁:从外到内 完成销毁:从内到外
序号 | 组件 | 名称 |
---|---|---|
1 | 父组件 | beforeDestroy |
2 | 子组件 | beforeDestroy |
3 | 子组件 | destroyed |
4 | 父组件 | destroyed |
# 三、Vue高级特性
# 1. 自定义v-model
子组件
<input type='text' :value="text" @input="$emit('change',$event.target.value)"/>
<!--
1. 上面的input 使用了 :value 而不是 v-model
2. 上面的change 和model.event 要对应起来
3. text 属性名称对应起来
-->
<!--
总结:
组件内要设置用于双向绑定的props参数
组件内model属性要设置绑定参数的名称和触发事件
绑定元素中的事件触发内容
-->
2
3
4
5
6
7
8
9
10
11
12
export default {
name: 'CustomVModel',
model: {
prop: 'text', // 对应 props text
event: 'change'
},
props: {
text: {
type: String,
default () {
return ''
}
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. $nextTick
<ul ref="ul1">
<li v-for="(item,index) in list" :key="index">
{{ item }}
</li>
</ul>
<button @click="addItem">添加一项</button>
2
3
4
5
6
export default {
name: 'NextTick',
data () {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem () {
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
this.list.push(`${Date.now()}`)
// nextTick主要是用于需要获取DOM元素的情况。需要在渲染后获取新的Dom
// 因为在Data改变后,Dom不会立刻渲染。虚拟DOM的概念
// 1. 异步渲染, $nextTick 待DOM 渲染完再回调
// 2. 页面渲染时会将data的修改做整合,多次data修改只会渲染一次
// ref 主要用于寻找DOM节点
this.$nextTick(function () {
// 获取DOM元素
const ulElem = this.$refs.ul1
console.log(ulElem.childNodes.length)
})
}
}
}
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
27
28
# 3. slot(插槽)
# 作用域插槽
子组件
<template>
<a :href="url">
<!-- 插槽要把什么数据外放,使用slotData-->
<slot name="subTitle" :slotData="website">
{{ website.subTitle }}
</slot>
</a>
</template>
2
3
4
5
6
7
8
export default {
name: 'scopedSlotDemo',
props: ['url'],
data () {
return {
website: {
url: 'http://wangEditor.com/',
title: 'wangEditor',
subTitle: '轻量级富文本编辑器'
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
父组件
<scopedSlotDemo :url="website.url">
<!-- v-slot 和slot-scope一样,都是用于从组建中取出特定数据-->
<template slot="subTitle" slot-scope="slotProps">
{{ slotProps.slotData.title }}
</template>
</scopedSlotDemo>
2
3
4
5
6
:slotData='内部数据名'
v-slot='外部变量名' 使用{{外部变量名.slotData.item}}
2
# 具名插槽
子组件
<template>
<a :href="url">
<slot name="label">
默认标签
</slot>
<slot name="name">
默认名称
</slot>
</a>
</template>
2
3
4
5
6
7
8
9
10
父组件
<scopedSlotDemo>
<template slot="name">
名字是什么
</template>
<template v-slot:label>
something
</template>
</scopedSlotDemo>
2
3
4
5
6
7
8
# 4. 动态、异步组件
# 动态组件
- :is= "component-name"用法
- 需要根据数据,动态渲染的场景。即组件类型不确定。
# 异步组件
- import()函数
- 按需加载,异步加载大组件
components: {
FormDemo: () => import('@/components/BaseUse/FormDemo')
},
2
3
# 5. keep-alive(缓存组件)
何时使用:频繁切换,不需要重复渲染 Vue常见性能优化
<template>
<div>
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<keep-alive> <!--tab 切换-->
<KeepAliveStageA v-if="state === 'A'"/>
<KeepAliveStageB v-if="state === 'B'"/>
<KeepAliveStageC v-if="state === 'C'"/>
</keep-alive>
</div>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
<script>
import KeepAliveStageA from '@/components/AdvancedUse/KeepAliveStageA'
import KeepAliveStageB from '@/components/AdvancedUse/KeepAliveStageB'
import KeepAliveStageC from '@/components/AdvancedUse/KeepAliveStageC'
export default {
components: {
KeepAliveStageA,
KeepAliveStageB,
KeepAliveStageC
},
data () {
return {
state: 'A'
}
},
methods: {
changeState (state) {
this.state = state
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
需要渲染时才进行渲染,当前组件不展示时不进行销毁。单纯v-if不展示时会进行销毁,增加渲染负担。
# keep-alive 与 v-show的对比
原理不同,keep-alive是view层级的是否加载渲染。v-show是css层级的是否展示。 在**复杂场景**下使用keep-alive进行渲染优化。
# 6. mixin(混合)
- 多个组件有*相同的逻辑,抽离出来*
- mixin并*不是完美的解决方案*,会有一些问题
- Vue3 提出的 *Composition API*旨在解决这些问题
mixin.js(可复用的逻辑)
export default {
data () {
return {
city: '青岛'
}
},
methods: {
showName () {
console.log(this.name)
}
},
mounted () {
console.log('mixin mounted', this.name)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用组件
<template>
<div>
<p>{{ name }} {{ major }} {{ city }}</p>
<button @click="showName">显示姓名</button>
</div>
</template>
2
3
4
5
6
// 引入混合逻辑
import myMixin from '@/components/AdvancedUse/mixin'
export default {
// 加载,可加载多个
mixins: [myMixin], // 可以添加多个,会自动合并起来
name: 'MixinDemo',
data () {
return {
name: '王子萁',
major: 'web 前端'
}
},
methods: {},
mounted () {
console.log('component mounted', this.name)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# mixin带来的问题
- 变量来源不明确,不利于阅读
- 多 mixin 可能会造成*命名冲突*
- mixin和组件可能出现*多对多的关系,复杂度较高*
# 7.总结
- 可以不太深入,但必须知道
- 熟悉基本用法,了解使用场景
- 最好能和自己的项目经验结合起来
# 四、Vuex使用(状态管理模式)
# 1. Vuex基本概念
- state(单一状态树)
- getters(认为是 store 的计算属性)
- action(执行异步操作)
- mutation(改变state)
# 2.用于Vue组件
- dispatch(分发Actions)
- commit(提交Mutations)
- mapState
- mapGetters
- mapActions
- mapMutations
action里面才能进行异步操作(ajax请求)
流程:
编号 | 模块 | 操作 | 指向 |
---|---|---|---|
1 | view | dispatch(分发) | action |
2 | action | commit(提交) | mutation |
3 | mutation | mutate(改变) | state |
4 | state | render(渲染) | view |
# 五、Vue-router使用
# 1. 路由模式(hash,H5 history)
- hash模式(默认),如https://abc.com/#/user/10
- H5 history模式, 如https://abc.com/user/20
区别是是否有#
H5 history模式需要server端支持,因此无特殊需求可选择hash模式
# 2. 路由配置(动态路由、懒加载)
- 动态路由
const User = {
// 获取参数如10 20
template: '<div>User {{ $router.params.id }}</div>'
}
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头。能命中'/user/10' '/user/20' 等格式的路由
{ path: '/user/:id', component:User }
]
})
2
3
4
5
6
7
8
9
10
11
12
- 懒加载
export default new VueRouter({
routes: [
{
path: '/',
component: () => import('@/views/business/main'),
},
{
path: '/info',
component: () => import('@/views/business/info'),
},
]
})
2
3
4
5
6
7
8
9
10
11
12
# 六、Vue原理
# 1.组件化基础
# 很久以前就有组件化
- asp jsp php 已经有组件化了
- nodejs中也有类似的组件化
# 数据驱动视图(MVVM,setState)
- 传统组件,只是静态渲染,更新还要依赖于操作DOM
- 数据驱动视图 — Vue MVVM
- 数据驱动视图 — React setState
# Vue MVVM
# 2.响应式
- 组件data的数据一旦变化,立刻触发视图的更新
- 实现数据驱动视图的第一步
# 核心API - Object.defineProperty
vue3.0 修改为Proxy 但proxy兼容性不好,且无法polyfill
Object.defineProperty基本用法
const data = {}
const name = 'wangziqi'
Object.defineProperty(data,"name",{
get: function () {
console.log('get')
return name
},
set: function (newVal) {
console.log('set')
name = newVal
}
})
2
3
4
5
6
7
8
9
10
11
12
console.log(data.name) // get wangziqi
data.name = 'lisi' // set
2
Object.defineProperty实现响应式
Object.defineProperty 的缺点
- 深度监听,需要递归到底,一次性计算量大
- 无法监听新增属性/删除属性(Vue.set Vue.delete)
- 无法原生监听数组,需要特殊处理
实现
- 监听对象,监听数组
- 复杂对象,深度监听
// 触发更新视图
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)
}
})
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 深度监听
observe(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 深度监听
observe(newValue)
// 设置新值
// 注意, value 一直在闭包中,此处设置完之后,再get也是会获取最新的值
value = newValue
// 触发更新视图
updateView()
}
}
})
}
// 监听对象
function observe(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: 'wangziqi',
age: 20,
info: {
address: '青岛' // 需要深度监听
},
nums: [10, 20, 30]
}
// 监听数据
observe(data)
// 测试
data.name = 'lisi'
// data.age = 21
// console.log(data.age)
// data.x = '100' // 新增属性,监听不到 —— 所有有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所以有 Vue.delete
// data.info.address = 'qingdao'
// data.nums.push(4)
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# 3.虚拟DOM(Virtual DOM)和diff
- vdom 是实现vue的重要基石
- diff算法是vdom中最核心、最关键的部分
1.DOM操作非常耗费性能 2.以前用jQuery,可以自行控制DOM操作的时机,手动调整 3.VUE和React是数据驱动视图,如何有效控制DOM操作
原理:vdom-用JS模拟DOM结构,计算出最小的变更,操作DOM
# 用JS模拟DOM结构
<div id="div1" class ="container">
<p>vdom</P>
<ul style="font-size:20px">
<li>a</li>
</ul>
</div>
2
3
4
5
6
{
tag: 'div',
props: {
clasName: 'container',
id: 'div1'
}
children: [
{
tag:'p',
children: 'vdom'
},
{
tag:'ul',
props: { style:'font-size:20px' },
children: [{
tag: 'li',
children: 'a'
}
// ...
]
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 通过snabbdom学习vdom
snabbdom 简洁强大的vdom库,易学易用 Vue参考snabbdom实现的vdom和diff
# 4.模板编译
# 5.渲染过程
# 6.前端路由
# 七、Vue面试题详解
# 1. v-for为何使用key
- 必须用key,且不能是index和random
- diff算法中通过tag和key来判断,是否是sameNode
- 减少渲染次数,提升渲染性能
# 2. 描述Vue组件生命周期(父子组件)
- Vue组件生命周期(单组件)
- Vue组件生命周期(父子组件)
# 3. Vue组件如何通讯
- 父子组件:props和this.$emit
- 无关组件、兄弟组件:自定义事件-event.$no event.$off event.$emit
- vuex
# 4.描述组件渲染和更新的过程
# 5.双向数据绑定 v-model的实现原理
- input元素的value = this.name
- 绑定input事件this.name = $event.target.value
- data更新触发re-render
# 6.对MVVM的理解
- View -> DOM
- ViewModel -> DOM监听、Directives指令
- Model -> Plain JavaScript Object
# 7.computed有何特点
- 缓存,data不变不会重新计算
- 提高性能
# 8.为何组件data必须是一个函数?
export default {
name: 'app',
data() {
return {
name: 'vue',
list: ['a','b', 'c']
}
}
}
2
3
4
5
6
7
8
9
定义的vue文件是一个class(类),每个地方在去使用类的时候会进行实例化。如果不是一个函数每一个组件在实例数据都一样了,数据共享了,在闭包之中。 例如实例化A和B。在A中修改data内容,B中将会同步变化
# 9.ajax 请求应该放在哪个声明周期
mounted
- JS是单线程的,ajax异步获取数据
- 放在mounted之前没有用,只会让逻辑更加混乱
只要是js没有渲染完,数据拿过来也是异步队列过程中,不会有什么提前的效果
# 10.如何将组件所有props传递给子组件
1. $props
2. <User v-bind="$props" />
2
# 11.如何自己实现v-model
1.定义model
model: {
prop: 'text', // 对应到props text
event: 'change' // 对应到触发动作
}
2
3
4
2.触发Dom动作
<input type="text" :value="text" @input="$emit('change',$event.target.value)">
3.创建对应props
props: {
text: {
type: String,
default: ''
}
}
2
3
4
5
6
# 12.多个组件有相同的逻辑,如何抽离?
mixin
但是mixin有一些缺点
- 不利于阅读
- 命名冲突
- 与组件多对多,复杂度高
# 13.何时使用异步组件?
- 加载大组件
- 路由异步加载
# 14.何时使用keep-alive
- 缓存组件,不需要重复渲染
- 如多个静态tab页的切换
- 优化性能
# 15.何时需要使用beforeDestory
- 解绑自定义事件 event.$off
- 清除定时器(setTime)
- 解绑自定义的DOM事件,如window scroll 等
# 16.什么是作用域插槽
子组件将slot数据外放
# 17.Vuex中action和mutation有何区别
- action中处理异步,mutation不可以
- mutation做原子操作(只处理一步)
- action可以整合多个mutation
action进行异步操作,commit到mutation mutation 执行修改state操作
# 18.Vue-router常用的路由模式
- hash-默认,有#
- H5 history(需要服务端支持)
# 19.如何配置Vue-router异步加载
- path(路由)
- component(组件)
component: () -> import('@view/business/main')
# 20. 用vnode描述一个Dom结构
tag props:className、id、style children
# 21. 监听data变化的核心API是什么
Object.defineProperty
缺点,递归监听,一次性消耗大
深度监听:递归 监听数组:重新定义数组方法
# 22.Vue如何监听数组变化
Object.defineProperty不能监听数组
重新定义数组原型,重写push、pop 等方法,实现监听
Proxy可以原生支持监听数组变化
# 23.请描述响应式原理
监听data变化的过程
组件渲染和更新的流程
# 24. diff算法的时间复杂度
- O(n)
- 在O(n^3)基础上做了一些调整
# 25. 简述diff算法过程
- patch(elem,vnode) 和 patch(vnode,newVnode)
- patchVnode 和 addVnodes和removeVnodes
- updateChildren(key的重要性)
# 26. Vue为何是异步渲染,$nextTick何用?
- 异步渲染(以及合并data修改),提高渲染性能
- $nextTick在DOM更新完之后,触发回调
# 27.Vue常见性能优化方式
- 合理使用v-show和v-if
- 合理使用computed
- v-for时加key,以及避免和v-if同时使用
- 自定义时间、DOM事件及时销毁
- 合理使用异步组件
- 合理使用keep-alive
- data层级不要太深(因为深度监听需要一次性完成,消耗大)
- 使用vue-loader在开发环境做模版编译(预编译)
- webpack层面的优化
- 前端通用的性能优化,如图片懒加载
- 使用SSR