一、前端常见面试流程
二、先看几个面试题
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)、路由模式
- hash模式(默认)
- H5 history模式
- juejin.com/
- 需要server端支持,无特殊情况选前者
(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操作比较费时,