Vue和React前端知识整理

327 阅读8分钟

一、前端常见面试流程

二、先看几个面试题

1、vue常见面试题

  • v-show和v-if的区别

    • v-show是通过css来控制的,v-if是本身机制来控制的,
    • 当组件频繁切换显示状态,用前者, 反之用 后者
  • 为何v-for中要用key

  • 描述vue组件生命周期(有父子组件的情况下)

  • vue组件如何通讯

  • 描述组件渲染和更新过程

  • 双向数据绑定v-model的实现原理

2、react面试题

  • React组件如何通讯
  • JSX本质是什么
  • context是什么,有何用途
  • shouldComponentUpdate的用途
  • 描述redux单项数据流
  • setState是同步还是异步

3、框架组合应用

  • 基于 react 设计一个todoList(组件结构,redux state 数据结构)
  • 基于 vue 设计一个购物车(组件结构, vuex state 数据结构)

4、webpack面试题

  • 前端代码为何要进行构建和打包
  • module chunk bundle 分别什么意思,有何区别
  • loader 和 plugin 的区别
  • webpack如何实现懒加载
  • webpack常见性能优化
  • babel-runtime 和 babel-polyfill的区别

5、如何应对上述面试题

  • 框架的使用(基本使用、高级特性、周边插件)
  • 框架的原理(基本原理的了解、热门技术的深度、全面性)
  • 框架的实际应用,即设计能力(组件结构、数据结构)

6、面试官为何要这样考场?

  • 保证候选人能正常功能 ——— 考察使用
  • 多个候选人竞争,选择有技术追求的 ——— 考察原理
  • 看候选人是否能独立承担项目 ——— 考察设计能力

三、vue

  • 基本使用、组件使用 —— 常用、必须会
  • 高级特性 ——— 不常用,但体现深度
  • vuex 和 vue-router 使用

1、插值、表达式、v-html

  • {{}} 插值
  • {{ flag ? 'yes' : 'no'}} 表达式
  • v-html 使用之后,将会覆盖子元素

2、computed 和 watch

  • computed 有缓存,data不变则不会重新计算

  • watch如何深度监听

        data(){
            return {
                name: '超越',
                info: {
                    city: '北京'
                }
            }
        },
        watch: {
            name(oldVal, val){
                console.log(oldVal, val) // 值类型,可正常拿到oldVal
            },
            info: {
                handler(oldVal, val){
                     console.log(oldVal, val) // 引用类型,拿不到oldVal
                },
                deep: true // 深度监听
            }
        }
    
  • watch监听引用类型,拿不到oldVal

    • 因为引用类型赋值是指针赋值,所以oldval 和val 是同一个地址,所以oldVal 和 val 是意义的

3、 v-show 和 v-if区别

  • v-show和v-if都是用来显示隐藏元素,v-if还有一个v-else配合使用,两者达到的效果都一样,性能方面去有很大的区别。
  • v-show不管条件是真还是假,第一次渲染的时候都会编译出来,也就是标签都会添加到DOM中。之后切换的时候,通过display: none;样式来显示隐藏元素。可以说只是改变css的样式,几乎不会影响什么性能。
  • 在首次渲染的时候,如果条件为假,什么也不操作,页面当作没有这些元素。当条件为真的时候,开始局部编译,动态的向DOM元素里面添加元素。当条件从真变为假的时候,开始局部编译,卸载这些元素,也就是删除。
  • 性能方面: v-if绝对是更消耗性能的,因为v-if在显示隐藏过程中有DOM的添加和删除,v-show就简单多了,只是操作css。
  • 使用场景: 因为v-show无论如何都会渲染,如果在一些场景下很难出现,那么使用v-if。如果是一些固定的,条件内容都不怎么会改变的,频繁切换的,使用v-show会比较省性能。如果是子组件,每次切换子组件不执行生命周期,使用v-show,如果子组件需要重新执行生命周期,那么使用v-if才能触发。

4、循环(列表)渲染

  • 如何遍历对象?———— 也可以用v-for(刚开始只支持数组,vue2.xx 才只是对象)
<template>
    <p>遍历数组</p>
    <div v-for="(item, index) in listArr" :key="item.id"> 
        {{index}} - {{item.id}} - {{item.title}}
    </div>
    <p>遍历对象</p>
     <div v-for="(val, key, index) in listObj" :key="key"> 
        {{index}} - {{key}} - {{val.title}}
    </div>
</template>

