面试题整理
2. BFC
块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
IE下为 Layout,可通过 zoom:1 触发
-
触发条件:
-
根元素
-
position: absolute/fixed
-
display: inline-block / table
-
float 元素
-
ovevflow !== visible
-
-
规则:
-
属于同一个 BFC 的两个相邻 Box 垂直排列
-
属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠
-
BFC 中子元素的 margin box 的左边, 与包含块 (BFC) border box的左边相接触 (子元素 absolute 除外)
-
BFC 的区域不会与 float 的元素区域重叠
-
计算 BFC 的高度时,浮动子元素也参与计算
-
文字层不会被浮动层覆盖,环绕于周围
-
-
应用:
-
阻止margin重叠
-
可以包含浮动元素 —— 清除内部浮动(清除浮动的原理是两个div都位于同一个 BFC 区域之中)
-
自适应两栏布局
-
可以阻止元素被浮动元素覆盖
-
4. 居中布局
-
水平居中
-
行内元素: text-align: center
-
块级元素: margin: 0 auto
-
absolute + transform
-
flex + justify-content: center
-
-
垂直居中
-
line-height: height
-
absolute + transform
-
flex + align-items: center
-
table
-
-
水平垂直居中
-
absolute + transform
-
flex + justify-content + align-items
-
5. 选择器优先级
-
!important > 行内样式 > #id > .class > tag > * > 继承 > 默认
-
选择器 从右往左 解析
6.去除浮动影响,防止父级高度塌陷
-
通过增加尾元素清除浮动
- :after / <br> : clear: both
-
创建父级 BFC
-
父级设置高度
7. 对象的拷贝
-
浅拷贝: 以赋值的形式拷贝引用对象,仍指向同一个地址,修改时原对象也会受到影响
-
Object.assign
-
展开运算符(...)
-
-
深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
-
JSON.parse(JSON.stringify(obj)): 性能最快
-
具有循环引用的对象时,报错
-
当值为函数、undefined、或symbol时,无法拷贝
-
-
递归进行逐一赋值
-
13. 类型判断
判断 Target 的类型,单单用 typeof 并无法完全满足,这其实并不是 bug,本质原因是 JS 的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待:
-
基本类型(null): 使用 String(null)
-
基本类型(string / number / boolean / undefined) + function: 直接使用 typeof即可
-
其余引用类型(Array / Date / RegExp Error): 调用toString后根据[object XXX]进行判断
17. ES6/ES7
由于 Babel 的强大和普及,现在 ES6/ES7 基本上已经是现代化开发的必备了。通过新的语法糖,能让代码整体更为简洁和易读。
-
声明
-
let / const: 块级作用域、不存在变量提升、暂时性死区、不允许重复声明
-
const: 声明常量,无法修改
-
-
解构赋值
-
class / extend: 类声明与继承
-
Set / Map: 新的数据结构
21. 数组(array)
-
map: 遍历数组,返回回调返回值组成的新数组
-
forEach: 无法break,可以用try/catch中throw new Error来停止
-
filter: 过滤
-
some: 有一项返回true,则整体为true
-
every: 有一项返回false,则整体为false
-
join: 通过指定连接符生成字符串
-
push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项
-
unshift / shift: 头部推入和弹出,改变原数组,返回操作项
-
sort(fn) / reverse: 排序与反转,改变原数组
-
concat: 连接数组,不影响原数组, 浅拷贝
-
slice(start, end): 返回截断后的新数组,不改变原数组
-
splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
-
indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
-
reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)
2. 生命周期
-
_init_
-
initLifecycle/Event,往vm上挂载各种属性
-
callHook: beforeCreated: 实例刚创建
-
initInjection/initState: 初始化注入和 data 响应性
-
created: 创建完成,属性已经绑定, 但还未生成真实dom
-
进行元素的挂载: $el / vm.$mount()
-
是否有template: 解析成render function
- *.vue文件: vue-loader会将<template>编译成render function
-
beforeMount: 模板编译/挂载之前
-
执行render function,生成真实的dom,并替换到dom tree中
-
mounted: 组件已挂载
-
-
update:
-
执行diff算法,比对改变是否需要触发UI更新
-
flushScheduleQueue
- watcher.before: 触发beforeUpdate钩子 - watcher.run(): 执行watcher中的 notify,通知所有依赖项更新UI
-
触发updated钩子: 组件已更新
-
-
actived / deactivated(keep-alive): 不销毁,缓存,组件激活与失活
-
destroy:
-
beforeDestroy: 销毁开始
-
销毁自身且递归销毁子组件以及事件监听
-
remove(): 删除节点
-
watcher.teardown(): 清空依赖
-
vm.$off(): 解绑监听
-
-
destroyed: 完成后触发钩子
-
6. vue-router
-
mode
-
hash
-
history
-
-
跳转
-
this.$router.push()
-
<router-link to=""></router-link>
-
-
占位
- <router-view></router-view>
7. vuex
-
state: 状态中心
-
mutations: 更改状态
-
actions: 异步更改状态
-
getters: 获取状态
-
modules: 将state分成多个modules,便于管理
vue生命周期面试题
什么是vue生命周期?
Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。
vue生命周期的作用是什么?
它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。
vue生命周期总共有几个阶段?
它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后
第一次页面加载会触发哪几个钩子?
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
DOM 渲染在 哪个周期中就已经完成?
DOM 渲染在 mounted 中就已经完成了
简单描述每个周期具体适合哪些场景?
生命周期钩子的一些使用方法: beforecreate : 可以在这加个loading事件,在加载实例时触发 created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用 mounted : 挂载元素,获取到DOM节点 updated : 如果对数据统一处理,在这里写上相应函数 beforeDestroy : 可以做一个确认停止事件的确认框 nextTick : 更新数据后立即操作dom
arguments是一个伪数组,没有遍历接口,不能遍历
Vue 开发必须知道的 36 个技巧
2.watch
2.1 常用用法
1.场景:表格初始进来需要调查询接口 getList(),然后input 改变会重新查询
created(){
this.getList()
},
watch: {
inpVal(){
this.getList()
}
}
复制代码
2.2 立即执行
2.可以直接利用 watch 的immediate和handler属性简写
watch: {
inpVal:{
handler: 'getList',
immediate: true
}
}
复制代码
2.3 深度监听
3.watch 的 deep 属性,深度监听,也就是监听复杂数据类型
watch:{
inpValObj:{
handler(newVal,oldVal){
console.log(newVal)
console.log(oldVal)
},
deep:true
}
}
3. 14种组件通讯
3.1 props
这个应该非常属性,就是父传子的属性; props 值可以是一个数组或对象;
// 数组:不建议使用
props:[]
// 对象
props:{
inpVal:{
type:Number, //传入值限定类型
// type 值可为String,Number,Boolean,Array,Object,Date,Function,Symbol
// type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认
required: true, //是否必传
default:200, //默认值,对象或数组默认值必须从一个工厂函数获取如 default:()=>[]
validator:(value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
复制代码
3.2 $emit
这个也应该非常常见,触发子组件触发父组件给自己绑定的事件,其实就是子传父的方法
// 父组件
<home @title="title">
// 子组件
this.$emit('title',[{title:'这是title'}])
复制代码
3.3 vuex
1.这个也是很常用的,vuex 是一个状态管理器 2.是一个独立的插件,适合数据共享多的项目里面,因为如果只是简单的通讯,使用起来会比较重 3.API
state:定义存贮数据的仓库 ,可通过this.$store.state 或mapState访问
getter:获取 store 值,可认为是 store 的计算属性,可通过this.$store.getter 或
mapGetters访问
mutation:同步改变 store 值,为什么会设计成同步,因为mutation是直接改变 store 值,
vue 对操作进行了记录,如果是异步无法追踪改变.可通过mapMutations调用
action:异步调用函数执行mutation,进而改变 store 值,可通过 this.$dispatch或mapActions
访问
modules:模块,如果状态过多,可以拆分成模块,最后在入口通过...解构引入
3.13 路由传参
1.方案一
// 路由定义
{
path: '/describe/:id',
name: 'Describe',
component: Describe
}
// 页面传参
this.$router.push({
path: `/describe/${id}`,
})
// 页面获取
this.$route.params.id
复制代码
2.方案二
// 路由定义
{
path: '/describe',
name: 'Describe',
component: Describe
}
// 页面传参
this.$router.push({
name: 'Describe',
params: {
id: id
}
})
// 页面获取
this.$route.params.id
复制代码
3.方案三
// 路由定义
{
path: '/describe',
name: 'Describe',
component: Describe
}
// 页面传参
this.$router.push({
path: '/describe',
query: {
id: id
`}
)
// 页面获取
this.$route.query.id
复制代码
4.三种方案对比 方案二参数不会拼接在路由后面,页面刷新参数会丢失 方案一和三参数拼接在后面,丑,而且暴露了信息
30.4 路由模式
设置 mode 属性:hash或 history
30.5 Vue.$router
this.$router.push():跳转到不同的url,但这个方法回向history栈添加一个记录,点击后退会返回到上一个页面
this.$router.replace():不会有记录
this.$router.go(n):n可为正数可为负数。正数返回上一个页面,类似 window.history.go(n)
复制代码
30.6 Vue.$route
表示当前跳转的路由对象,属性有: name:路由名称 path:路径 query:传参接收值 params:传参接收值 fullPath:完成解析后的 URL,包含查询参数和 hash 的完整路径 matched:路由记录副本 redirectedFrom:如果存在重定向,即为重定向来源的路由的名字
this.$route.params.id:获取通过 params 或/:id传参的参数
this.$route.query.id:获取通过 query 传参的参数
框架: React
React 的核心流程可以分为两个部分:
-
reconciliation (调度算法,也可称为 render):
-
更新 state 与 props;
-
调用生命周期钩子;
-
生成 virtual dom;
- 这里应该称为 Fiber Tree 更为符合;
-
通过新旧 vdom 进行 diff 算法,获取 vdom change;
-
确定是否需要重新渲染
-
-
commit:
- 如需要,则操作 dom 节点更新;
2. 生命周期
在新版本中,React 官方对生命周期有了新的 变动建议:
-
使用getDerivedStateFromProps 替换componentWillMount;
-
使用getSnapshotBeforeUpdate替换componentWillUpdate;
-
避免使用componentWillReceiveProps;
其实该变动的原因,正是由于上述提到的 Fiber。首先,从上面我们知道 React 可以分成 reconciliation 与 commit 两个阶段,对应的生命周期如下:
-
reconciliation:
-
componentWillMount
-
componentWillReceiveProps
-
shouldComponentUpdate
-
componentWillUpdate
-
-
commit:
-
componentDidMount
-
componentDidUpdate
-
componentWillUnmount
-
新版的建议生命周期如下:
class Component extends React.Component {
// 替换 `componentWillReceiveProps` ,
// 初始化和 update 时被调用
// 静态函数,无法使用 this
static getDerivedStateFromProps(nextProps, prevState) {}
// 判断是否需要更新组件
// 可以用于组件性能优化
shouldComponentUpdate(nextProps, nextState) {}
// 组件被挂载后触发
componentDidMount() {}
// 替换 componentWillUpdate
// 可以在更新之前获取最新 dom 数据
getSnapshotBeforeUpdate() {}
// 组件更新后调用
componentDidUpdate() {}
// 组件即将销毁
componentWillUnmount() {}
// 组件已销毁
componentDidUnMount() {}
}
-
使用建议:
-
在constructor初始化 state;
-
在componentDidMount中进行事件监听,并在componentWillUnmount中解绑事件;
-
在componentDidMount中进行数据的请求,而不是在componentWillMount;
-
需要根据 props 更新 state 时,使用getDerivedStateFromProps(nextProps, prevState);
- 旧 props 需要自己存储,以便比较;
-
-
public static getDerivedStateFromProps(nextProps, prevState) {
-
// 当新 props 中的 data 发生变化时,同步更新到 state 上
-
if (nextProps.data !== prevState.data) {
-
return {
-
data: nextProps.data
-
}
-
} else {
-
return null1
-
}
-
}
复制代码
- 可以在componentDidUpdate监听 props 或者 state 的变化,例如:
componentDidUpdate(prevProps) {
// 当 id 发生变化时,重新获取数据
if (this.props.id !== prevProps.id) {
this.fetchData(this.props.id);
}
}
复制代码
-
在componentDidUpdate使用setState时,必须加条件,否则将进入死循环;
-
getSnapshotBeforeUpdate(prevProps, prevState)可以在更新之前获取最新的渲染数据,它的调用是在 render 之后, update 之前;
-
shouldComponentUpdate: 默认每次调用setState,一定会最终走到 diff 阶段,但可以通过shouldComponentUpdate的生命钩子返回false来直接阻止后面的逻辑执行,通常是用于做条件渲染,优化渲染的性能。
3. setState
setState: React 中用于修改状态,更新视图。它具有以下特点:
异步与同步: setState并不是单纯的异步或同步,这其实与调用时的环境相关:
-
在 合成事件 和 生命周期钩子(除 componentDidUpdate) 中,setState是"异步"的;
-
原因: 因为在setState的实现中,有一个判断: 当更新策略正在事务流的执行中时,该组件更新会被推入dirtyComponents队列中等待执行;否则,开始执行batchedUpdates队列更新;
-
在生命周期钩子调用中,更新策略都处于更新之前,组件仍处于事务流中,而componentDidUpdate是在更新之后,此时组件已经不在事务流中了,因此则会同步执行;
-
在合成事件中,React 是基于 事务流完成的事件委托机制 实现,也是处于事务流中;
-
-
问题: 无法在setState后马上从this.state上获取更新后的值。
-
解决: 如果需要马上同步去获取新值,setState其实是可以传入第二个参数的。setState(updater, callback),在回调中即可获取最新值;
-
-
在 原生事件 和 setTimeout 中,setState是同步的,可以马上获取更新后的值;
- 原因: 原生事件是浏览器本身的实现,与事务流无关,自然是同步;而setTimeout是放置于定时器线程中延后执行,此时事务流已结束,因此也是同步;
批量更新: 在 合成事件 和 生命周期钩子 中,setState更新队列时,存储的是 合并状态(Object.assign)。因此前面设置的 key 值会被后面所覆盖,最终只会执行一次更新;
函数式: 由于 Fiber 及 合并 的问题,官方推荐可以传入 函数 的形式。setState(fn),在fn中返回新的state对象即可,例如this.setState((state, props) => newState);
- 使用函数式,可以用于避免setState的批量更新的逻辑,传入的函数将会被 顺序调用;
注意事项:
-
setState 合并,在 合成事件 和 生命周期钩子 中多次连续调用会被优化为一次;
-
当组件已被销毁,如果再次调用setState,React 会报错警告,通常有两种解决办法:
-
将数据挂载到外部,通过 props 传入,如放到 Redux 或 父级中;
-
在组件内部维护一个状态量 (isUnmounted),componentWillUnmount中标记为 true,在setState前进行判断;
-
5. Redux
Redux 是一个 数据管理中心,可以把它理解为一个全局的 data store 实例。它通过一定的使用规则和限制,保证着数据的健壮性、可追溯和可预测性。它与 React 无关,可以独立运行于任何 JavaScript 环境中,从而也为同构应用提供了更好的数据同步通道。
-
核心理念:
-
单一数据源: 整个应用只有唯一的状态树,也就是所有 state 最终维护在一个根级 Store 中;
-
状态只读: 为了保证状态的可控性,最好的方式就是监控状态的变化。那这里就两个必要条件:
-
Redux Store 中的数据无法被直接修改;
-
严格控制修改的执行;
-
-
纯函数: 规定只能通过一个纯函数 (Reducer) 来描述修改;
-
-
大致的数据结构如下所示:
-
理念实现:
-
Store: 全局 Store 单例, 每个 Redux 应用下只有一个 store, 它具有以下方法供使用:
-
getState: 获取 state;
-
dispatch: 触发 action, 更新 state;
-
subscribe: 订阅数据变更,注册监听器;
-
-
-
// 创建
-
const store = createStore(Reducer, initStore)
复制代码
- Action: 它作为一个行为载体,用于映射相应的 Reducer,并且它可以成为数据的载体,将数据从应用传递至 store 中,是 store 唯一的数据源;
// 一个普通的 Action
const action = {
type: 'ADD_LIST',
item: 'list-item-1',
}
// 使用:
store.dispatch(action)
// 通常为了便于调用,会有一个 Action 创建函数 (action creater)
funtion addList(item) {
return const action = {
type: 'ADD_LIST',
item,
}
}
// 调用就会变成:
dispatch(addList('list-item-1'))
复制代码
- Reducer: 用于描述如何修改数据的纯函数,Action 属于行为名称,而 Reducer 便是修改行为的实质;
// 一个常规的 Reducer
// @param {state}: 旧数据
// @param {action}: Action 对象
// @returns {any}: 新数据
const initList = []
function ListReducer(state = initList, action) {
switch (action.type) {
case 'ADD_LIST':
return state.concat([action.item])
break
defalut:
return state
}
}
复制代码
注意:
-
遵守数据不可变,不要去直接修改 state,而是返回出一个 新对象,可以使用 assign / copy / extend / 解构 等方式创建新对象;
-
默认情况下需要 返回原数据,避免数据被清空;
-
最好设置 初始值,便于应用的初始化及数据稳定;
-
进阶:
-
React-Redux: 结合 React 使用;
-
<Provider>: 将 store 通过 context 传入组件中;
-
connect: 一个高阶组件,可以方便在 React 组件中使用 Redux;
-
将store通过mapStateToProps进行筛选后使用props注入组件
-
根据mapDispatchToProps创建方法,当组件调用时使用dispatch触发对应的action
-
-
-
Reducer 的拆分与重构:
-
随着项目越大,如果将所有状态的 reducer 全部写在一个函数中,将会 难以维护;
-
可以将 reducer 进行拆分,也就是 函数分解,最终再使用combineReducers()进行重构合并;
-
-
异步 Action: 由于 Reducer 是一个严格的纯函数,因此无法在 Reducer 中进行数据的请求,需要先获取数据,再dispatch(Action)即可,下面是三种不同的异步实现:
-
作者:汤晨旭
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。