uploadhub的技术博客 uploadhub的技术博客
首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档

uploadhub

首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档
  • Vue相关

    • 1.Vue基本使用
    • 2.Vue原理
    • 3.Vue面试真题演练
      • 1.v-show 和 v-if 的区别
      • 2.为何在 v-for 中用 key
      • 3.描述 Vue 组件生命周期(父子组件)
      • 4.Vue 组件如何通讯(常见)
      • 5.双向数据绑定 v-model 的实现原理
      • 6.对 MVVM 的理解
      • 7.computed 有何特点
      • 8.为何组件 data 必须是一个函数
      • 9.ajax 请求应该放在哪个生命周期
      • 10.如何将组件所有 props 传递给子组件 ?
      • 11.如何自己实现 v-model
      • 12.多个组件有相同的逻辑,如何抽离?
      • 13.何时使用异步组件?
      • 14.何时需要使用 keep-alive ?
      • 15.何时需要使用 beforeDestroy
      • 16.Vuex 中 action 和 mutation 有何区别
      • 17.Vue-router 常用的路由模式
      • 18.如何配置 Vue-router 异步加载
      • 19.请用 vnode 描述一个 DOM 结构
      • 20.监听 data 变化的核心 API
      • 21.Vue 如何监听数组变化
      • 22.请描述响应式原理
      • 23.diff 算法的时间复杂度
      • 24.简述 diff 算法过程
      • 25.Vue 为何是异步渲染, $nextTick 何用?
      • 26.Vue 常见性能优化方式
    • 4.Vue3使用
  • React相关

  • Webpack和Babel

  • 项目设计
  • 项目流程
  • 二三面-进阶
  • Vue相关
uploadhub
2022-05-28
目录

3.Vue面试真题演练

# 1.v-show 和 v-if 的区别

  1. v-show 通过 css display 控制显示和隐藏
  2. v-if 组件真正的渲染和销毁,而不是显示和隐藏
  3. 频繁切换显示状态用 v-show,否则用 v-if

# 2.为何在 v-for 中用 key

  1. 必须用 key,且不能是 index 和 random
  2. diff 算法中 通过 tag 和 key 来判断,是否是 sameNode
  3. 减少渲染次数,提升渲染性能

# 3.描述 Vue 组件生命周期(父子组件)

  1. 单组件生命周期图
  2. 父子组件生命周期关系

生命周期(单个组件)

  1. 挂载阶段
  2. 更新阶段
  3. 销毁阶段

父组件 index 组件 子组件 List 组件

index.vue

