一、react
1. react生命周期
react 16生命周期相对于15的变化:
componentWillMount,componentWillReceiveProps, componentWillUpdate准备废除。理由:主要是16 版本 render 之前的生命周期可能会被多次执行。
static getDerivedStateFromProps和 getSnapshotBeforeUpdate新增,用于补充上述的生命周期。
2. 对虚拟dom的理解
本质上是 JavaScript 对象,这个对象就是更加轻量级的对 DOM 的描述。DOM 有很多属性,如果把对 DOM 的 diff 操作转移到 JS 对象,就可以避免大量对 DOM 的查询操作。这个更轻量级的 JS 对象就称为 Virtual DOM 。
虚拟dom工作过程:
- 维护一个使用 JS 对象表示的 Virtual DOM,与真实 DOM 一一对应
- 对前后两个 Virtual DOM 做 diff ,生成变更(Mutation)
- 把变更应用于真实 DOM,生成最新的真实 DOM
可以看出,因为要把变更应用到真实 DOM 上,所以还是避免不了要直接操作 DOM ,但是 React 的 diff 算法会把 DOM 改动次数降到最低。
diff的原理:将树形结构按照层级分解,只比较同级元素;给列表结构每一个单元添加key属性,方便比较。
3. setState
如果直接修改state,react无法得知需要重新渲染组件,使用setState(),react可以更新组件UI。调用setState后,react将传入的参数与当前状态合并,生成新的元素树,与老的元素树进行对比,根据差异对界面进行最小化重新渲染。
State 的更新可能是异步的,所以如果setState需要要依赖this.state或this.props的值,需传入函数。
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用(render)。setState不会立即改变state的值,在 React的生命周期和合成事件中, React仍然处于他的更新机制中,这时无论调用多少次 setState,都不会立即执行更新,而是将其放入一个任务队列。当上一次更新机制执行完毕(eg;生命周期),将多个setState合并,一次性渲染页面。
通过 addEventListener || setTimeout/setInterval 的方式处理的setState会同步更新。
4. react的事件机制
react事件没有绑定在真实的dom上,而是通过事件代理,绑在document上,对事件进行分发。React的所有事件都通过 document进行统一分发。当真实 Dom触发事件后冒泡到 document后才会对 React事件进行处理。
react事件要自己绑定this,因为事件绑定在document上,不是真实要调用的组件上。
react事件和原生事件区别:
- react事件命名为驼峰式,不是全小写;
- jsx可以传递一个函数为事件处理程序,不是字符串
- react中不能返回false阻止默认事件,必须调用preventDefault。
合成事件:事件处理函数的参数。事件处理程序将传递 SyntheticEvent 的实例,这是一个跨浏览器原生事件包装器。它具有与浏览器原生事件相同的接口。
因为真实dom出发时间冒泡到document后才会处理react事件,所以事件触发顺序为:原生事件 =》 合成事件 =》react真正在document上挂载的事件
react事件和原生事件最好不要混用。因为原生事件如果执行了stopPropagation方法,所有元素的事件都无法冒泡到document上,所有的react事件都无法触发。
5. refs
Refs是react提供的用来访问dom或者组件的句柄。是父组件用来获取子组件的dom元素的。我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回。
- ref设置为普通字符串
给元素/组件定义ref属性,后续可以通过 this.refs.myBtn 来获取这个真实DOM对象/组件的实例对象 - ref设置为回调函数
给元素/组件定义ref属性,后续可以通过 this.myBtn 来获取这个真实DOM对象/组件的实例对象
- refs 并不是类组件的专属,函数式组件同样能够利用闭包暂存其值。
function CustomForm ({handleSubmit}) {
let inputElement
return (
)
}
6. 动态加载
const LazyComponent = React.lazy(() =>
import(/* webpackChunkName: 'lazyComponent'*/ "../components/LazyComponent")
);
7. Hook
hook之前的函数组件是无状态、无副作用,只作单纯的展示组件。
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。
用法: useState, useEffect,useReducer,自定义hook
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
//定义一个state:isOnline,和修改state的方法setIsOnline
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
//副作用,相当于 componentDidMount 和 componentDidUpdate,页面每次渲染都会执行
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
//return一个方法就是清楚副作用,相当于componentWillUnmount
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
//useEffect也可以传入第二个参数[isOnline],代表只有isOnline修改时才执行副作用
//若第二个参数为 [],代表只有页面第一次渲染执行副作用
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
8. 受控组件和非受控组件
受控组件:使用表单元素时,将react的state作为数据的唯一源,并且控制着用户输入时表单发生的操作。被react以这种方式控制取值的表单元素就是受控组件。
非受控组件:表单数据由dom节点处理。
//受控组件
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
);
}
}
//非受控组件
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
);
}
}
9. 高阶组件(HOC)
高阶组件(HOC)是参数为组件,返回值为新组件的函数。
二、vue
1. vue2.x的响应式原理
vue初始化数据时,用Object.defineProperty()将data中的property转为getter/setter。每个vue实例对应一个watcher实例,它在组件渲染过程中将所有property记录为依赖。当属性发生变化时触发依赖项的setter,通知watcher,使对应组件重新渲染。
Vue3.x改用Proxy替代Object.defineProperty。
2. vue2.x中如何监测数组变化
使用函数劫持的方法,对data中的数组进行方法重写,使其能够通知依赖变更,如果数组中包含引用类型,使用递归遍历进行监控。
3. nextTick
在下次dom更新后执行回调。nextTick主要使用了宏任务和微任务,根据执行环境尝试采用Promise,MutationObserver,setImmediate和setTimeout。
4. 生命周期
beforeCreated, created, beforeMounted, mounted, beforeDestroyed, destroyed
5. computed 和 watch
computed是一个计算属性,基于响应式依赖进行缓存,适用于比较消耗性能的计算场景。很长很复杂的表达式放在模板难以维护,就放入计算属性。
watch是一个侦听器,监听依赖变化来执行一些操作。
6. v-if 和 v-show
v-if在条件成立时才会渲染。提升页面初始渲染性能。
v-show页面渲染时就会同时渲染,只是将display设为none,不展示。提升页面多次切换渲染性能。
7. 组件中的data为什么是一个函数
一个组件被多次使用就是创建了多个实例,这些实例用的都是同一个构造函数,如果data是对象,多个实例共享data的引用,数据会互相影响,使用函数可以避免这个问题。
8. vue模板编译原理
vue的模板通常被编译成了render函数。会经历以下阶段:
- AST语法树
- 优化
- codegen
首先解析模板,转化为AST语法树。使用正则表达式解析模板,遇到标签,文本都会有相应的钩子进行处理。
模板中有很多数据不是响应式的,首次渲染后不会再发生变化。优化过程就是深度遍历AST语法树,将其标记为静态节点,用来跳过对他们的比对。
将优化后的AST语法树转换为可执行的代码。
9. key
key是在使用v-for的列表中为节点添加标识,如果没有key,vue重新渲染会选择就地修改重用相同元素,而使用key则会根据key的变化进行重新排列,移除key不存在的元素。
10. keep-alive
keep-alive用于包裹动态组件,在组件变更时将其进行缓存,再次展示时无需重新渲染。
11. Vue中组件生命周期调用顺序
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程
父 beforeUpdate -> 父 updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed