VUE笔记

 如有错误,欢迎评论区指正(>v<)

ref和reactive区别(字节一面) 

refreactive
创建响应式的基本数据类型创建一个响应式的对象或数组
基于 Object.defineProperty()基于 Proxy 

vue的双向绑定(字节一面)

原理:主要通过数据劫持和发布-订阅者模式实现,也就是说数据发送变化视图跟着改变,视图改变数据也会发送改变

核心:

数据劫持‌:Vue使用Object.defineProperty方法对数据对象的每个属性进行劫持,重写其getter和setter。当属性被访问或修改时,会触发相应的逻辑,从而监控数据变化‌ ‌

发布-订阅者模式‌:当数据变化时,发布者(数据对象)会通知所有订阅者(视图和其他依赖该数据的部分)进行更新。Vue利用Dep类来管理这些订阅者,并在数据变化时通知它们进行更新‌

实现过程

Observer‌:在Vue实例初始化时,通过Object.defineProperty重定义data中的所有属性,监听属性变动。当数据变化时,setter会被触发,通知订阅者进行更新‌

Watcher‌:Watcher负责监听数据变化并更新视图。它通过Dep类管理订阅者,并在数据变化时通知所有订阅者进行视图更新‌

Compiler‌:Compiler解析模板指令,将模板中 的变量替换成数据,并绑定更新函数。当数据变化时,Compiler通知Watcher进行视图更新‌

具体步骤

初始化数据‌:在Vue实例初始化时,通过Object.defineProperty重定义data中的所有属性,设置getter和setter来监听属性变动‌

依赖收集‌:在渲染视图时,访问数据对象的属性会触发getter,将当前的观察者(通常是渲染函数)添加到依赖列表中‌

视图更新‌:当数据变化时,setter会触发Dep类的notify方法,通知所有订阅者进行视图更新‌

Object.defineProperty(obj, prop, descriptor)  

obj:要在其上定义属性的对象

prop:要定义或修改的属性的名称

descriptor:属性的描述符对象,包含以下属性:(具体改变的方法)

  • value:属性的值,默认为undefined
  • writable:属性是否可写,默认为false
  • enumerable:属性是否可枚举,默认为false
  • configurable:属性是否可配置(即是否可以通过delete删除或重新配置),默认为false
  • get和set:获取和设置属性值的函数,使用访问器描述符时使用‌

 实现过程

1.初始化的时候,把data中绑定的数据给他显示到输入框和文本节点里

2.输入框的内容改变,data里对应属性的值也要发生改变

3.data的值改变,那么文本节点的内容也要发生改变  model->view

model->view

修改输入框,改变了vm实例的属性,1-1的关系

data里的数据对应节点,是1对多的关系,利用发布-订阅者模式来解决(发布-订阅者模式就是专门处理一对多的关系,让多个订阅者同时监听一个发布者)

Dep发通知,订阅者触发update,更新视图

文档碎片 documentfragment

把n多个节点放入这个容器里,最后再把容器放回到文档里面,让浏览器只回流了1次

vue中出现数据更新视图不更新的原因是什么?

响应式系统,基于Object.defineproperty()实现,可以追踪对象属性的变化,但是无法检测到某些特殊情况下的变化

  • 直接新增或删除对象的属性

vue无法检测到对象新增、删除的属性,因为它只对初始化时存在的属性进行响应式处理

解决:使用 Vue.setthis.$set 方法来添加响应式属性

  •  直接替换数组索引或长度

vue2没有办法检测到通过索引直接修改数组元素或修改数组长度的操作

解决:使用数组方法(如 push、pop、splice 等)来修改数组,确保 Vue 能够追踪变化

 VUE2和VUE3的区别

VUE2和VUE3区别
区别VUE2VUE3
响应式系统Object.defineproperty()proxy()
API选项式API data methods created组合式API setup ref reactive...
生命周期beforeCreate created加了on onBeforeCreate
支持fragment要求组件只能有一个根节点支持多个根节点(fragment)
hooksmixinshooks
teleport组件编写可以出现在父组件之外的弹窗、模态框 to目标位置
支持tree-shaking不支持去除一些没有使用的代码的技术(按需打包)
获取组件实例thisgetCurrentInstance

VUE3优化