export default {
    components: {
        List
    },
    created() {
        // eslint-disable-next-line
        console.log('index created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('index mounted')
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('index before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('index updated')
    },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

List.vue

export default {
    ....
    created() {
        // eslint-disable-next-line
        console.log('list created')
    },
    mounted() {
        // eslint-disable-next-line
        console.log('list mounted')

        // 绑定自定义事件
        event.$on('onAddTitle', this.addTitleHandler)
    },
    beforeUpdate() {
        // eslint-disable-next-line
        console.log('list before update')
    },
    updated() {
        // eslint-disable-next-line
        console.log('list updated')
    },
    beforeDestroy() {
        // 及时销毁,否则可能造成内存泄露
        event.$off('onAddTitle', this.addTitleHandler)
    }
}
1
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

页面初始化的时候,父子组件生命周期执行顺序

index created
list created
list mounted
index mounted
1
2
3
4

updated 时候

index beforeUpdate
list beforeUpdate
list updated
index updated
1
2
3
4

# 4.Vue 组件如何通讯(常见)

  1. 父子组件 props 和 this.$emit

  2. 自定义事件 event.$on event.$off event.$emit
  3. vuex

# 5.双向数据绑定 v-model 的实现原理

  1. input 元素的 value = this.name
  2. 绑定 input 事件 this.name = $event.target.value
  3. data 更新触发 re-render

# 6.对 MVVM 的理解

http://pengyouyi.site/assets/images/2016/7-8-9/9-21-7.png

# 7.computed 有何特点

  1. 缓存,data 不变不会重新计算
  2. 提高性能

# 8.为何组件 data 必须是一个函数

定义的组件 export default { data(){} } 是一个 Class,组件被引用的时候,组件实例化,data不是函数的话会造成数据共享

# 9.ajax 请求应该放在哪个生命周期

  1. mounted
  2. JS 是单线程的;ajax 异步获取数据,不会影响页面的渲染
  3. 放在 mounted 之前没有用,vue 逻辑走完才能走 ajax 的回调

另外一种观点:

  1. 接口不复杂会放 created 里面,接口多复杂的话会放在 mounted 里面.
  2. created 和 mounted一般相差多少毫秒?这点延迟用户是否能感知到?
  3. 官方也没有强制说明绑定在哪里。

# 10.如何将组件所有 props 传递给子组件 ?

  1. $props
  2. <User v-bind="$props" />
  3. 细节知识点,优先级不高

# 11.如何自己实现 v-model

<template>
    <div>
        <!-- 自定义 v-model -->
        <p></p>
        <CustomVModel v-model="name"/> 
    </div>
</template>

<script>
import CustomVModel from './CustomVModel'

export default {
    components: {
        CustomVModel
    },
    data() {
        return {
            name: '双越'
        }
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

CustomVModel.vue

<template>
    <!-- 例如:vue 颜色选择 -->
    <input type="text"
        :value="text1"
        @input="$emit('change1', $event.target.value)"
    >
    <!--
        1. 上面的 input 使用了 :value 而不是 v-model
        2. 上面的 change1 和 model.event1 要对应起来
        3. text1 属性对应起来
    -->
</template>

<script>
export default {
    model: {
        prop: 'text1', // 对应 props text1
        event: 'change1'
    },
    props: {
        text1: String,
        default() {
            return ''
        }
    }
}
</script>
1
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

# 12.多个组件有相同的逻辑,如何抽离?

mixin

  1. 多个组件有相同的逻辑,抽离出来
  2. mixin 并不是完美的解决方案,会有一些问题
  3. Vue3 提出的 Composition API 旨在解决这些问题

mixin 缺点

  1. 变量来源不明确,不利于阅读
  2. 多个 mixin ,可能会造成命名冲突
  3. mixin 和组件可能会出现多对多的关系,复杂度较高

MixinDemo.vue

<template>
    <div>
        <p>  </p>
        <button @click="showName">显示姓名</button>
    </div>
</template>

<script>
import myMixin from './mixin'

export default {
    mixins: [myMixin], // 可以添加多个,会自动合并起来
    data() {
        return {
            name: '双越',
            major: 'web 前端'
        }
    },
    methods: {
    },
    mounted() {
        console.log('component mounted', this.name)
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

mixin.js

export default {
    data() {
        return {
            city: '北京'
        }
    },
    methods: {
        showName() {
            console.log(this.name)
        }
    },
    mounted() {
        console.log('mixin mounted', this.name)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 13.何时使用异步组件?

  1. import() 函数
  2. 路由异步加载
  3. 按需加载,异步加载大文件(如:编辑器、图表)
<template>
    <div>
        <!-- 异步组件 -->
        <FormDemo v-if="showFormDemo"/>
        <button @click="showFormDemo = true">show form demo</button>

    </div>
</template>

<script>
import MixinDemo from './MixinDemo' 

export default {
    components: {
        FormDemo: () => import('../BaseUse/FormDemo'),  // 异步引入
        FormDemo2: () => {
            return import('../BaseUse/FormDemo2')
        },
        MixinDemo // 这是同步引入
    },
    data() {
        return {
        }
    }
}
</script>
1
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

# 14.何时需要使用 keep-alive ?

  1. 缓存组件,不需要重复渲染
  2. 频繁切换, 如多个静态 tab 页的切换
  3. 可以优化性能
<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'"/> <!-- v-show -->
            <KeepAliveStageB v-if="state === 'B'"/>
            <KeepAliveStageC v-if="state === 'C'"/>
        </keep-alive>
    </div>
</template>

<script>
import KeepAliveStageA from './KeepAliveStateA'
import KeepAliveStageB from './KeepAliveStateB'
import KeepAliveStageC from './KeepAliveStateC'

export default {
    components: {
        KeepAliveStageA,
        KeepAliveStageB,
        KeepAliveStageC
    },
    data() {
        return {
            state: 'A'
        }
    },
    methods: {
        changeState(state) {
            this.state = state
        }
    }
}
</script>
1
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

KeepAliveStageA.vue

<template>
    <p>state A</p>
</template>

<script>
export default {
    mounted() {
        console.log('A mounted')
    },
    destroyed() {
        console.log('A destroyed')
    }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

注意

  1. 因为 Keep-alive 会将组件保存在内存中,并不会销毁以及重新创建,所以不会重新调用组件的 created 等方法
  2. keep-alive 控制显示隐藏是 vue 层级的, v-show 是 css 层级

# 15.何时需要使用 beforeDestroy

  1. 解绑自定义事件 event.$off
  2. 清除定时器
  3. 解绑自定义的 DOM 事件,如 window scroll 等
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
beforeDestroy() {
   // 及时销毁,否则可能造成内存泄露
   event.$off('onAddTitle', this.addTitleHandler)
}
1
2
3
4
5
6

# 16.Vuex 中 action 和 mutation 有何区别

  1. action 中处理异步,mutation 不可以
  2. mutation 做原子操作(每次做一个操作)
  3. action 可以整合多个 mutation

# 17.Vue-router 常用的路由模式

  1. hash 默认
  2. H5 history (需要服务端支持)
  3. hash - window.onhashchange
  4. H5 history - history.pushState 和 window.onpopsate

# 18.如何配置 Vue-router 异步加载

动态 import()

# 19.请用 vnode 描述一个 DOM 结构

<div id="div1" class="container">
	<p>vdom</p>
	<ul style="font-size:20px">
		<li>a</li>
	</ul>
</div>
{
    tag: 'div',
    props: {
        className: 'container',
        id: 'div1'
    },
    children: [
        {
            tag: 'p',
            children: 'vdom'

        },
        {
            tag: 'ul',
            props: { style: 'font-size: 20px'},
            children: [
                {
                    tag: 'li',
                    children: 'a'
                },
                // ...
            ]

        }
    ]
}
1
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

# 20.监听 data 变化的核心 API

  1. Object.defineProperty
  2. 以及深度监听、监听数组
  3. 有何缺点

Object.defineProperty 基本用法

const data = {}
const name = 'zhangsan'
Object.defineProperty(data, 'name', {
    get: function() {
        console.log('get')
        return name
    },
    set: function(newVal) {
        console.log('set')
        name = newVal
    }
})

// 测试
console.log(data.name) // get zhangsan
data.name = 'lisi' // set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

监听 data 变化 - 简版

// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义属性,监听起来
function defineReactive(target, key, value) {

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if (newValue !== value) {

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
}

// 监听数据
observer(data)

// 测试
data.name = 'lisi'
data.age = 21
console.log('age', data.age)
1
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

监听 data 变化 - 深度监听

// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义属性,监听起来
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
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
}

// 监听数据
observer(data)

// 测试
data.info.address = '上海' // 深度监听
data.age = {num: 22}  // set 的时候也需要深度监听 newVal
data.age.num = 24

// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
1
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

Object.defineProperty 缺点

  1. 深度监听,需要递归到底,一次性计算量大
  2. 无法监听新增属性/删除属性 (Vue.set Vue.delete)
  3. 无法原生监听数组,需要特殊处理

# 21.Vue 如何监听数组变化

  1. Object.defineProperty 不能监听数组变化
  2. 重新定义原型,重写 push pop 等方法,实现监听
  3. proxy 可以原生支持监听数组变化
// 触发更新视图
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
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }
    
    // 监听数组
    if (Array.isArray(target)) {
        target.__proto__ = arrProto
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
}

// 监听数据
observer(data)

// 测试
// data.name = 'lisi'
// data.age = 21
// console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
1
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
83
84
85
86
87
88

# 22.请描述响应式原理

  1. 监听 data 变化
  2. 组件渲染和更新的流程

初次渲染过程

❶ 解析模板为 render 函数(或在开发环境已完成,vue-loader) ❷ 触发响应式,监听 data 属性 getter setter ❸ 执行 render 函数,生成 vnode, ❹ patch(elem, vnode)

更新过程

❺ 修改 data,触发 setter (此前在 getter 中已被监听) ❻ 重新执行 render 函数,生成 newVnode ❼ patch(vnode, newVnode) 【diff 算法上场】

# 23.diff 算法的时间复杂度

  1. O(n)
  2. 在 O(n^3) 基础上做了一些调整

# 24.简述 diff 算法过程

  1. patch(elem,vnode) 和 patch(vnode,newVnode)
  2. patchVnode 和 addVnodes 和 removeVnodes
  3. updateChildren (key 的重要性)

# 25.Vue 为何是异步渲染, $nextTick 何用?

  1. 异步渲染(以及合并 data 修改),以提高渲染性能
  2. $nextTick 在 DOM 更新完之后,触发回调

# 26.Vue 常见性能优化方式

  1. 合理使用 v-show 和 v-if
  2. 合理使用 computed
  3. v-for 时加 key,以及避免和 v-if 同时使用
  4. 自定义事件、DOM 事件及时销毁
  5. 合理使用异步组件
  6. 合理使用 keep-alive
  7. data 层级不要太深
  8. 使用 vue-loader 在开发环境做模板编译(预编译)
  9. webpack 层面的优化
  10. 前端通用的性能优化,如图片懒加载
  11. 使用 SSR
#面试#Vue
2.Vue原理
4.Vue3使用

← 2.Vue原理 4.Vue3使用→

最近更新
01
HTTP协议及缓存机制
05-28
02
开发环境
05-28
03
JS基础知识(一)-变量类型和计算
05-28
更多文章>
Theme by Vdoing | Copyright © 2021-2023 uploadhub | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式