vue全家桶

178 阅读14分钟

vue2和vue3区别

vue3有更好的ts支持,打包大小比vue2减少41%,初次渲染和整体渲染快

双向绑定原理不同(响应式原理)

  • Vue2.0实现响应式(双向数据绑定)的原理是通过 Object.defineProperty 来劫持各个属性的getter、setter,在数据变动时发布消息给订阅者,触发相应的监听回调

    • 基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式('push','pop','shift'等方法)。
    • Object.defineProperty 无法检测到对象属性的添加和删除 。
    • 由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让Vue将它转换为响应式(核心API Object.defineProperty)。
    • 深度监听需要一次性递归,对性能影响比较大
       function observer(target){
           if(typeof target !== 'object' || target === null)return target
           // 如果是数组类型,重写数组原型的方法("push","pop","shift","unshift","splice")
           if(Array.isArray(target)) target.__proto__ == arrProto;
      
           // 如果是对象,遍历对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter
           for(let key in target){
               defineReactive(target,key,target[key])
           }
       }
      
      
  • vue3用proxy+Reflect对数据进行处理监听整个对象的变化

    • 解决了数组无法通过下标修改,无法监听到对象属性的新增和删除的问题。也提升了响应式的效率
    • 基于Proxy和Reflect,可以原生监听数组,可以监听对象属性的添加和删除。
    • 不需要一次性遍历data的属性,可以显著提高性能

深入回答:vue3并不是完全抛弃了defineProperty,通过reactive定义的响应式数据使用proxy包装出来,而ref还是用的defineProperty去给一个空对象,定义了一个value属性来做的响应式

数据响应式实现方式不同

  • vue2数据写在data中就可以实现响应式
  • vue3用ref,reactive实现
ref,reactive
  • ref通过Object.defineProperty()的get和set方法实现数据代理

  • reactive是使用ES6的Proxy对象来实现数据代理

  • ref定义的数据可以是基本数据类型也可以是引用数据类型,、返回一个包含value属性的对象,通过.value去访问具体的值

  • reactive一定义的数据只能是对象或数组或者像map,set这样的集合类型,、返回一个响应式的Proxy对象、可以直接去访问对象的一些属性

一般定义对象和数组用ref还是reactive是根据赋值方式决定的:

  • 直接赋值用ref(赋值用reactive的话,会让数据失去响应式proxy{})

  • 修改数据用reactive,和ref没区别,ref的话会多个.value

    • ref得到的变量必须.value赋值,不然等于把ref变成了普通的数据,失去响应式
    • ref的值如果是对象,里面的对象是响应式的,因为引用类型会先包装成proxy再赋值,所以ref的值如果是对象,可以修改其中的属性而引发响应式
    • 使用 ref() 函数可以替换整个对象实例,但是在使用 reactive() 函数时就不行:
// 无效 - x 的更改不会被 Vue 记录
let x = reactive({name: 'John'})
x = reactive({todo: true})

// 有效
const x = ref({name: 'John'})
x.value = {todo: true}

截屏2024-07-30 11.16.05.png

截屏2024-07-30 11.16.40.png reactive会先判断数据是不是引用类型,是的话就new Proxy,在设置Proxy的get和set,get进行依赖收集,set修改值,触发依赖更新

根结点个数不同

  • vue2不支持碎仅div
  • vue3引入了碎片Fragments,允许有多个根结点,减少不必要的嵌套

api类型不同

  • vue2用选项类型api(optionsApi),在代码中分割了不同的属性:dat,methods,computed等,使用生命周期和方法可直接引用
tree-shaking按需引入
  • vue3用组合式api(composition Api)更简洁,方便逻辑更加的聚合使用、使用生命周期和方法需要先引入、可以更好tree-shaking

    • 组合式api的写法下源码改成了函数式编程、方便按需引入、因为tree-shaking功能必须配合按需引入,所以vue3更好的配合tree-shaking能打包体积更小
    • 因为改成组合式api,所有没有this

生命周期不同

vue2中:

  1. beforeCreate: 实例初始化
  2. created: 实例的数据观测、事件等已经初始化完成
  3. beforeMount: 相关的render函数首次被调用
  4. mounted: 挂载后调用,

在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之
注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick

5、beforeUpdate: 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
6、updated:组件更新完成后调用,此时虚拟 DOM 已重新渲染并应用补丁 7、activated: 被 keep-alive 缓存的组件激活时调用。 8、deactivated:被 keep-alive 缓存的组件失活时调用。
9、beforeDestroy: 实例销毁之前调用。
10、destroyed: 实例销毁后调用
11、errorCaptured 在捕获一个来自后代组件的错误时被调用

  • vue3中:

