一、VUE的基本使用
1. v-html:会有XSS风险,会覆盖子组件
2. computed:有缓存,data不变则不会计算
3. watch:默认是浅监听,可以deep:true 如果是引用类型的值 newValue和oldValue是一样的 因为是引用类型 值是指针地址 他们指向一样
4. v-for和v-if不能一起使用 v-for的会优先v-if计算 也就是在v-for每次循环的时候 v-if都会执行一遍 官网不推荐使用
5. $event 传参获取到的就是原生的event对象 事件被挂在到当前元素
6. 组件通信 父子组件 props $emit
兄弟组件 自定义事件 event.$on event.$emit (event是vue的实例)最好在beforeDestroy 时候 emit.$off,否则可能造成内存泄漏 所以 在 event.$on('onAddTitle',this.addTitle) 给一个函数名调用this.addTitle
beforeDestroy 可以做什么?卸载事件监听,卸载自定义事件
7. 生命周期 挂载 - 更新 -销毁
beforeCreate/created(事件,周期初始化,还未开始渲染) beforeMount/mounted (页面已经渲染完了) - beforeUpdate/updated - beforeDestroy/destroyed
父 index 子 list 生命周期执行顺序 index created -> list created -> list mounted -> index mounted ;update和destroy也如此
8. vue的高级特性
①:自定义v-model (场景:颜色选择器)
②:$nextTick
Vue是异步渲染的框架(data改变之后,dom不会立即渲染,$nextTick会在DOM渲染之后触发,以获取最新DOM,也就是data改变后,立即去获取dom数据是获取不到的,但是可以在nextTIck中获取到)
③:slot
作用域插槽、具名插槽
父组件想要往子组件插一些东西
④:动态、异步组件
动态组件: :is="component-name" 需要根据数据,动态渲染的场景,即组件类型不确定
异步组件:import()函数 在某个特定条件下这个组件才会渲染 v-if判断 然后 ()=>import('../../') ; 按需加载,异步加载大组件;
⑤:keep-alive
缓存组件,频繁切换,不需要重复渲染,出现在Vue常见性能优化面试题
第一次触发mouted 后面就不会触发生命周期函数
分页
⑥:mixin
多个组件有相同的逻辑,抽离出来
mixin并不是完美的解决方案,会有一些问题
Vue3 提出的composition API旨在解决这些问题
缺点:变量来源不明确,不利于阅读;多mixin可能造成命名冲突;mixin和组件可能会出现多对多的关系,复杂度较高
9.vuex
10.vue-router (hash history)
二. Vue原理篇
1. Vue响应式
①:监听对象变化
2.0核心API Object.defineProperty (监听对象)
缺点:深度监听,需要一次性递归,一次性计算量大;无法监听新增属性/删除属性 (Vue.set,Vue.delete);无法监听原生数组,需要特殊处理
proxy有兼容性问题:兼容性不好,无法polyfill
function defineReactive(target,key,value){ //注意如果是 info:{name:'123'},值是对象 observe(target) Object.defineProperty(target,key, { get(){ return value } set(newValue){ if(newValue!=value){ //value一直在闭包中,再get也是可以获取到值的 //这里有种情况 就是赋值是对象 info.name = {age:11},所以需要再次监听 observe(target) value = newValue updateView() //更新视图 } } }) } function observe(target){ if(type target!=='object'&&target===null){ return target //不是对象或数组 } //监听数组时候 判断是否是数组 下面有代码 //是对象的情况 for(let key in target){ defineReactive(target,key,target[key] } }
②:监听数组变化
const oldArrayProto = Array.prototype //创建新对象,原型指向oldArrayProto,再扩展的方法不会影响原型 const arrProto = Object.create(oldArrayProto) ['push','pop','shift','unshift','splice','sort','reverse'].forEach(value=>{ arrProto[value] = function(){ updateView() //更新视图 oldArrayProto[value].call(this,...arguments) //监听7个方法 更新视图 然后执行真正意义上的数组方法 } }) // 在observe深度监听时候 如果是数组则将value的原型 赋值为arrProto function observe(value){ if(Array.isArray){ value.__proto__ = arrProto } }
2.vdom (用JS模拟DOM)
vdom核心概念:h、vnode、patch、diff、key等
存在的价值:数据驱动视图,控制DOM操作
3.diff算法总结
patchVnode
addVnodes
removeVnodes
updateChildren(key的重要性)
4.模板编译 (组件渲染和更新过程?)
with语法:改变{}内自由变量的查找规则,当做obj属性来查找;如果找不到匹配的obj属性,就会报错;with要慎用,它打破了作用域规则,易读性变差
_c:createElement
_v:createTextVNode
_s:toString
_l:renderList
模板编译为render函数,执行render函数返回vnode
基于vnode再执行patch和diff
使用webpack vue-loader 会在开发环境下编译模板(重要) 开发环境就会编译好 就不用在浏览器运行时候去编译 性能优化
组件 渲染/更新 过程
初次渲染过程:
①:解析模板为render函数 (或在开发环境已完成,vue-loader)
②:触发响应式,监听data属性 getter和setter
③:执行render函数,生成vnode, patch(elem,vnode)
更新过程:
①:修改data,触发setter(此前在getter中已被监听)
②:重新执行render函数,生成newVnode
③:patch(vnode,newVnode)
异步渲染:
①:$nextTick (vue组件是异步渲染的)
②:汇总data的修改,一次性更新视图
③:减少DOM操作次数,提高性能
总结:
1:渲染和响应式的关系;渲染和模板编译的关系;渲染和vdom的关系
2:初次渲染;更新过程;异步渲染
5、前端路由原理
①:hash
稍微复杂一点的SPA都需要路由 路由模式:hash、H5 history
hash特点:hash变化会触发网页跳转,即浏览器的前进、后退
hash变化不会刷新页面,spa必需的特点
hash永远不会提交到server端(前端自生自灭)
监听 onhashchange 事件
hash变化 包括:JS修改url ;手动修改url的hash;浏览器的前进、后退
②:h5 history
用url规范的路由,但跳转时不刷新页面
history.pushState(from,'',to) window.onpopstate(监听浏览器的前进、后退)
服务端:无论访问什么样的路由都需要返回index.html的页面 由前端路由去判断渲染哪一块
6.vue面试真题演练
①:v-show和v-if的区别
v-show:通过css display控制显示和隐藏
v-if:组件真正的渲染和销毁,而不是显示和隐藏
频繁切换显示状态用v-show,否则v-if
②:为何在v-for使用key
必须用key,且不能是index和random
diff算法中通过tag和key来判断,是否是sameNode
减少渲染次数,提升渲染性能
③:描述Vue组件的生命周期(父子组件)
④:vue组件如何通讯
父子组件props和$emit
自定义事件 event.$on/$emit/$off
vuex
⑤:描述组件渲染和更新过程
⑥:双向绑定v-model实现原理
input元素的value = this.name
绑定input事件this.name = $event.target.value
data更新触发re-render
⑦:对MVVM的理解
⑧:为何组件data必须是个函数
定义的每个vue文件都是一个类,在各个地方去使用时候 其实是实例化这个类,如果是对象的话,修改data之后其他地方都会改变,引用类型保存的是指针地址
⑨:ajax请求应该放在哪个生命周期
mounted
js是单线程,ajax异步获取数据
放在mounted之前没有用,只会让逻辑更乱,因为在那之前如果js没有执行完成,也不会去执行异步请求
⑩:如何将组件所有props传给子组件
<User v-bind="$props"> 在user组件正常接收,列出需要的组件
11:如何实现v-model
12.多个组件有相同的逻辑,如何抽离
mixin 以及mixin的一些缺点
13.何时使用异步组件
加载大组件;路由异步加载
14.何时需要使用keep-alive
缓存组件,不需要重复渲染
15.何时需要使用beforeDestory
解绑自定义事件 event.$off
清楚定时器
解绑自定义的DOM事件,如window.scroll
16.作用域插槽
17.Vuex中action和mutation有何区别
action中处理异步,mutation不可以
mutation做原子操作
action可以整合多个mutation
18.请用vnode描述一个DOM结构
{tag:'div',props:{},children:[]}
19.Vue如何监听数组变化
Object.defineProperty不能监听数组变化
重新定义原型,重写push,pop等方法,实现监听
19.diff算法的时间复杂度
O(n)
20.Vue为何是异步渲染,$nextTick有何用
异步渲染(以及合并data修改)以提高渲染性能
$nextTick在DOM更新完之后,触发回调
21.Vue常见性能优化
①:合理使用v-show和v-if
②:合理使用computed(有缓存)
③: v-for时加key,以避免和v-if同时使用
④:自定义事件,DOM事件及时销毁
⑤:合理使用异步组件
⑥:合理使用keeplive
⑦:data层级不要太深
⑧:使用vue-loader在开发环境做模板编译(预编译)
7.Vue3
Proxy实现响应式
基础使用
const proxyData = new Proxy(data,{ get(target,key,receiver){ //只处理本身(非原型的)属性 const ownKeys = Reflect.ownKeys(target) if(ownKeys.includes(key)){ console.log('get',key) } const result = Reflect.get(target,key,receiver) console.log('get',key) return result }, set(target,key,val,receiver){ //重复的数据,不处理 const oldVal = target[key] if(val===oldVal){ return true } const result = Reflect.set(target,key,val,receiver) console.log('set',key,val) return result }, deleteProperty(target,key){ const result = Reflect.deleteProperty(target,key) console.log('deleteProperty',key) return result } })
Reflect作用
和proxy能力一一对应
规划化,标准化,函数式
替代掉Object上的工具函数
proxy实现响应式好处
深度递归,性能更好 (在每次获取值的时候去递归)
可监听 新增/删除 属性
const ownKeys = Reflect.ownKeys(target) if(ownKeys.includes(key)){ console.log('get',key) //已存在的 }else{ //新增的属性 }
可监听数组变化
弊端:无法兼容所有浏览器,无法polyfill
二、React
1. this默认是undefined
2.event 不是原生的event事件 SyntheticEvent(组合事件) constructor是个class (原生的是MouseEvent)
可以通过event.nativeEvent获得原生事件
vue触发的节点和绑定的节点 都是当前的节点
react 触发是当前节点 但绑定在document上面 通过event.nativeEvent.currentTarget:绑定节点 event.nativeEvent.target:触发节点
参数传参,最后一个参数是event
总结:react所有的事件都被挂载在document上,和DOM事件不一样,和Vue事件也不一样
3.受控组件和非受控组件
受控组件:input value onChange时候去重新赋值 类似于vue的v-model 表单绑定的value受state控制 ;
非控组件:组件的value不受state控制,内部存储其自身状态的组件 可以通过ref查看
4.setState
①:不可变值
不要修改原来state的状态
//数组 this.setState({ data:this.state.data.push(100) ,//会修改原来的值 data1:this.state.data1.concat(100) //不会修改原来的值,会返回新的值 })
//对象
不能直接对this.state.obj的属性进行设置,可以返回一个新值作为替换
this.setState({ obj:Object.assign({},this.state.obj,{a:100}) })
在shouldComponentUpdate时候 会对比前后值来确定是否改变视图,以此优化性能
②:可能是异步更新
渲染是异步的 ,修改值之后页面渲染时正常的,但是控制台打印是同步的,结果就会出错,
this.setState({},()=>{ //这里可以拿到最新的值,有点像vue的$nextTick })
setTimeout 是同步的
自定义的DOM事件是同步的(原生事件中是同步)
直接使用时异步的
钩子函数和合并事件是异步的
③:可能被合并
传入对象可能会被合并类似 Object.assign
初始是 this.state = {name:'alhh'}
后期setState的对象会和上面的合并 说明setState不会覆盖老的state而是合并操作
this.setState({})
传入函数 不会被合并 ,会更新完状态后再和老的合并
this.setState((prevState,props)=>{ return { count:this.state.count+1 } })
5.组件生命周期
父子组件生命周期和vue一样
6.React高级特性
①:函数组件
如何区别函数组件和class组件
instanceof React.Component 为true的是class组件
class组件如果只接收一个props去渲染页面, 不涉及state和其他逻辑 就可以使用函数组件
函数组件:输入props,输出JSX,没有实例,没有生命周期,没有state,不能扩展其他方法
②:非受控组件
ref defaultValue defaultChecked 手动操作DOM元素
只是通过ref去获取值 没有onChange去赋值
使用场景:
必须手动操作DOM元素,setState实现不了
文件上传 <input type="file">
某些富文本编辑器,需要传入DOM元素
受控组件 vs 非受控组件
优先使用受控组件,复合React设计原则(数据驱动视图)
必须操作DOM,再使用非受控组件
③:Portals(传送门)
组件默认按照既定的层次嵌套渲染,如何让组件渲染到父组件外?
ReactDom.createPortal(jsx,location)
第一个参数是jsx,第二个参数是想要放的位置
使用场景:
父组件形成bfc,overflow:hidden
父组件z-index太小
fixed需要放在body第一层级
④:context
公共信息(语言,主题)如何传递给每个组件?用props太繁琐,用redux小题大做
最外层Provider:<Theme.Provider value={}>
需要用到的地方 函数组件:<Sider.Consumer> {value=><p>{value}</p>} class组件:const ThemeContext = React.createContext('light') statis contextType = ThemeContext 然后this.context就可以获取到了
⑤:异步组件
import()
React.lazy:React.lazy(()=>import('../'))
React.Suspense:<React.Suspense fallback={<div>loading....</div>}
⑥:性能优化
shouldComponentUpdate(SCU):通过返回true或者false 来控制render的渲染 默认返回的是true ;必须配合“不可变值”一起使用,否则修改两者相同则不会触发SCU;不建议使用深度比较
React默认:父组件有更新,子组件则无条件更新(触发了render函数,render里面有子组件)
PureComponent 适合class组件和React.memo 适合函数组件(使用了浅比较:只比较第一层)底层是SCU
不可变值 immutable.js
⑦:高阶组件 HOC(接收一个组件参数,返回一个组件)
mixin已被React弃用
⑧:Render Props
render属性传入组件来控制渲染
7.redux
单向数据流
中间件在dispatch中加逻辑
8.react原理
函数式编程:纯函数,不可变值
diff算法
只比较同一层级,不跨级比较
tag不相同,则直接删除重建,不再深度比较
tag和key都相同,就认为是相同节点,不再深度比较
①:合成事件
所有事件挂载到document上
event不是原生的,是SyntheticEvent合成事件,event.nativeEvent是原生事件对象
和Vue事件不同,和DOM事件也不同
合成事件优点:
更好的兼容性和跨平台 (自己实现一套事件逻辑)
全部挂载到document,减少内存消耗,避免频繁解绑
方便事件的统一管理(如事务机制)
②.setState和batchUpdate
setState主流程
batchUpdate机制
1.哪些命中batchUpdate机制
生命周期(和它调用的函数)
React中注册的事件(和它调用的函数)
React可以“管理”的入口 (是在入口中设置)
2.哪些不能命中batchUpdate机制
setTimeout setInterval等(和它调用的函数)
自定义DOM事件(和它调用的函数)
React "管不到“的入口
transaction(事务)机制
先执行一个开始的逻辑 -》函数体执行 -》执行一个结束的逻辑
③:更新的两个阶段
patch会被分为两个阶段
reconciliation阶段,执行diff算法,纯JS计算
commit阶段 将diff结果渲染DOM
④:注意:可能会有的性能问题
JS是单线程,且和DOM渲染共用一个线程
当组件足够复杂,组件更新时计算和渲染都压力大
同时再有DOM操作需求(动画,鼠标拖拽等)将卡顿
解决方案:fiber(内部运行机制)
将reconciliation阶段进行任务拆分 (commit无法拆分)
目的是:DOM需要渲染时暂停,空闲是恢复;什么时候知道DOM需要渲染 通过window.requestIdleCallback(某些浏览器不一定支持)
9.React面试题目
①:组件之间如何通信
父子组件props
自定义事件
Redux和Context
②:JSX本质是什么
createElement
执行返回vnode
③:context是什么,如何应用
父组件,向其下所有子组件传递信息
如一些简单的公共信息:主题色、语言等
复杂的公共信息 用redux
④:shouldComponentUpdate用途
性能优化
配合”不可变值“一起使用
⑤:redux单向数据流