静态树提升,如果有个静态节点树,vue在编译的时候会把它标记为静态,后续的更新中,vue不会再重新渲染这一部分

引入patchflag,vue在更新的时候,只关注已经确定发生改变的部分

mixins和hooks区别

mixinshooks
共享同一个对象,可能导致命名冲突不会有命名冲突问题,每个hooks都有自己的作用域
使组件实例难以理解,一个组件可能从多个mixins继承属性和方法,使得组件逻辑复杂难懂可以单独和组合使用,使得逻辑更加清晰模块化,易于理解和维护
不支持TypeScript的类继承与TypeScript兼容

mixins: 

// mixins.js
export default{
    data(){
        return {
               msg:'hello world'
        }
    },
    created(){
              console.log('123456')
    },
    methods:{
             getters(){
                 console.log(this.msg)
             }
    }
}

//index.vue
<template>
    <div>
        <p>{{msg}}</p>
        <p @click='getters'>获取</p>
    </div>
</template>

 <script>
     import mixins from './mixins'
     export default{
        mixins:[mixins],
     }
</script>

 hooks:

// hooks.js
import {ref} from 'vue'
export function hooks(){
    const error = ref(null)
    function validate(value){
        if(!value){
            error.value = '这个必须有'
        }esle{
             error.value = null
        }
    }

    return { error,validate }
}

// index.vue
<template>
    <form @submit='onSubmit'>
        <input v-model='email' />
        <button type='submit'>提交</button>
        <p v-if='error'>{{error}}</p>
    </form>
</template>

<script>
    import {hooks} from './hooks'
    export default{
        setup(){
            const {error,validate} = hooks
            function onSubmit(){
                validate(email.value)
                if( error.value ){
                    //报错 提示信息
                }else{
                    //提交表单
                }
             }

             return {error , onSubmit}
         }
}
</script>

nextTick

官方:nextTick是等待下一次DOM更新刷新的工具方法

当修改了响应式数据,vue不会立刻更新DOM,而是把更新操作放进异步队列中,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新(Promise.then放入微任务队列中)。当前事件循环结束之后,清空执行栈再处理异步任务,这样可以避免频繁的操作DOM

nextTick是确保DOM更新完成之后执行回调函数,可拿到更新后的dom

原理:利用事件循环,Vue优先选择微任务(当前环境不支持微任务时会回退至宏任务),当DOM更新完成后才会执行nextTick的回调,那么nextTick也就可以拿到更新后的数据

场景:

  • 获取更新后的DOM元素(ex:  created中想要获取DOM)
  • DOM更新后初始化一些第三方库,比如:图表、动画库
  • 响应式数据变化后获取DOM更新后的状态(ex: 获取列表更新后的高度)

v-if和v-show区别

v-if: 通过创建/删除dom元素来控制显隐

v-show: 通过修改css属性display来控制显隐

 VUE的跨域怎么解决的?

env和pro开头的文件确定了生产环境(上线)www.baidu.com和开发环境http://39.100...所请求的域名

生产环境不存在跨域问题,服务器和服务器之前之前不存在跨域问题

产生跨域原因:因为浏览器的同源策略(协议+域名+端口号完全一致)

本质:把需要代理的域名(本地域名)代理到服务器上,就不存在跨域了

JSONP(仅get请求) CORS(后端配置)

方法:

JSONP(script标签不受跨域限制,但仅能进行get请求)

CORS跨域

需要前端后端同时支持,CORS请求在浏览器上完成,关键在于服务器

简单请求:HEAD GET POST

请求头字段有限制,请求由浏览器直接发出,在请求头添加Origin字段(说明请求来自哪个源:协议+域名+端口号),服务端验证

服务端需要配置字段:Access-Control-Allow-Origin(允许的源,*代表都允许)

如果响应数据头信息包含Access-Control-Allow-Origin字段,则允许跨域;如果Orign指定的域名不在许可范围之内,服务器会返回一个正常的HTTP回应,浏览器发现没有上面的Access-Control-Allow-Origin头部信息,就知道出错了。

非简单请求

非简单请求是对服务器有特殊要求的请求,比如请求方法为DELETE或者PUT等。非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,称为预检请求OPTIONS。浏览器会询问服务器,当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则就会报错。

发起OPTIONS请求询问,头信息中的关键字段是Orign,还包含Access-Control-Request-Method、Access-Control-Request-Headers字段,服务器根据头信息这三个字段进行判断