Vue 3在Composition API中引入了新的生命周期钩子,它们以on开头,并且是在setup()函数中使用的:

  1. setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
  2. onBeforeMount() : 组件挂载到DOM之前调用;
  3. onMounted() :组件挂载完成后调用,此时可以访问到DOM元素;
  4. onBeforeUpdate(): 组件更新之前执行的函数;
  5. onUpdated(): 组件更新完成之后执行的函数,虚拟DOM重新渲染和打补丁后调用;
  6. onBeforeUnmount(): 组件卸载之前执行的函数;
  7. onUnmounted(): 组件卸载完成后执行的函数;
  8. onActivated(): 被包含在 <keep-alive> 中的组件,会多出两个生命周期钩子函数,被激活时执行;
  9. onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
  10. onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数
  • beforeCreate --> setup(()=>{})
  • created --> setup(()=>{})
  • beforeMount --> onBeforeMount(()=>{})
  • mounted --> onMounted(()=>{})
  • beforeUpdate --> onBeforeUpdate(()=>{})
  • updated --> onUpdated(()=>{})
  • beforeDestroy --> onBeforeUnmount(()=>{})
  • destroyed --> onUnmounted(()=>{})
  • activated --> onActivated(()=>{})
  • deactivated --> onDeactivated(()=>{})
  • errorCaptured --> onErrorCaptured(()=>{})

总结: Vue2和Vue3钩子变化不大,beforeCreate 、created  两个钩子被setup()钩子来替代、卸载改成unmount

setUp
  1. 是一个语法糖,,组件中用到的数据,方法,生命周期都要写在setup中,
  2. 返回值:如果返回的是对象,对象中的属性和方法可以直接在template中使用、若返回的是一个函数,就可以自定义函数的内容
  3. 在beforeCreate之前执行一次,是领先所有钩子执行的
  4. setup的参数,props值为对象,组件外部传递过来,组件内部声明并接受

diff算法不同

v2使用的是双端算法,v3使用的是快速diff算法

静态节点标记PatchFlag

增加了静态节点标记。会标记静态节点,不对静态节点进行比对。从而增加效率 image.png image.png 不同动态节点的值时不一样的,这样的目的就是为了区分静态节点以及不同类型的动态节点

深入回答标记策略:在文本节点中文本内容为变量会标记为1,属性为动态会标记为2,如果静态则不标记跳过比对

在vue2中,diff算法执行时,不管你是静态节点还是动态节点,都会进行比较。但是vue3的话就只会比较patchflag标记的节点,跳过了静态节点的比较,大大节省了时间

存放公共数据和生态系统不同

  • vue2用vuex和webpack
  • vue3用pina和vite

v-if和v-for优先级不同

  • vue2中v-if低于v-for的优先级
  • vue3中v-if高于v-for的优先级

watch,computed的不同

复用逻辑提取方式不同

  • vue3不推荐使用mixin进行复用逻辑提取,而是推荐写成hook、因为mixin写法更贴合选项时api、和组合式api就没办法配合
    • mixin 重复出现的数据和操作逻辑会提取出来到一个文件里.mixin
    • hook使用方式,创建一个文件,哪里需要使用时引入该文件即可

v-model

  • v-model应用于组件时,监听的事件和传递的值改变

  • v-model主要是用在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素

  • v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将 Vue 实例的数据作为数据来源

  • v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

    • text 和 textarea 元素使用 value property 和 input 事件
    • checkbox 和 radio 元素使用 checked property 和 change 事件
    • select 元素使用 value property 和 change 事件
    • 注意:v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件
  • 相同点

用在表单上都是给input绑定一个 value 值和 input 事件(默认情况)

  • 不同点(主要用在自定义组件上)

vue2

  1. v-model 只能使用一次
  2. 默认传递 vulue属性,接受 input 事件
  3. 默认传递的属性和事件可通过 model 选项进行修改

vue3

  1. v-model 只能使用一次,但可以在v-model后添加参数可使用多次(因为去除了.sync修饰符,但把相应功能合并到了 v-model 上,所以可以使用多次)
  2. 默认传递 modelValue 属性,接受 update:modelValue 事件,当在v-model后添加参数时如v-model:myPropName,则传递为 myPropName 属性,接受 update:myPropName事件
  3. 默认传递的属性和事件无法修改,必须是modelValue 和 监听update:modelValue事件
