v-html 有xss风险
computed有缓存,data不变就不会重新变化
watch如何深度监听
watch正常监听不到引用值对象属性的变化,需要在watch开启deep实现深度监听
watch:花销较大
watch擅长处理的场景:一个数据影响多个数据
https://blog.csdn.net/yuwenshi12/article/details/78561372
v-for和v-if不能一起使用
key的重要性,key不能乱写(乱写random、index)
vue事件的event是原生的event事件
事件被挂载到当前对象
事件修饰符
<a @click.stop="doThis">
按键修饰符
<button @click.ctrl="onClick"></button>
表单知识
<input type="text" v-model.trim="name">
<input type="text" v-model.lazy="name">
<input type="text" v-model.number="name">
vue自定义事件通讯
event.$on()
event.$emit()
这里的event是新建了一个vue实例
需要在beforeDestory里面及时销毁
beforeDestroy(){
event.$off(‘onAddTitle‘,this.addTitleHandler)
}
Object.defineProperty基本用法
const data={}
const name=‘zhangsan‘
Object.defineProperty(data,"name",{
get:function(){
console.log(‘get‘)
return name
},
set:function(newVal){
console.log(‘set‘)
name=newVal
}
})
Vue是异步渲染(
data改变之后,DOM不会立刻渲染,是异步渲染,$nextTick待DOM渲染完再回调
页面渲染时会将data的修改做整合,多次data修改只会渲染一次(item.add*3)
$nextTick会在DOM渲染之后被触发,以获取最新DOM节点
)
addItem(){
const ulElem=this.$refs.ul
console.log(ulElem.childNodes.length)
}
slot插槽的使用
高级使用:作用域插槽
------父组件
{{slotProps.slotData.title}}
----------子组件
{{website.subTitle}}
------------------------动态组件
用法 :is="component-name"
需要根据数据,动态渲染的场景,即组件类型不确定
--------------------------------异步组件(重要)性能优化
按需加载
components:{
FormDemo:()=>import(‘../BaseUse/FormDemo‘)
}
---------------------------------keep-alive性能优化
缓存组件
适用于频繁切换,不需要重复渲染的场景,例如tab页
vue常见性能优化
常规的组件,v-if 切换的时候,都是销毁然后重建,其中会重新创建 vnode ,重新执行 diff 算法。
而 keep-alive 它会将组件生成 DOM 缓存起来,下次再重建时直接拿来使用。
关键就是,将组件渲染的结果缓存起来,不用再重新创建、重新渲染。
使用keep-alive包裹的组件有一个activated生命周期,当每次切换到的时候就触发
-------------------------------------------vue生命周期
beforeCreate
初始化vue,
无法获取dom,methods,data
create
可以获取methods,data,
无法获取dom
寻找el,如果没有el,使用外部html作为节点渲染
beforeMount
无法获取dom,替换html 的el节点
mount
挂载到页面上
beforeupdate
update
beforedestroy
destroyed
-----------------vue抽离公共逻辑mixin
这里的mixin并不是css那边的mixins,而是js的mixins
import myMixin from ‘./mixin‘
export default{
mixins:[myMixin],
data(){
return{
}
}
}
mixin 除了容易覆盖的问题,还会导致代码混乱,变量作用域不清晰。
Vue响应式
核心API Object.defineProperty
Object.defineProperty缺点
深度监听,需要递归到底,一次性计算量大
无法监听新增属性、删除属性
vue中defineProperty无法原生监听数组变化,需要特殊处理
--------------------------------------------------虚拟DOM-diff算法概述
优化时间复杂度从o(n^3)到o(n)
只比较同一层级,不跨级比较
tag不相同,则直接删掉重建,不再深度比较
tag和key,两者都相同,则认为是相同节点,不再深度比较
********* vnode 创建元素
********* patch 查询是否存在元素,存在比较是否相同,相同比较子元素,不相同直接重建
********* pathVnode 比较子元素,新旧都有children(一般来说要么有text要么有children){
新children有text,旧无:{
删除旧的children,添加text
}
新children有children ,旧有text:{
清空text,添加children
}
}
********* updateChildren 开始开始,结束结束,开始结束,结束开始,循环查询旧节点是否存在相同key元素
-------------------------------------------------vdom和diff总结
vdom核心概念:h vnode patch diff key
vdom存在的价值:数据驱动视图,控制DOM元素
diff算法是比较两个vnode,计算出最小的变更,以便减少DOM操作次数,提高性能。
其原理有:
只比较同级,不跨域比较;
如果tag不相同,直接删除重建,不再深度比较
如果tag和key都相同,默认是一样的节点,也不再深度比较
它的流程是:
首先,它会判断是否是首次渲染,因为如果是首次渲染,没有旧的vnode,不需要比较,直接渲染就可以了。
在非首次渲染,首先比较两个节点是否一样。如果不一样,直接删除重建;如果一样,就需要进行vnode比较、就是比较children。
如果新节点没有文本节点,删除旧节点的文本节点;如果有文本节点,替换掉旧的文本节点。
如果只有新节点有子节点,直接插入;如果只有旧节点有子节点,直接删除。
最后就是,新旧节点都有子节点的情况。
这时候会遍历新节点的children,每个新的子节点都需要在旧的children里面进行寻找,找一个一样的节点。
如果没有找到,新的子节点直接插入;如果找到了,这两个节点再进行vnode比较。
也可以简单的理解为,如果没有是重新渲染,如果有的话,直接把旧的子节点挪过来用就可以了。
---------------------------------------模板编译
面试官一般不会直接问,但会通过"组件渲染和更新过程"考察
1.JS的with语法
2.template complier将模板编译成rander函数
3.rander函数生成vnode
平常中with要慎用,它打破了作用域规则
vue模板被编译成什么:
转换成js代码
总结:模板编译为render函数,执行render函数返回vnode
基于vnode再执行patch和diff
使用webpack vue-loader,会在开发环境下编译模板
vue组件可以用render代替template
----------------------------------------总结组件、渲染、更新过程
一个组件渲染到页面,修改data触发更新(数据驱动视图)
响应式:监听data属性getter setter(包括数组)
vdom:patch(elem,vnode) 和patch(vnode,newVnode)
组件渲染过程:
解析模板为render函数
触发响应式,监听data属性getter、setter
执行render函数,生成vnode,patch(elem,vnode)
组件更新过程:
修改data,触发setter(此前在getter中已被监听)
重新执行render函数,生成newVnode
patch(vnode,newVnode)
-----------------------------------------vue异步渲染
--------------------------------------vue-router路由模式
hash模式(默认) http://abc.com/#/user/10
H5 history模式 http://abc.com/user/20 (需要server端支持,没有特殊需求可选择前者)
动态路由:
const router=new VueRouter({
routes:[
{path:‘/user/:id‘,component:User}
]
})
路由跳转
this.$router.push({ name: ‘news‘, params: { userId: 123 }})
this.$router.push({ path: ‘/news‘, query: { userId: 123 }});
-------------------------------------hash路由特点:
hash变化会触发网页跳转,即浏览器的前进、后退
hash变化不会刷新页面,SPA必需的特点
hash永远不会提交到server端(前端自生自灭)
两种选择:
to B的系统推荐使用hash,简单易用,对url规范不敏感
to C的系统,可以考虑选择H5 history,但需要服务端支持
总结:hash-window.onhashchange
H5 history-history.pushState(打开一个新路由)和window.onpopstate(前进和后退)
---------------------------------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和this.$emit
自定义事件event.$on event.$off event.$emit
vuex
------------------------------------双向数据绑定v-model的实现原理
input元素的value=this.name
绑定input事件this.name=$event.target.value
data更新触发re-render
--------------------------------------computed特点
缓存,data不变不会重新计算
提高性能
--------------------------------------为什么组件data必须是一个函数
避免变成全局变量
vue文件是一个类
--------------------------------------ajax请求放在哪个生命周期
mounted
JS是单线程的,ajax异步获取数据
放在mounted之前没有用,只会让逻辑更加混乱
----------------------------------------如何将组件所有props传递给子组件
$props
细节知识点,优先级不高
--------------------------------如何自己实现v-model
<input type="text" :value="text" @input="$emit(‘change‘,$event.target.value)">
export default{
model:{
prop:"text",
event:"change"
},
props:{
text:String
}
}
----------------------------------多个组件有相同的逻辑,如何抽离
mixin
---------------------------------异步组件
----------------------------------何时使用keep-alive
缓存组件,不需要重复渲染
如多个静态tab页的切换
优化性能
----------------------------------何时使用beforeDestory
解绑自定义事件event.$off
清除定时器
解绑自定义的Dom事件,如window scroll等,vue定义的事件无需手动解除
---------------------------------------什么是作用域插槽
子组件
使用组件时
{{slotProps.website}}
-------------------------------------vuex中action和mutation有何区别
action中处理异步,mutation不可以
mutation做原子操作(一次只做一件事件)
action可以整合多个mutation
------------------------------Vue-router常用的路由模式
hash默认
H5 history(需要服务端支持)
两者实现原理
如何配置router异步加载
请用vnode描述一个dom结构
(标签tag、子元素、属性)具体翻看视频
监听data变化的核心API是什么
Object.defineProperty(不能监听数组变化,proxy可以)
以及深度监听、监听数组(监听数组通过重写改变数组的方法,改变数组的原型)
------------------------------描述响应式原理
监听data变化
组件渲染和更新的流程
-------------------------------diff算法的时间复杂度
O(n)
------------------------------简述diff算法过程
patch(elem,vnode) 和patch(vnode,newVnode)
patchVnode和addVnodes和removeVnodes
updateChildren
----------------------------vue为何是异步渲染,$nextTick有什么用
异步渲染(以及合并data修改),以提高渲染性能
$nextTiCK在DOM更新完之后,触发回调
--------------------------------Vue常见性能优化
合理使用v-show和v-if
合理使用computed
v-for使用时加key,以及避免和v-if同时使用
自定义事件、DOM事件及时销毁
合理使用异步组件
合理使用keep-alive
data层级不要太深
使用vue-loader在开发环境下做模板编译(预编译)
----------------------------------webpack层面优化
----------------------------通用性能优化
图片懒加载
------------------------使用SSR
*************************************************vue3
------------------------------Vue3比vue2有什么优势
性能更好
体积更小
更好的ts支持
更好的代码组织
更好的逻辑抽离
更多新功能
----------------------------描述Vue3生命周期
OptionsAPI的改变
beforeDestroy改为beforeUnmount
destroyed改为unmounted
其余沿用Vue2的生命周期
import {onBeforeMount、onMounted。。。。} from ‘vue‘
setup(){ //在beforecreate之后,create之前执行.
console.log(‘setup‘)
onBeforeMount(()=>{
console.log(‘‘)
})
onMounted(()=>{
})
onBeforeUpdate(()=>{
})
onUpdate(()=>{
})
onBeforeUnmount(()=>{
})
onUnmounted(()=>{
})
}
-----------------------如何看待Composition API和Options API
Composition API带来了什么?
更好的代码组织
更好的逻辑复用(有一道专门的面试题)
更好的类型推导
Composition API和Options API如何选择
小项目:Options API
复杂项目:Composition API
别误解Composition API
Composition API属于高阶技巧,不是基础必会
Composition API是为解决复杂业务逻辑而设计的
Composition API就像Hooks在React中的地位
--------------------------如何理解ref toRef和toRefs
ref:
生成值类型的响应式数据
可用于模板和reactive
通过.value修改值
<template>
<p> {{nameRef}} {{state.name}}</p>
</template>
<script>
import{ref,reactive} from ‘vue‘
export default{
name:‘Ref‘,
setup(){
const ageRef=ref(20)
const nameRef=ref(‘ccc‘)
const state=reactive({
name:nameRef
})
return {
nameRef,
state
}
}
}
</script>
Ref变量尽量用ref后缀
两个场景不能用.value{
1.模板
2.reactive
}
ref另外一个使用场景:获取元素
<template>
<p ref="elemRef">xxx</p>
</template>
<script>
import {ref,onMounted} from ‘vue‘
export default{
name:‘RefTemplate‘,
setup(){
const elemRef=ref(null)
onMounted(()=>{
console.log(elemRef.value.innerHtml,elemRef.value)
})
return elemRef
}
}
</script>
------------------------------toRef
针对一个响应式对象(reactive封装)的prop
创建一个ref,具有响应式(toRef不能用于普通对象(非响应式对象),产出的结果不具备响应式)
两者保持引用关系
setup(){
const state=reactive({
age:20,
name:"双越"
})
const ageRef=toRefs(state,‘age‘)
setTimeout(()=>{
state.age=25
})
setTimeout(()=>{
ageRef.value=30
})
return {
state,
ageRef
}
}
-------------------------------------toRefs
将响应式对象(reactive封装)转换为普通对象
对象的每个prop都是对应的ref
两者保持引用关系
setup(){
const state=reactive({
age:20,
name:‘双月‘
})
const stateAsRefs=toRefs(state)
return stateAsRefs
}
如果直接解构reactive.state里面的数据出来用的话会丢失响应式
下面可以实现响应式,使用toRefs
function useFeatureX(){
const state=ive({
x:1,
y:2
})
}
return toRefs(state)
setup(){
const {x,y}=useFeatureX()
return{
x,y
}
}
------------------------------------------------总结最佳使用:
用reactive做对象的响应式,用ref做值类型响应式
setup中返回toRefs(state),或者toRef(state,‘xxx‘)
ref的变量命名都用xxxRef
合成函数返回响应式对象时,使用toRefs
-----------------------------------为什么使用ref
返回值类型会丢失响应式
如在setup、computed、合成函数,都可能返回值类型
Vue如不定义ref,用户将自造ref,反而混乱
computed返回的是响应式数据
retrun{
...state //解构后变成普通值类型
}
------------------------------------为什么需要.value
ref是一个对象(不丢失响应式,value存储值)
通过.value属性的get和set实现响应式
用于模板、reactive时,不需要.value,其他情况都需要
----------------------------------为什么需要toRef和toRefs
初衷:在不丢失响应式的情况下,把对象数据分解、扩散
前提:针对的是响应式对象(reactive封装的)非普通对象
注意:不创造响应式,而是延续响应式
--------------------------vue3升级了哪些重要的功能
createApp
------------vue2
const app=new Vue({})
--------------vue3
const app=Vue.createApp({})
-------------vue2
Vue.use()
Vue.mixin()
-------------vue3
app.use()
app.mixin()
----------------------------------emits属性
<HelloWorld :msg="msg" @sayHello="sayHello" />
export default{
name:‘HelloWorld‘,
props:{
msg:String
},
emits:[‘sayHello‘], //这里需要声明子组件emit的方法
setup(props,{emit}){
emit(‘sayHello‘,‘bbb‘)
}
}
----------------------------------多事件
<button @click="one($event),two($event)">
--------------------------------Fragment
vue3中template可以不写根节点
---------------------------------移除.sync
vue2:
vue3:
----------------------------异步组件的写法
vue3:
components:{
AsyncComponent:defineAsyncComponent(()=>{
import(‘./components/AsyncComponent.vue‘)
})
}
-----------------------------------移除filter
----------------------------------Teleport
弹窗例子
-------------------------------Suspense
---------------------------Vue3如何实现响应式
---------------------------watch和watchEffect区别是什么
watch需要指定监听属性名
watchEffect不需要
---------------------------------setup中如何获取组件实例
const instance=getCurrentInstance()
---------------------------------Vue3为什么比Vue2快
----------------------------------Proxy响应式
PatchFlag
编译模板时,动态节点做标记
标记,分为不同的类型,如TEXT PROPS
diff算法,可以区分静态节点,以及不同类型的动态节点
----------------------------------hoistStatic
将静态节点的定义,提升到副作用域,缓存起来
多个相邻的静态节点,会被合并起来
典型的拿空间换时间
----------------------------------cacheHandler
缓存事件
-------------------------------------SSR优化
静态节点直接输出,绕过vdom
动态节点,还是需要动态渲染
-----------------------------tree-shaking
-------------------------------------Vite是什么
打包工具
开发模式下无需打包,使用ES6 Module
Composition API和React Hooks的对比
------------------------------------------webpack
webpack基本配置:
拆分配置和merge
启动本地服务
------------------------------webpack使用chunks参数配置多个html入口文件
---------------------------抽离css文件
使用MiniCssExtractPlugin
----------------------------抽离公共代码和第三方代码
---------------------------异步加载(懒加载)
import(‘./dynamic-data.js‘).then(res=>{
console.log(res.default.message)
})
这个也是定义了一个chunk
---------------------------处理jsx
在.babelrc里面配置
--------------------------处理vue
vue-loader
----------------------module chunk bundle的区别
module-各个源码文件,webpack中的一切皆模块
chunk-多个模块合并成的,如entry、import()、splitChunk
bundle-最终的输出文件
------------------------------webpack性能优化
--------------------优化babel-loader
{
test:/.js$/,
use:[‘babel-loader?cacheDirectory‘],
include:path.resolve(__dirname,‘src‘),
//排除范围,include和exclude 两者选一个即可
//exclude:path.resolve(__dirname,‘node_modules‘)
}
---------------------------IgnorePlugin
在webpack.config.js里面的plugin目录设置
//忽略moment下的/locale目录
new webpack.IgnorePlugin({
resourceRegExp: /^./locale$/,
contextRegExp: /moment$/,
})
在引入时间转换函数那里手动引入语言包
import ‘moment/locale/zh-cn‘
moment.locale(‘zh-cn‘)
-----------------------------------noParse
在一些已经打包过的文件就无需重复打包
module.exports={
module:{
noParse:[/react.min.js$/]
}
}
IgnorePlugin vs noParse
IgnorePlugin直接不引入,代码中没有
noParse引入,但不打包
-------------------------------happyPack
JS单线程,开启多进程打包
提高构建速度(特别是多核CPU)
在rules里面
{
test:/.js$/,
use:[‘happypack/loader?id=babel‘]
include:srcPath
}
在Plugin里面
new HappyPack({
id:‘babel‘,
loaders:[‘babel-loader?cacheDirectory‘]
})
--------------------ParallelUglifyPlugin多进程压缩JS
webpack内置Uglify工具压缩JS,但是是单线程的
开启多进程压缩更快
和happyPack同理
---------------------------------关于开启多进程
项目较大,打包较慢,开启多进程能提高速度
项目较小,打包很快,开启多进程会降低速度
---------------------自动更新(devserver已经开启了,这里过一下就行)
module.export={
watch:true,
watchOptions:{
ignored:/node_modules/,
aggregateTimeout:300,
poll:1000
}
}
---------------------热更新
自动刷新:整个网页全部刷新,速度较慢,状态会丢失
热更新:新代码生效,网页不刷新,状态不丢失
配置:
devServer:{
hot:true
}
Plugin:new HotModuleReplacementPlugin()
entry:{
index:[
‘webpack-dev-server/client?http://localhost:8080‘,
‘webpack/hot/dev-server‘,
path.join(srcPath,‘index.js‘)
]
}
index.js:
if(module.hot){
module.hot.accept([‘./math‘],()=>{
const sumRes=sum(10,20)
console.log(‘sumRes in hot‘ ,sumRes)
})
}
------------------------------DllPlugin动态链接库插件
webpack已内置DllPlugin
DllPlugin-打包出dll文件
DllReferencePlugin-使用dll文件
只能用于开发环境
例如把react和react-dom打包成dll,不需要重复打包
webpack优化构建速度(可用于生产环境)
1.优化babel-loader(缓存,明确优化范围)
2.IgnorePlugin
3.noParse
4.happyPack
5.ParallelUglifyPlugin
webpack优化构建速度(不用于生产环境!)
自动刷新
热更新
DllPlugin
webpack性能优化-产出代码
1.体积更小
2.合理分包,不重复加载
3.速度更快,内存使用更小
手段:
小图片base64编码
bundle加hash
懒加载
提取公共代码
IngorePlugin
使用CDN加速
使用production(环境是生产环境,会自动开启代码压缩等,自动删除调试代码,自动开启tree-sharing【ES6 Module才能让tree-shaking生效,commonjs就不行】)
ES6 Module和Commonjs区别
ES6 Module静态引入,编译时引入
Commonjs动态引入,执行时引入
只有ES6 Module才能静态分析,实现Tree-Shaking
-----------------------Scope Hosting
代码体积更小
创建函数作用域更少
代码可读性更好
引入插件 new ModuleConcatenationPlugin()
-----------------------------------babel
环境搭建
.babelrc配置
presets和plugins
--------------------------------babel-polyfill
corejs是大部分babel-polyfill的集合
regenerator是corejs里面没包含的函数补丁库
-------------------------------babel-polyfill如何按需引入
const sum=(a,b)=>a+b
Promise.resolve(100).then(data=>{
})
{
"presets":[
[
"@babel/preset-env",
{
useBuiltIns:"usage",
"corejs":3
}
]
]
}
-------------------babel-polyfill的问题
污染全局变量
-----------------------------balel-runtime
解决污染全局变量的问题
---------------------前端为什么要打包构建
1.体积更小
编译高级语言或语法
兼容性和错误检查
2.统一、高效的开发环境
统一的构建流程和产出标准
集成公司构建规范
------------------------------babel和webpack区别
babel关心语法编译
webpack关心打包构建
-------------------------------如何产出一个lib
output:{
filename:‘lodash.js‘
path:distPath,
library:‘lodash‘
}
webpack如何实现懒加载import()、结合vue异步组件、异步路由
为什么proxy不能被Polyfill
proxy的功能无法模拟