data(){
    return {
        listArr: [
            {id: 'a', title: '标题'}
        ],
        listArr: {
            a: {title: '标题1'},  
            b: {title: '标题2'}
        }
    }
}

  • key的重要性。key不能乱写(如 random 或者 index),要写与业务观连的信息,如id

  • v-for 不能与 v-if一起使用

    原因: v-for 比 v-if优先级更高一些,所以只有v-for循环完毕之后才会执行v-if判断,这是如果v-if=false ,这时v-for就做了无用循环

5、事件

(1)、 event参数,自定义参数

```
    <button @click="myIsClick"></button> // event参数
    <button @click="myIsClick2(2, $event)"></button> // 自定义参数, event参数
    
    myIsClick(event){
        // event 可直接拿到event 参数 
        console.log('event', event, event.__proto__.constructor) // 是原生event 对象
        console.log(event.target) // 事件监听,是挂载到什么地方的,
        console.log(event.currentTarget) // 事件在什么地方出发的
        // 1、event 是原生的
        // 2、事件被挂载到当前元素
    },
    myIsClick2(val, event){
        console.log(event.target)
    }
```

(2)、 事件修饰符,按键修饰符

- https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6 去看官网吧

6、vue父子组件通讯

(1)、 父传子

  • props, 可以定义 props 类型 和默认值

(2)、子传父

  • $emit

(3)、 组件之间 —— 自定义事件 event(vue 示例),

```
    //兄弟组件一
    addTile(){
        // 调用自定义组件
        event.$emit('onAddTitle', '我是title')
    }
    // 兄弟组件二
    mounted(){
        event.$on('onAddTitle', this.addTitleHandle)
    },
    methods(){
        addTitleHandle(title){
            console.log(title)
        }
    }
    brforeDestroy(){
        // 使用自定义事件要解绑,防止内存泄漏
        event.$off('onAddTitle', this.addTitleHandle)
    }
    // event.js
    import Vue from 'vue'
    export default new Vue()
```

7、vue组件声明周期

(1)、单组件声明周期

- 挂载阶段
    -  `beforeCreate`: 实例刚创建好,没有初始化好data 和 methods 属性
    -  `created`: 初始化好vue示例(data,methods已经有了),没有开始编译模板,数据存在于内存中
    -  `beforeMount`: 完成了模板的编译,但是没有挂载到页面中
    -  `mounted`:已经将编译好的模板挂载到html上
- 更新阶段
    - `beforeUpdate`: 状态更新之前执行此函数,此时data中的状态值是最新的,但是界面上显示的数据依然是旧的
    - `updated`: 根据新的数据重新渲染页面,此时页面和data中的数据一致,页面已经完成更新
-  销毁阶段
    - `beforeDestroy`:表示Vue实例即将销毁,但是还未销毁,实例中的数据等都可以使用 
    - `destroyed`:表示vue实例完全销毁,实例中的任何内容都不能被使用了

####(2)、 父子组件声明周期 挂载子先父后,销毁父先子后 - 挂载阶段 - 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted - 子组件更新过程 - 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated - 父组件更新过程 - 父 beforeUpdate -> 父 updated - 销毁阶段 - 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

8、vue高级特性

####(1)、 自定义v-model

```
// 使用v-model 传参数
// 父组件.vue data 部分省略
<template>
    <div>
        <p>{{name}}</p>
        <childComponent v-model="name"></childComponent>
    </div>
</template>
// 子组件 childComponent.vue
 <template>
   <input type="text" :value="name" @input="$emit('change', $event.target.value)"/>
   // input 使用的是:value  不是 v-model(原因是:自定义事件 不能使用v-model)
   // change  要和 model.event 对应
   // name 属于要props.name 和 model.prop 要对应
</template>
<script>
    export default {
        props:{
            name: String,
            default() {
                return: ''
            }
        },
        model: {
            prop: 'name', // 对应 props name
            event: 'change'
        }
    }
</script>
```