<input type="text" v-model="searchText">

等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
    
    
<input type="radio" id="man" value="Man" v-model="sex" />
等同于
<input type="radio" id="man" value="Man" :checked="sex === 'Man'" @change="handleSexChange" />

vue3中父组件通过v-model:属性名=‘属性值’的形式给子组件传递数据

<Son v-model:num = 'num'></Son>

子组件通过defineProps定义接受的数据

const props = defineProps({
num:{
type:Number,
default:0
    }
})

子组件要修改父组件传来的数据:通过$emit事件去出发父组件修改 修改时定义的一个事件名是写死的,必须是update:属性名

截屏2024-07-30 16.14.35.png

  • 用在vue2上时
 父组件:
<!-- Test.vue -->
<CustomInput v-model="searchText" />

等同于
<CustomInput
  :value="searchText"
  @input="newValue => searchText = newValue"
/>
    
    
子组件:
<!-- CustomInput.vue -->
<template>
  <input type="text" :value="value" @input="handleInput($event)" />
</template>
<script>
export default {
  props: {
    value: String//必须定义value属性,否则input元素无法使用
  },
  methods: {
    handleInput (event) {
      this.$emit('input', event.target.value)//需要向外派发 input 事件去更新 value 值
    }
  }
}
</script>
    
如果想在 CustomInput.vue 中使用v-model,则需要定义同时具有 getter 和 setter 的计算属性
get 方法需返回 value prop,而 set 方法需触发相应的事件    


<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>

<!-- CustomInput.vue -->
<template>
  <input type="text" v-model="customValue" />
</template>
<script>
export default {
  props: {
    value: String
  },
  computed: {
    customValue: {
      get () {
        return this.value
      },
      set (value) {
        this.$emit('input', value)
      }
    }
  }
}
</script>   
如果在 CustomInput.vue 中我们不想使用默认 props 里的 value 名和 触发 input事件,那我们可以使用 model 选项,它可以通过 prop 自定义 value 字段名并通过 event 自定义触发的 事件名    
 
<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>

<!-- CustomInput.vue -->
<template>
  <div>
   <input type="text" :value="customValue" @input="handleInput($event)" />
  </div>
 </template>
<script>
export default {
  model: {
    prop: 'customValue',
    event: 'customInput'
  },
  props: {
    customValue: String
  },
  methods: {
    handleInput (event) {
      this.$emit('customInput', event.target.value)
    }
  }
}
</script>
  • v-model用在vue3组件 默认情况下 vue2 的 v-model 用在表单上 和 用在组件上 表现形式是一样的。都是 :value 和 @input,但在vue3上 v-model的表现就不一样了 用在表单上是:
 <input v-model="searchText" />
 等同于
 <input
  :value="searchText"
  @input="searchText = $event.target.value"
/>
    
**而当使用在一个组件上时,v-model 会被展开为如下的形式:**
 <CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

ts更好地配合

toRef 和 toRefs

toRef 和 toRefs 是 Vue3 中的响应式转换工具函数,它们的存在主要有以下两点原因:

  1. 在 Vue 中,直接使用响应式对象的属性可以实现属性的双向绑定和响应式更新。但是有时候需要将某个属性提取出来作为独立的 ref 对象,这样可以在不影响源对象的情况下,对属性进行单独的访问和修改。toRef 函数正是为了解耦属性的关联,将属性转换为一个独立的 ref 对象。
  2. Vue 的组件系统中,父组件向子组件传递属性时,需要将这些属性声明为响应式对象。但是,如果直接将整个响应式对象传递给子组件,子组件无法通过解构或者直接访问每个属性。这时,toRefs 函数就可以将整个响应式对象转换为一个普通对象,每个属性都是独立的ref对象,子组件可以轻松解构和访问这些属性

toRef 和 toRefs 就是用来创建响应式的引用的,主要用来取出响应式对象里的属性,或者解构响应式对象,解构出来的属性值依然是响应式属性,如果不用 toRef 或者 toRefs,直接解构会丢失响应式效果

<script setup>
  import { reactive } from 'vue';

  let info = reactive({
    name: 'Echo',
    age: 26,
    gender: 'Male',
  })

  // 这样解构会丢失响应式效果
  let { name, age, gender } = info;
  
  let age = toRef(info, 'age');//单个属性
  
  let { name, age, gender } = toRefs(info);//多个属性