服务端需要配置字段(至少):Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Hesders

如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错

注意:只要服务器通过了预检请求,在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段

组件传值(父->子、子->父、兄弟组件传值、跨组件传值)

父 --> 子

// 父组件

<template>
  <div>
    <ChildComponent :message="parentMessage" /> //这里数据绑定除了String类型,可以赋其他常量
  </div>
</template>

<script setup>
    import { ref } from 'vue';
    import ChildComponent from './ChildComponent.vue';

    const parentMessage = ref('Hello from parent');
</script>



// 子组件

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

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

const props = defineProps({
  message: String
});

//或者 const props = defineProps(['message'])
</script>

 子 -> 父

子组件可以通过 $emit 触发自定义事件,并将数据传递给父组件。父组件监听这些事件并执行相应的处理逻辑

// 父组件

<template>
  <div>
    <ChildComponent @sendMessage="handleMessage" />  //传入函数并监听
    <p>{{ message }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const message = ref('');

const handleMessage = (data) => {
    message.value = data;
};
</script>

// 子组件

<template>
  <button @click="sendMessage">Send Message</button>
</template>

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

const emit = defineEmits(['sendMessage']); // 1

const sendMessage = () => {
  emit('sendMessage', 'Hello from child'); // 2
};
</script>

兄弟组件传值

通过共享的事件总线(Event Bus)实现

// eventBus.js
import { defineComponent } from 'vue';

export const eventBus = defineComponent({
  methods: {
    emit(eventName, data) {
      this.$emit(eventName, data);
    },
    on(eventName, callback) {
      this.$on(eventName, callback);
    }
  }
});


//兄弟组件1
<template>
  <button @click="sendMessage">Send Message</button>
</template>

<script setup>
import { eventBus } from './eventBus.js';

const sendMessage = () => {
  eventBus.emit('sendMessage', 'Hello from sibling 1');
};
</script>


// 兄弟组件2
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { eventBus } from './eventBus.js';

const message = ref('');

eventBus.on('sendMessage', (data) => {
  message.value = data;
});
</script>

 全局状态管理(如 Vuex)实现

// store.js
import { createStore } from 'vuex';

export default createStore({
  state: {
    message: ''
  },
  mutations: {
    setMessage(state, message) {
      state.message = message;
    }
  }
});


// 兄弟组件1
<template>
  <button @click="sendMessage">Send Message</button>
</template>

<script setup>
import { useStore } from 'vuex';

const store = useStore();

const sendMessage = () => {
  store.commit('setMessage', 'Hello from sibling 1');
};
</script>


// 兄弟组件2
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

const store = useStore();

const message = computed(() => store.state.message);
</script>

跨层级组件传值

祖先组件可以通过 provide 提供数据,后代组件可以通过 inject 注入这些数据

// 祖先组件
<template>
  <div>
    <GrandChildComponent />
  </div>
</template>

<script setup>
import { provide, ref } from 'vue';
import GrandChildComponent from './GrandChildComponent.vue';

const ancestorMessage = ref('Hello from ancestor');

provide('ancestorMessage', ancestorMessage);
</script>


// 后代组件
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

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

const message = inject('ancestorMessage');
</script>

 任意组件之间

使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。

父子组件生命周期的执行顺序

父:

        beforeCreate

        created

        beforeMount

子:

        beforCreate

        created

        beforeMount

        mounted

父:

        mounted 

VUE生命周期

VUE2Vue3
beforeCreatesetup
created
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyonUnmounted

VUE的路由模式

hash

开发中默认的模式,使用锚点技术重写URL访问路径(纯静态技术),在原有的URL路径后拼接/#/xx,解决了单页面应用的页面划分,能够在不触发网页重新加载的情况下切换URL路径。hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退

原理: onhashchange()事件,window会监听hash值变化,按照规则加载相应代码

缺陷:hash模式的URL路径只能存在一个#,当嵌套的子应用和主应用都使用hash模式时,在定义URL路径上存在困难。视觉上不美观(逼死强迫症)

history

VueRouter中常用的一种路由模式,history模式使用的URL路径不存在#(不使用锚点技术),视觉上更美观。采用history对象中的pushState()函数重写URL路径

原理:利用点击事件,使用history.pushState重写页面路径,再加载对应DOM对象

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>页面跳转</title>
  <style>
    .page {
      width: 400px;
      height: 400px;
    }

    .about {
      background-color: antiquewhite;
      display: none;
    }

    .index {
      background-color: aquamarine;
      display: none;
    }
  </style>
</head>

<body>
  <!-- 定义路由菜单 -->
  <a href="javascript:jump('/index')">跳转到index页面</a>
  <a href="javascript:jump('/about')">跳转到about页面</a>
  <!-- 定义页面结构 -->
  <div class="page index">我是首页</div>
  <div class="page about">我是关于页面</div>
  <script type="text/javascript">
    // 跳转函数
    function jump(path) {
      // 重写URL路径为超链接传入的名称
      window.history.pushState(null, 'page', path);
      // 获取所有页面组件
      var pages = document.querySelectorAll('.page');
      // 获取指定跳转的目标页面对象
      var newPage = document.querySelector(path.replace('/', '.'))
      console.log(newPage);

      // 隐藏其他页面
      pages.forEach(item => item.style.display = 'none');
      // 展示跳转的页面
      newPage.style.display = 'block';
    }
  </script>
</body>

</html>

缺陷:重写后的新路径中并不包含原有HTML物理文件的访问地址,所以在重写URL路径后,一旦刷新网页会造成404无法访问

解决:上线时,服务器端(配置)将url中的路径重定向为可以找到的物理文件地址

computed和watch区别 

computedwatch
具有只读的响应式的返回值(相当于一个响应式数据的派生数据)侦测变化,执行回调
具备缓存性无缓存
传递对象,既可读又可写传递对象,设置deep(观察深层次变化)、immediate(初始化时立即执行)选项

$route和$router区别 

 $route: “路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数

$router: “路由实例”对象包括了路由的跳转方法,钩子函数...

vuex和pinia区别

区别/状态管理vuexpinia
API需要定义stategettersmutations、actions更简洁,直接通过 actions 修改状态
类型支持需要配置支持ts原生支持ts,
模块管理需要通过模块注册和嵌套来管理状态,更复杂(模块化需额外配置modules)可以直接在模块中定义状态和方法,更方便(自动模块化store即模块)
插件支持较为成熟的插件生态系统相对较少

pinia为什么比vuex快?

  1. pinia的设计更加轻量
  2. pinia的内存使用更加高效(占用内存更小)
  3. 更好的响应式性能:Pinia 基于 Vue 3 的 Composition API 构建,利用了 Vue 3 的新响应式系统。这使得 Pinia 在处理大型状态时更加流畅,减少了滞后现象

虚拟DOM、真实DOM

定义:VUE中的虚拟DOM(Virtual DOM)是一种在内存中表示真实DOM的轻量级JavaScript对象模型‌,可以用来描述真实DOM的结构和状态 ,真实DOM:浏览器提供的原生DOM(documen object model),每次的修改都会触发重绘和回流

作用:避免直接频繁操作真实DOM,提高性能

实现过程:JS对象来模拟DOM树,更新的时候用Diff算法对比新旧虚拟DOM树,然后把差异点应用到真实DOM上

具体实现:

// 创建虚拟DOM(旧虚拟DOM树) 
const virtualDOM = {
    tag:'div',
    attrs:{id:'app'},
    children:[
        {
            tag:'p',
            attrs:{},
            children:['hello,world!']
        }
    ]
}

// 更新虚拟DOM(新虚拟DOM树)
const virtualDOM = {
    tag:'div',
    attrs:{id:'app'},
    children:[
        {
            tag:'p',
            attrs:{},
            children:['更新的数据']
        }
    ]
}

Diff算法

定义:用来优化DOM更新的核心机制。当你使用Vue进行数据绑定和组件更新时,diff算法帮助Vue高效地识别哪些部分的实际DOM需要被改变,从而只更新必要的部分,而不是整个页面或组件。

核心思想:通过比较新旧虚拟DOM树的差异,找到最小变化应用到真实DOM上

特点:

  • 同层级比较
  • 基于key值的节点复用(标签属性tag和key值相同,input要加type属性,均相同则节点相同)
  • 优化策略(提升diff算法的策略 ):首尾指针、双端比较

diff的时机:当组件创建时以及依赖的属性或数据发生变化时,会运行一个函数,该函数会做两件事:

  • 运行_render生成一棵新的虚拟DOM树(vnode tree)
  • 运行_update,传入虚拟DO树的根节点,对新旧两棵树进行对比,最终完成对真实DOM的更新
// vue构造函数
function Vue(){
    // ...其他代码
    var updateComponenet = () => {
        this._update(this._render())
    }
    new Watcher(updateComponent)
    // ...其他代码
}

 diff算法就发生在_update函数的运行过程中

_update函数:收到一个vnode参数(新生成的虚拟dom树),同时_update函数通过当前组件的_vnode属性拿到旧虚拟dom树,_update函数首先给组件的_vnode重新赋值,让它指向新树,然后判断旧树是否存在

  • 不存在:第一次加载组件,通过内部patch函数(diff)直接(深度|广度)遍历新树,为每个节点生成真实DOM,挂载到每个节点的elm属性上
  • 存在:之前已经渲染过该组件,通过patch函数对新旧两棵树进行对比,完成对所有真实DOM的最小化处理,让新树的节点对应合适的真实DOM

 首尾指针、双端比较:(自己理解)

1.新首-旧首比较:相同,都移至下一位继续比较;不同,新尾-旧尾比较

2.新尾-旧尾比较:相同,都移至上一位继续比较;不同,新尾-旧头比较

3.新尾-旧头比较:相同,新节点指向真实dom对应节点且真实dom节点移至对应位置,新尾前移,旧头后移,继续比较;不同,新头-旧尾比较

4.新头-旧尾比较:相同,新节点指向真实dom对应节点且真实dom节点移至对应位置,新头后移,旧尾前移;不同,以新头为准值,在旧dom上寻找是否有该节点,有则新节点指向真实dom对应节点且真实dom节点移至对应位置,无则在真实dom节点对应位置添加新节点

新头在新尾后则结束循环,判断旧树指针指向是否正常,正常则循环(头指针->尾指针),销毁循环内还存在的所有真实dom节点

旧头-新头比较,若相同则将新节点指向真实dom,更新变化的数据  (若有子节点,继续深度优先遍历) --> 旧头、新头后移一位继续比较,若不同 --> 比较旧尾-新尾,若相同则旧尾新尾向前移一位继续比较,若不同 --> 比较旧头-新尾,若相同新尾节点指向真实dom并移动真实dom节点至相对新节点的真实dom节点位置,两个相同节点各自继续向'前'移动 --> 旧头-新头比较,不同,旧尾-新尾比较,不同,旧头-新尾比较,不同,新头-旧尾比较,不同(4次比较) --> 以新头为基准,在旧dom树中寻找是否存在,若存在,该新节点指针指向真实节点并移至对应位置,新头后移一位 --> 新头-旧头不同,新尾-旧尾不同,在旧dom树中寻找是否有该节点,没有,则在真实dom对应位置中新增节点,新头后移,头指针在尾指针后结束循环;判断旧树指针指向是否正常,正常则循环(头指针->尾指针),销毁循环内还存在的所有真实dom节点

key值的作用

每个节点都有唯一的key,那么vue就可以根据key快速的判断节点是否复用,如果没有设置key值VUE默认用index索引值。

总结:

虚拟DOM就是提供了一种高效的 机制来描述和更新真实DOM
    diff算法就是通过同层比较和最小化DOM操作,来优化性能和视图渲染
    key是vue里用来标识节点身份的重要属性,它可以优化vue在diff算法中能更高效的识别节点,避免不必要的DOM操作,如果不设置key或用index,可能会导致性能下降和逻辑出错。

key值都可以使用在哪些地方?

唯一标识:key值的主要作用是为每一个虚拟DOM节点提供一个唯一标识。在Vue.js中,当我们使用v-for指令循环渲染列表时,key值能够帮助Vue区分每个列表项,而不仅仅是根据顺序来判断。

优化渲染性能:特别是在列表的顺序发生变化时。Vue.js使用diff算法来比较新旧虚拟DOM,当key值存在时,Vue能够更高效地判断哪些元素需要更新、添加或删除,从而减少不必要的DOM操作。

保持组件状态: 当组件在列表中被重新排序或替换时,使用key值可以帮助vue.js正确识别和匹配组件,确保每个组件的状态不会丢失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值