####(2)、 $nextTick

  • vue是异步渲染

  • data改变之后,dom不会立刻渲染

  • $nextTick会在dom渲染之后被触发,以获取最新dom节点

     <template>
       <div>
            <ul ref="ul1" v-for="item in list" :key="item">
                <li>{{item}}</li>
            </ul>
            <button @click="addITem">添加</button>
       </div
    </template>
    <script>
        export default {
           data(){
               return {
                   list: ['a', 'b', 'c']
               }
           },
           methods: {
               addITem(){
                   this.list.push(`${Date.now()}`)
                   this.list.push(`${Date.now()}`)
                   this.list.push(`${Date.now()}`)
                   
                   const ulElem = this.$refs.ul1
                   console.log(ulElem.childNodes.length) // 打印出 3
                   
                   // 1、"异步渲染 nextTick 待dom渲染完在回调"
                   // 2、"页面渲染时将data 的修改做整合,多次data修改 只会渲染一次"
                   this.c(() => {
                        const ulElem = this.$refs.ul1
                        console.log(ulElem.childNodes.length) // 打印出 6
                   })
               }
           }
        }
    </script>    
    

####(3)、 slot 插槽、作用域插槽、具名插槽

####(4)、 动态组件 <component :is="想要显示的组件名称"></component>

####(5)、异步组件

  • import()函数
    import List from './list' 引入list 组件
    components: {
        List,
        Login:() => import('../login') // 异步引入登录组件
    }
    
  • 按需加载,异步加载大文件

####(6)、 keep-alive

  • keep-alive: 是缓存组件
  • 什么时候使用: 频繁切换,不需要重复渲染
  • 会出现在什么地方: vue常见性能优化

####(7)、 mixin

  • 多个组件有相同的逻辑,抽离出来
  • mixin 缺点
    • 变量来源不明确,不利于阅读
    • 多mixin可能会造成命名冲突
    • mixin和组件可能出现多对多的关系,复杂复杂度较高

9、vuex

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  • 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。

(1)、vuex 作用

  • 项目数据状态的集中管理,复杂组件(如兄弟组件、远房亲戚组件)的数据通信问题。

(2)、vuex的5种属性

  • state:基本数据(数据源存放地)
  • getters : 从基本数据派生出来的数据
  • action:提交更改数据的方法,同步
  • mutation:像一个装饰器,包裹mutations,使之可以异步。
  • Module:模块化Vuex

(3)、工作原理

  • 在vue组件里面,通过dispatch来触发actions提交修改数据的操作。
  • 然后再通过actions的commit来触发mutations来修改数据。
  • mutations接收到commit的请求,就会自动通过Mutate来修改state(数据中心里面的数据状态)里面的数据。
  • 最后由store触发每一个调用它的组件的更新

(4)、用于vue组件

  • dispatch:
  • commit
  • mapState
  • mapGetters
  • mapActions
  • mapMutations

10、vue-router

(1)、路由模式

(2)、路由配置

  • 动态路由
    const router = new VueRouter({
      routes: [
        // 动态路径参数 以冒号开头
        { path: '/user/:id', component: User }
      ]
    })
    
  • 懒加载
    • 在router.js import() 引入组件

11、vue原理(大厂必考)

(1)面试为何考察原理

  • 知其然知其所以然
  • 了解原理,才能应用得当更好(竞争激烈,择优录取)
  • 大厂造轮子(有钱有资源,业务定制,技术KPI)

(2)面试中如何考察?以何种方式?

  • 考察重点,而不是考察细节,2/8原则
  • 和使用相关联的原理,例如:vdom,模板渲染
  • 整体流程是否全面?热门技术是否有热度?

(3)vue原理包括哪些

  • 组件化
    • 例如:如何理解MVVM模型?
    • “很久以前” 的组件化: asp、jsp、php已经有组件化了
    • nodejs中也有类似的组件化
  • 响应式原理
  • vdom和diff算法
  • 模板编译
  • 组件渲染过程
  • 前端路由

12、如何理解MVVM

  • MVVM(Model-View- ViewModel) 数据驱动视图

13、监听data变化的核心API是什么(vue 响应式)

  • 核心API —— Object.defineProperty

(1)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的
                value = newValue
                updateView()
            }
        }
    })
}

// 监听对象

function observer(target) {
    if(typeof target !== 'object' || target === null){
        return target
    }
    if(Array.isArray(target)){
        target.__proto__ = arrProto
    }
    // 重新定义各个属性
    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 = 'list'
data.age = 21
// data.x = '100' // 新增属性, 监听不到——所以有Vue.set
// delete data.name // 删除属性, 监听不到 所以有 vue.delete
data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
```

(2) Object.defineProperty 的一些缺点(vue 3.0 启用proxy)

  • 【proxy兼容性不好,且无法用polyfill】
  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增属性、删除属性(所以需要:Vue.set、Vue.delete api来解决)
  • 无法原生监听数组,需要特殊处理

14、虚拟DOM(Virtual DOM)和diff

背景:dom操作比较费时,