</script>

  • toRef

    • toRef 函数可以将一个响应式对象的属性转换为一个独立的 ref 对象。
    • 返回的是一个指向源对象属性的 ref 引用,任何对该引用的修改都会同步到源对象属性上。
    • 使用 toRef 时需要传入源对象和属性名作为参数
  • toRefs

    • toRefs 函数可以将一个响应式对象转换为一个普通的对象,该对象的每个属性都是独立的 ref 对象。
    • 返回的对象可以进行解构,每个属性都可以像普通的 ref 对象一样访问和修改,而且会保持响应式的关联。
    • toRefs 的使用场景主要是在将响应式对象作为属性传递给子组件时,确保子组件可以正确地访问和更新这些属性。
  • 相同点

    • toRef 和 toRefs 都用于将响应式对象的属性转换为 ref 对象。
    • 转换后的属性仍然保持响应式,对属性的修改会反映到源对象上。
    • 不管是使用 toRef 还是 toRefs 将响应式对象转成普通对象,在 script 中修改和访问其值都需要通过 .value 进行。
  • 不同点

    • toRef 修改的是对象的某个属性,生成一个单独的 ref 对象。
    • toRefs 修改的是整个对象,生成多个独立的 ref 对象集合。
    • toRefs 适用于在组件传递属性或解构时使用,更加方便灵活,而 toRef 更适合提取单个属性进行操作

v-router

模式

  • hash:使用window.location.hash属性以及onhashchange事件监听浏览器地址hash值的变化,仅hash符号前的内容被包含在请求中,改变hash不会重新加载页面,对后端来说不会有404,hash变化会增加一个访问历史记录,push(),replace()
  • history:发生改变时只改变路径不刷新页面
    • history对象保存所有访问的页面,history.length,pushstate新增,replacestate替换,history.go(-2/2)后退/前进、back后退,forWord前进
    • 该模式下刷新页面无法从服务端匹配到静态资源,因路由分两种,前端路由和后端路由,若是该history路由只是改变了地址的显示,并未发请求,但刷新就会发送get请求,由于后端未配置相应路由就返回404

路由守卫

  • 全局前置守卫:beforEach(to,form,next)=>{next()}跳转前操作、next是正常跳转,不写就不会跳转,可用在验证用户访问权限
  • 全局后置守卫:afterEach()跳转后操作,可用在路由切换将页面滚动位置,返回到顶部window.scrollTo(0,0),页面跳转后判断当前页面宽度大小后是否隐藏侧边栏
  • 路由独享守卫:beforeEnter(),在路由配置中定义,只有访问到此路由时才触发
  • 组件内守卫
    • beforRouterEnter(){}在渲染该组件对应路由被调用,不能获取组件实例this,因执行前组件实例还没被创建
    • beforRoutundate(){}当前路由改变,但该组件被复用时可调用this,动态参数的路径/foo/:id
    • beforRouteLeave(){}离开该组件时可调用可访问this,可处理不未保存时或正在会议中离开的情况

路由传参

push传参的区别

生命周期的变化

Vue 3引入了两个新的调试钩子onRenderTracked和onRenderTriggered。
在Vue 3中,销毁相关的生命周期钩子从beforeDestroy和destroyed变为onBeforeUnmount和onUnmounted,以保持与挂载钩子的一致性。

什么是虚拟Dom

真实dom:操作真实dom以及后续更新,如果没有虚拟dom的话就会全量更新整个dom树,但是真实dom操作是很消耗资源的,虚拟dom就是为了提高性能的

虚拟dom是相对于游览器所渲染出来的真实dom的、用js按照dom结构来实现的一个树形结构,这个树形结构可以完全表现真实dom的每个dom节点以及dom节点的属性真实dom 有了虚拟dom后,每次dom的更改就变为了js对象的属性的更改,这样一来就能查询js对象的属性变化要比查询dom树的性能开销小

那虚拟dom是怎么解决这个问题的?diff算法 通过依赖更新触发,然后生成新的虚拟dom,将新的虚拟dom传入path中,通过diff算法对比新旧两个虚拟dom,如果虚拟dom没有改变则不操作,改变了就操作真实dom,,减少了dom操作,只会操作以及改变的dom

  • 为什么说虚拟dom可提高性能 虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没必要的dom操作来提高性能

当数据发生变化时,vue是怎么更新节点的? 先根据真实dom生成一个虚拟dom,当虚拟dom某个节点的数据改变后会生成一个新的节点,然后新旧节点做对比,发现有不一样的地方就直接修改在真实的dom上,然后使旧节点的值为节点

diff的过程就是就是调用名为patch函数,比较新旧节点,一边比较一边给真实的dom打补丁

1、vue项目的路由权限是怎么开发的?
2、如何做大文件上传的功能?
3、单点登录是怎么做的?
4、refresh_token的逻辑是怎么写的?
5、如何使用自定义指令控制按钮级别的权限?
6、websocket有没有接触过?
7、说一下websocket的心跳检测和断线重连。
8、有没有封装过npm组件库?
9、有没有做过项目代码优化和打包优化?
10、大屏可视化如何实现屏幕自适应? 强缓存和协商缓存分别什么时候用

BFC简单介绍一下 promise和async await的关系

image.png

axios

juejin.im/post/5b0ba2…

juejin.im/post/5a5f1c…

juejin.im/post/5b55c1…

juejin.im/post/684490…

juejin.im/post/684490…

juejin.im/entry/68449…

juejin.im/post/684490…

案例:www.jianshu.com/p/c9e05ac3a…

axios内部运作流程 image.png

原生ajax

  • 优点:局部更新;原生支持
  • 缺点:可能破坏浏览器后退功能;嵌套回调 原生ajax及4个步骤和过程:
var xhr = new XMLHttpRequest();//1、创建异步对象 
    xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");//2、设置请求基本信息,并加上请求头
    xhr.open('post', 'test.php' ,'true');//3、配置用get还说post请求,发送的url请求是否是异步
    xhr.send('name=Lan&age=18');//4、发送请求
    xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {// 这步为判断服务器是否正确响应
       console.log(xhr.responseText);} };//获取异步调用返回的数据

手写ajax:

  function ajx(url) {
        const p = new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest() //初始化一个实例
            xhr.open('GET', url, true) //true是开启异步请求,false是同步请求
            chr.onreadystatechange = function () {
                if (xhr.readystate === 4) {
                    //0未初始化,还未调用send()、1载入,已调send(),正在发送请求,2载入完成,send()执行完成,已接受全部响应内容,3交互:正在解析响应内容还未发送回来,4完成:响应内容解析完成,可在客户端调用
                    if (xhr.status === 200) {
                        //状态码,2XX:成功处理请求,3xx需要重定向,浏览器直接跳转,4xx客户端请求错误,5xx服务端错误
                        resolve(JSON.parse(xhr.responseText))
                    } else if (xhr.status === 404) { reject(new Error('404 not found')) } }} 
xhr.send(null)
})
        return p }
    const url = '/data/test.json'
    ajx(url).then(res => console.log(res)).catch(err => console.log(err))

jqueryAjax

在原生的ajax的基础上进行了封装;支持jsonp

var loginBtn =  document.getElementsByTagName("button")[0];
loginBtn.onclick = function(){
    ajax({
        type:"post",
        url:"test.php",
        data:"name=lan&pwd=123456",
        success:function(data){
           console.log(data);
        }
    });
}

fetch

  • 优点:解决回调地狱
  • 缺点:API 偏底层,需要封装;默认不带Cookie,需要手动添加; 浏览器支持情况不是很友好,需要第三方的ployfill
fetch('http://www.mozotech.cn/bangbang/index/user/login', {
    method: 'post',
    headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams([
        ["username", "Lan"],["password", "123456"]
    ]).toString()
})
.then(res => {
    console.log(res);
    return res.text();
})
.then(data => {
    console.log(data);
})

axios

  • 特点:
    • 支持浏览器和node.js
    • 支持promise
    • 能拦截请求和响应
    • 能转换请求和响应数据
    • 能取消请求
    • 自动转换JSON数据
    • 浏览器端支持防止CSRF(跨站请求伪造)
axios({
    method: 'post',
    url: '/abc/login',
    data: {
        userName: 'Lan',
        password: '123'
    }
})
.then(function (response) {
    console.log(response);
})
.catch(function (error) {
    console.log(error);
});

同时发起多个请求:

image.png 如何将axios异步请求同步化处理?

//使用 asyns/await 
async getHistoryData (data) {
 try {
   let res = await axios.get('/api/survey/list/', {
     params: data
   })
   this.tableData = res.data.result
   this.totalData = res.data.count
 } catch (err) {
   console.log(err)
   alert('请求出错!')
 }
}

如何中断(取消)axios的请求?

image.png axios怎么解决跨域的问题?几种跨域方式了解一下

借鉴阅读