# vue2

# Redux和Vuex的区别

redux是一个范用的js库,vuex是专门服务vue的

相同点:

state共享数据 流程一致:定义全局state,触发,修改state 原理相似,通过全局注入store。 不同点:

Vuex定义了state,getter、mutation、action,module五个对象;redux定义了state、reducer、action; Vuex触发方式有两种commit同步和dispatch异步;redux同步和异步都使用dispatch; Vuex中action有较为复杂的异步ajax请求;redux中action中可简单可复杂,简单就直接发送数据对象({type:xxx, your-data}),复杂需要调用异步ajax(依赖redux-thunk插件)。 Redux使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改; Redux在检测数据变化的时候,是通过diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过getter/setter来比较的。

# vuex和pinia的区别

Vuex 和 Pinia 都是 Vue.js 的状态管理库,但它们在设计理念和使用方式上有显著区别。以下是它们的全面对比:

核心区别概览

特性 Vuex Pinia
Vue版本支持 Vue 2 & 3 专为 Vue 3 设计(兼容 Vue 2)
TypeScript支持(*) 需要额外配置 原生完美支持
API设计 较复杂(mutations/actions) 更简单直观(单一 actions)
模块系统 嵌套模块 扁平化模块(stores)
打包体积(*) 较大 (~10KB) 更小 (~5KB)
Composition API(*) 需要额外适配 原生支持
DevTools支持 支持 支持(体验更好)

详细对比

  1. 基本架构差异

Vuex 结构

import { createStore } from 'vuex';

export default createStore({
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    asyncIncrement({ commit }) {
      setTimeout(() => commit('increment'), 1000)
    }
  },
  modules: {
    user: { /* 嵌套模块 */ }
  }
})

Pinia 结构

import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    },
    async asyncIncrement() {
      setTimeout(() => this.increment(), 1000)
    }
  }
})
  1. 状态修改方式

Vuex 要求通过 mutations 同步修改状态:

// Vuex
mutations: {
  increment(state) {
    state.count++
  }
},
actions: {
  incrementAsync({ commit }) {
    commit('increment')
  }
}

Pinia 允许直接修改或通过 actions:

// Pinia - 方式1:直接修改
store.count++

// Pinia - 方式2:通过action
store.increment()
  1. TypeScript 支持

Vuex 需要额外配置类型:

// Vuex的类型定义较复杂
interface State {
  count: number
}

const store = createStore<State>({
  state: () => ({ count: 0 }),
  // ...
})

Pinia 提供自动类型推断:

// Pinia自动推断类型
const useStore = defineStore('store', {
  state: () => ({ coun3t: 0 }), // 自动推断为number
  getters: {
    double: (state) => state.count * 2 // 自动推断返回类型
  }
})
  1. 模块管理

Vuex 使用嵌套模块:

const moduleA = {
  state: () => ({ /* ... */ }),
  mutations: { /* ... */ }
}

const store = createStore({
  modules: {
    a: moduleA
  }
})

Pinia 使用独立 store:

// 每个store都是独立的
const useStoreA = defineStore('storeA', { /* ... */ })
const useStoreB = defineStore('storeB', { /* ... */ })
  1. Composition API 集成

Vuex 在 Vue 3 中使用较繁琐:

import { useStore } from 'vuex'

export default {
  setup() {
    const store = useStore()
    return {
      count: computed(() => store.state.count),
      increment: () => store.commit('increment')
    }
  }
}

Pinia 与 Composition API 完美融合:

import { useCounterStore } from '@/stores/counter'

export default {
  setup() {
    const counter = useCounterStore()
    return { counter }
  }
}

迁移建议(*)

从 Vuex 迁移到 Pinia 的情况

  1. 新项目:优先选择 Pinia
  2. 需要更好的 TS 支持:Pinia 是更好的选择
  3. 简化代码结构:Pinia 的 API 更简洁
  4. 需要更小的包体积:Pinia 体积更小

保留 Vuex 的情况

  1. 大型已有项目:迁移成本可能过高
  2. 需要严格的状态变更追踪:Vuex 的 mutations 提供了明确的历史记录
  3. 团队熟悉 Vuex:没有充分的迁移理由时

性能对比(*)

指标 Vuex Pinia
包大小 ~10KB ~5KB
内存占用 较高 较低
初始加载速度 稍慢 更快
复杂状态操作 性能较好 性能优秀

最佳实践推荐

Pinia 最佳实践

// 推荐的文件结构
stores/
  ├─ index.js        # 主入口
  ├─ user.store.js   # 用户相关状态
  ├─ cart.store.js   # 购物车状态
  └─ product.store.js # 产品状态

// 示例store定义
export const useUserStore = defineStore('user', {
  state: () => ({
    name: 'Guest',
    isAdmin: false
  }),
  actions: {
    async login(username, password) {
      // 异步操作
    }
  }
})

Vuex 优化建议

// 使用模块化
const store = createStore({
  modules: {
    user: {
      namespaced: true,
      state: () => ({ /* ... */ }),
      mutations: { /* ... */ }
    }
  }
})

// 动态注册模块
store.registerModule('dynamic', { /* ... */ })

#

# vue的生命周期

  • 创建阶段(Creation): beforeCreate:在实例创建之前调用,此时数据观测和事件还未初始化。 created:在实例创建完成后调用,可以访问到 data 和 methods 等选项,但尚未挂载到 DOM 上。

  • 挂载阶段(Mounting): beforeMount:在组件挂载之前调用,此时模板编译已完成,但尚未将组件渲染到页面上。 mounted:在组件挂载完成后调用,此时组件已经被渲染到页面上,可以进行 DOM 操作和发送异步请求。

  • 更新阶段(Updating): beforeUpdate:在数据更新之前调用,即响应式数据发生变化时。 updated:在数据更新完成后调用,此时 DOM 已经重新渲染完成。

  • 销毁阶段(Destruction): beforeDestroy:在组件销毁之前调用,可以进行清理工作,如解绑自定义事件、

    vue2 vue3 执行时机 相关操作
    创建前阶段 beforeCreate setup 在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。 此时实例的data和methods等配置还未初始化
    创建完成阶段 created setup 在实例创建完成后被立即同步调用 实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。模板还没有编译,也就是我们还不能获取到DOM。
    组件挂载前 beforeMounted onBeforeMount 挂载开始之前被调用 render 函数首次被调用,生成虚拟DOM
    组件挂载完成 mounted onMounted 实例被挂载后调用 挂载到真实DOM树
    更新前 beforeUpdate onBeforeUpdate 在数据发生改变后 这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。
    更新完成 updated onUpdated 在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。 不要在updated中修改data数据,很容易造成死循环。
    实例卸载前 beforeDestroy onBeforeUnmount 实例仍然完全可用,通常解除一些全局或者自定义事件。
    实例卸载完成 destroyed onUnmounted 对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
    keep-alive激活状态调用 activated onActivated
    keep-alive失活时调用 deactivated onDeactivated
    捕获一个来自后代组件的错误时被调用。 errorCaptured

# vue3的生命周期

钩子函数 触发时机 对应 Vue2 钩子
setup() 组件初始化时执行(在 beforeCreatecreated 之间),用于设置响应式数据、方法等。 替代 beforeCreate + created
onBeforeMount 组件挂载到 DOM 之前执行(此时模板已编译,但未渲染到页面)。 beforeMount
onMounted 组件挂载到 DOM 之后执行(可访问 DOM 元素)。 mounted
onBeforeUpdate 响应式数据更新,组件重新渲染之前执行(可获取更新前的 DOM 状态)。 beforeUpdate
onUpdated 组件重新渲染之后执行(可获取更新后的 DOM 状态)。 updated
onBeforeUnmount 组件卸载之前执行(可清理定时器、事件监听等资源)。 beforeDestroy
onUnmounted 组件卸载之后执行(所有子组件也已卸载,事件监听等已移除)。 destroyed

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

挂载阶段的执行顺序

在组件的挂载过程中,生命周期钩子的执行顺序如下:

父组件 beforeCreate
父组件 created
父组件 beforeMount
子组件 beforeCreate
子组件 created
子组件 beforeMount
子组件 mounted
父组件 mounted

这种顺序确保了父组件在渲染子组件之前完成必要的初始化工作,而子组件的挂载完成后,父组件才最终完成挂载。

更新阶段的执行顺序

当父子组件的数据发生变化时,更新阶段的生命周期钩子执行顺序如下:

父组件 beforeUpdate
子组件 beforeUpdate
子组件 updated
父组件 updated

这种顺序保证了父组件的数据变化能够及时传递到子组件,并在子组件更新后同步父组件的视图。

销毁阶段的执行顺序

在组件销毁时,生命周期钩子的执行顺序如下:

父组件 beforeDestroy
子组件 beforeDestroy
子组件 destroyed
父组件 destroyed

这种顺序确保子组件的资源和依赖在父组件销毁之前被正确清理,避免潜在的引用问题。

# vue的响应式原理(*)

Vue的响应式原理是指当Vue实例的数据发生变化时,相关的视图会自动更新。这种自动更新的机制是通过Vue的数据劫持和发布-订阅模式实现的。

具体来说,当Vue实例创建时,Vue会对数据进行劫持,即将数据转换为响应式数据。这个过程是通过Object.defineProperty()方法实现的,该方法可以拦截对象属性的读取和修改操作。在Vue中,当数据被访问时,会触发getter函数,当数据被修改时,会触发setter函数。在setter函数中,Vue会通知相关的视图进行更新。

另外,Vue还使用了发布-订阅模式来实现数据的响应式更新。当数据发生变化时,Vue会通知相关的视图进行更新。这个过程是通过Watcher(观察者)和Dep(依赖管理器)两个对象实现的。Watcher对象用于监听数据的变化,当数据发生变化时,会通知Dep对象。Dep对象维护了一个订阅者列表,当数据发生变化时,会通知所有的订阅者进行更新。

notify(通知),

trigger re-render:(触发器重新渲染)

collect as dependency:(收集为依赖项)

注意:Vue 不能检测的情况

  • 对象:

    问题:

    • Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

      解决办法:

      Vue.set(vm.someObject, 'b', 2)
      // or
      this.$set(this.someObject,'b',2)
      
    • 往已有对象上添加多个新的属性

      解决办法:

      this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
      
  • 数组:

    问题:

    • 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

      解决办法:

      Vue.set(vm.items, indexOfItem, newValue)
      
      vm.items.splice(indexOfItem, 1, newValue)
      
      vm.$set(vm.items, indexOfItem, newValue)
      
    • 当你修改数组的长度时,例如:vm.items.length = newLength

      解决办法:

      vm.items.splice(newLength)
      
class Vue{
    constructor(option){
        this.$el = document.querySelector(option.el)
        this.$data = option.data()
        this.proxy()

        // 劫持监听所有属性
        new Observer(this.$data)

        // 解析指令
        new Compiler(this.$el, this.$data)
    };
    // 将 data 中的每一条数据,同步到 this 身上
    proxy(){
        const keys = Object.keys(this.$data);
        keys.forEach(key => {
            Object.defineProperty(this, key, {
                get(){
                    return this.$data[key]
                },
                set(newValue){
                    this.$data[key] = newValue
                }
            })
        })
    }
}

// 一、数据劫持:
// 将 data 中每一条数据,都通过 Object.defineProperty 方法,变成响应式数据
class Observer{
    constructor(data){
        this.data = data
        this.walk()
    };
    walk(){
        for (const key in this.data) {
            this.defineReactive(this.data, key)
        }
    };
    defineReactive(data, key, value = data[key]) {
        const dep = new Dep();
        Object.defineProperty(data, key, {
            get(){
                if(Dep.target){
                    dep.addSubs(Dep.target)
                }
                console.log(`访问了${key}`)
                return value
            },
            set(newValue){
                value = newValue
                dep.notify()
            }
        })
        console.log('00000', Dep.target, dep)
    }
}

// 渲染函数
class Compiler{
    constructor(el, data){
        this.el = el;
        this.data = data;
        this.compile()
    }
    compile(){
        console.log('执行编译')
        const node = this.el.firstElementChild;
        const key = node.innerText.slice(2, -2)
        const render = () => {
            node.innerText = this.data[key]
        }
        new Watcher(render)
    }
}

// 观察者
class Watcher{
    constructor(render){
        this.render = render
        Dep.target = this;
        this.update()
        Dep.target = null
    }
    update(){
        this.render()
    }
}

// 订阅者
class Dep{
    constructor(){
        this.subs = [];
    }
    addSubs(watcher){
        this.subs.push(watcher)
    }
    notify(){
        this.subs.forEach(watcher => {
            watcher.update()
        })
    }
}

# vue3响应式原理

通过Proxy(代理): 拦截对象中任意属性的变化,包括:属性值的读写,属性的增加,属性的删除等。

通过Reffect(反射): 对源对象的属性进行操作, Reflect不是一个函数对象,因此它是不可构造的。

# vue双向数据绑定原理(*)

简易实现:v-model分为两部分,通过v-bind绑定值,再通过v-on:input来同步修改值

原理:

需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化 通过dep来理清依赖关系,watcher在依赖中添加自身 compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图 待属性变动dep.notice()通知时,能调动watcher自身的update方法,并处罚compile回调渲染视图

# 异步更新队列

Vue 在更新 DOM 时是异步执行的

例如,当你设置 vm.someData = 'new value',该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数将在 DOM 更新完成后被调用。例如:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false,不会立即更新
Vue.nextTick(function () {
	vm.$el.textContent === 'new message' // true,立即更新
})

# Object.defineProperty()

Object.defineProperty()静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。

const object1 = {
	a: 12
};
const obj = Object.defineProperty(object1, 'b', {		// b: 键名
	value: 42,											// value: 键值
    writable: true,										// 是否可以通过赋值运算符进行修改
    enumerable: true,
    configurable: true,									// false:不可更改,不可删除
    get() {
		return bValue;
    },
	set(newValue) {
		bValue = newValue;
    }
});
console.log(obj)		// {a: 12, b:42}

[Object.defineProperty() - JavaScript | MDN (mozilla.org) (opens new window)](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)

# vue组件通信

  • 父子通信

    • 父传子

      • Props(父子组件通信):通过props属性可以在父组件中向子组件传递数据。父组件通过子组件标签上的属性来传递数据,子组件通过props选项接收数据。这样子组件就可以使用父组件传递的数据了。

      • 依赖注入provide / inject

        • provide 钩子用来发送数据或方法。

        • inject钩子用来接收数据或方法

          注意: 依赖注入所提供的属性是非响应式的

    • 子传父

      • this.$emit():父组件定义自定义事件,通过props传给子组件,子组件再通过this.$emit()触发并传递数据
      • ref / $refs:这个属性用在子组件上,它的用用就指向了子组件的实例,可以通过实例来访问组件的数据和方法
    • $parent / $children

      • 使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)。
      • 使用 $children 可以让组件访问子组件的实例,但是, $children 并不能保证顺序,并且访问的数据也不是响应式的。
  • 兄弟组件通信

    • $emit/$on

      brotherA.vue

      <template>
        <div>
          <button @click="sendMessage">Send Message to Brother B</button>
        </div>
      </template>
      
      <script>
      export default {
        name: 'BrotherA',
        methods: {
          sendMessage() {
            this.$emit('message-sent', 'Hello from Brother A!');
          }
        }
      }
      </script>
      

      brotherB.vue

      <template>
        <div>
          <p>{{ message }}</p>
        </div>
      </template>
      
      <script>
      export default {
        name: 'BrotherB',
        data() {
          return {
            message: ''
          }
        },
        mounted() {
          this.$on('message-sent', (data) => {
            this.message = data;
          });
        },
        // 在 beforeDestroy 钩子函数中,我们需要手动取消对 'message-sent' 事件的监听,以避免内存泄漏
        beforeDestroy() {
          this.$off('message-sent');
        }
      }
      </script>
      
  • 祖孙通信

    • 依赖注入(provide / inject)
    • $attrs / $listeners
  • 复杂组件通信

    • Events(子父组件通信):通过events和自定义事件可以在子组件中向父组件传递数据。子组件通过this.$emit()方法触发一个自定义事件,并且可以传递数据给父组件。父组件通过监听自定义事件来接收子组件传递的数据。

    • Vuex(全局状态管理):Vuex是Vue官方推荐的用于应对应用中较大规模状态管理的库。它提供了一个集中式的状态存储,供多个组件共享数据。通过Vuex,不同组件可以直接访问和修改共享的状态,实现了组件之间的数据共享和通信。

    • localStorage/sessionStorage

# provide与inject如何使用

//父组件
provide: {
   user: 'John Doe'
}
--------------------
//子组件
inject: ['user']
//使用
console.log(this.user)

# vue2和vue3的区别

响应式原理:

  • Vue2:Object.defineProperty() 进行数据劫持,
    • 无法劫持对象属性的新增和移除,数组的下标和长度操作,vue3则不存在这个问题
  • Vue3:Object.proxy() 进行数据劫持;

新特性:

  • Vue2:选项式 API;
  • Vue3:组合式 API;

生命周期:

  • Vue2:创建,挂载,更新,卸载八个生命周期
  • Vue3:取消了创建阶段的 beforeCreate 和 created 两个生命周期函数;

获取props:

  • vue2:script代码块可以直接获取props
  • vue3:通过setup指令传递

算法:

  • Vue2:双端比较法
  • Vue3:最长递增子序列;

性能:Vue3 重写了虚拟 DOM 的实现方式,初始渲染速度更快

# Composition Api (组合式)与Options Api(选项式)的区别?

options Api 当组件变得复杂,导致对应属性的列表也会增长,这可能会导致组件难以阅读和理解 。composition Api它将功能定义在一起,利于查找和理解 Composition API 对 tree-shaking 友好,代码也更容易压缩 Composition API中见不到this的使用,减少了this指向不明的情况 如果是小型组件,可以继续使用Options API,也是十分友好的

# keep-alive

include: 属性的值决定组件要缓存

// 1. 将缓存 name 为 test 的组件
<keep-alive include="test"></keep-alive>
// 2. 将缓存 name 为 a 或者 b 的组件,结合动态组件使用
<keep-alive include='a,b'></keep-alive>
// 3. 使用正则表达式,需使用 v-bind
<keep-alive :include='/a|b/'></keep-alive>	
// 4.动态判断
<keep-alive :include='includedComponents'></keep-alive>
// 5. 将不缓存 name 为 test 的组件
<keep-alive exclude='test'></keep-alive>

exclude:属性的值决定组件不需要缓存。

max:最多可以缓存多少组件实例。

原理:

​ Vue 内部维护了一个 cache 对象,用于存储需要缓存的组件; 当一个组件第一次被 会将组件对应的 vnode 重新放回到 cache 缓存中,而不是直接销毁。

​ 如果一个组件在缓存时被设置了 max 属性,则当缓存的组件实例数超过了 max 值时,Vue 会按照 LRU 算法(最近最少使用)从缓存中清除不活动的组件,以保证缓存的组件实例数不会超过 max 值。

# keep-alive的生命周期

activated 当画面重新显示回来时触发该生命周期函数,当组件第一渲染时也会执行该生命周期函数

deactivated 当画面被隐藏时触发该生命周期函数

# v-if和v-show区别

v-if v-show
显示隐藏原理 控制DOM树不渲染 display:none
频繁切换
动态添加或移除

# v-if和v-for 优先级

实践中不管是vue2或者vue3都不应该把v-if和v-for放在一起使用。

在 vue 2.x 中,在一个元素上同时使用 v-if 和 v-for 时, v-for 会优先作用。

在 vue 3.x 中, v-if 总是优先于 v-for 生效。

vue2中v-for的优先级是高于v-if的,放在一起,会先执行循环在判断条件,并且如果只渲染列表中一小部分元素,也得再每次重渲染的时候遍历整个列表,比较浪费资源。

vue3中v-if的优先级是高于v-for的,所以v-if执行时,它调用相应的变量如果不存在,就会导致异常

# MVVM模式-***-20230928

  • MVVM分为Model、View、ViewModel三者。
  • Model:代表数据模型,数据和业务逻辑都在Model层中定义;
  • View: 代表UI视图,负责数据的展示;
  • ViewModel: ViewModel层通过观察数据层的变化,并对视图对应的内容进行实时更新。ViewModel层通过监听视图层的变化,并能够通知数据发生相应变化。

:Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。 这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。

为什么使用MVVM:低耦合,可复用,独立开发,可测试

# route和router区别

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

$router 是“路由实例”想要导航到不同URL 对象包括了路由的跳转方法,钩子函数等。通过push、replace、go、back等方法,来实现页面间的跳转

# vue自定义指令

分类:局部指令,全局指令

# vue项目优化

代码层面

  • 长列表性能优化
  • 事件销毁, beforeDestroy生命周期函数内执行销毁逻辑。
  • 图片懒加载
  • 路由懒加载
  • 按需加载插件
  • v-if,v-for避免同时使用
  • v-if,v-show选择
  • keep-alive组件缓存
  • input防抖节流

基础的web技术优化

  • 开启gzip压缩
  • 浏览器缓存
  • CDN加速

webpack优化

# 单页应用如何提高加载速度?*

使用代码分割:将代码拆分成小块并按需加载(懒加载),以避免不必要的网络请求和减少加载时间。 缓存资源:利用浏览器缓存来存储重复使用的文件,例如 CSS 和 JS 文件、图片等。 预加载关键资源:在首次渲染之前,先提前加载关键资源,例如首页所需的 JS、CSS 或数据,以保证关键内容的快速呈现。 使用合适的图片格式:选择合适的图片格式(例如 JPEG、PNG、WebP 等),并根据需要进行压缩以减少文件大小。对于一些小图标,可以使用 iconfont 等字体文件来代替。 启用 Gzip 压缩:使用服务器端的 Gzip 压缩算法对文件进行压缩,以减少传输时间和带宽消耗。 使用 CDN:使用内容分发网络(CDN)来缓存和传递文件,以提高文件的下载速度和可靠性。 优化 API 请求:尽可能地减少 API 调用的数量,并使用缓存和延迟加载等技术来优化 API 请求的效率。 使用服务器端渲染:使用服务器端渲染(SSR)来生成 HTML,以减少客户端渲染所需的时间和资源。但需要注意,SSR 也可能增加了服务器的负担并使网站更复杂。

# 一、代码层面优化

# 1. 长列表性能优化

使用虚拟滚动库(如 vue-virtual-scroll-list

实现分页/分批加载

避免一次性渲染大量 DOM 节点

# 2. 事件和资源销毁

beforeDestroy(Vue 2)/onBeforeUnmount(Vue 3)中清理:

定时器(`clearInterval`/`clearTimeout`)
事件监听器(`removeEventListener`)
WebSocket 连接
第三方库实例(如 ECharts、地图组件)

# 3. 图片懒加载

使用 vue-lazyloadIntersection Observer API

自定义懒加载指令

使用低质量图片占位符(LQIP)

# 4. 路由懒加载

// Vue Router 配置
const Home = () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')
// Vue 3
const AsyncComp = defineAsyncComponent(() => import('./AsyncComponent.vue'))

# 5. 第三方库按需加载

// Element Plus / Ant Design Vue
import { Button, Select } from 'element-plus'
// 使用 babel-plugin-import 自动按需引入

# 6. 指令使用规范

v-if 和 v-for 不同时使用:先用计算属性过滤数据

v-if 与 v-show 选择

`v-if`:切换频率低,初始渲染开销小
`v-show`:切换频率高,初始渲染开销大

key 的正确使用:列表项使用唯一稳定的 key

# 7. 组件缓存

使用 <keep-alive>缓存不活跃组件

配合 include/exclude属性精确控制

activated/deactivated生命周期管理状态

# 8. 防抖与节流

搜索框输入:防抖(300-500ms)

窗口调整/滚动:节流(16-100ms)

使用 lodash 的 debouncethrottle

# 二、构建与打包优化(Webpack)

# 1. 代码分割与懒加载

路由级别分割

组件级别异步加载

第三方库分离(externals)

# 2. Tree Shaking

Tree Shaking(树摇) 是前端打包阶段的一种死代码消除技术,目标只有一个:

没用到的代码,不打进最终的 bundle

名字来自:

把一棵“代码树”摇一摇,枯叶子(无用代码)掉下来

使用 ES6 模块语法(import/export

配置 sideEffects字段

使用支持 Tree Shaking 的库

# 3. 压缩与优化

JS/CSS 压缩(Terser、CSSNano)

图片压缩(image-webpack-loader)

删除无用代码(PurgeCSS)

# 4. 缓存策略

文件名哈希(contenthash)

模块标识符稳定(moduleIds、chunkIds)

Runtime 代码分离

# 三、网络与传输优化

# 1. Gzip/Brotli 压缩

# Nginx 配置
gzip on;
gzip_types text/plain text/css application/json application/javascript;
brotli on;
brotli_types text/plain text/css application/json application/javascript;

# 2. 浏览器缓存策略

强缓存:Cache-Control: max-age=31536000

协商缓存:Etag/Last-Modified

Service Worker 离线缓存

# 3. CDN 加速

静态资源上传 CDN

多域名并行加载

HTTP/2 服务器推送

# 4. HTTP/2 优化

多路复用减少连接数

头部压缩

服务器推送关键资源

# 四、单页应用加载速度优化

# 1. 关键渲染路径优化

内联关键 CSS

异步加载非关键 JS

资源预加载/预连接

<!-- 预加载关键资源 -->
<link rel="preload" href="critical.css" as="style">
<link rel="preconnect" href="https://api.example.com">

# 2. 代码分割策略

基于路由分割

基于组件分割

公共代码提取

// webpack 配置
optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        priority: 10
      }
    }
  }
}

# 3. 图片优化

使用 WebP 格式(兼容性回退)

响应式图片(srcset

SVG 图标代替图片

图片懒加载

# 4. API 请求优化

请求合并

请求缓存(内存/本地存储)

请求优先级控制

错误重试机制

# 5. 渲染性能优化

减少重排重绘

使用 CSS3 动画代替 JS 动画

虚拟列表渲染

骨架屏加载状态

# 五、进阶优化方案

# 1. 服务端渲染(SSR)

Nuxt.js 框架

解决首屏白屏问题

更好的 SEO 支持

# 2. 预渲染(Prerendering)

静态页面预生成

适合内容变化少的页面

使用 prerender-spa-plugin

# 3. PWA 支持

Service Worker 缓存

离线可用

添加到主屏幕

# 4. 性能监控

Lighthouse 性能审计

Web Vitals 核心指标监控

错误监控(Sentry)

# 总结

优化阶段 关键措施 预期效果
开发阶段 代码分割、懒加载、组件优化 减少打包体积 30-50%
构建阶段 Tree Shaking、压缩、CDN 提升加载速度 40-60%
运行阶段 缓存、预加载、SSR 提升用户体验 50-70%
监控阶段 性能监控、持续优化 长期性能稳定

# vuex持久化

  • 使用 vuex-along
  • 使用 localStorage 或者 sessionStroage

注意:刷新浏览器后,Vuex的数据是否存在?

不存在

原因:因为 store 里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,store里面的数据就会被重新赋值初始化。

# vue和react区别-***

相同点

  • 都有组件化思想
  • 都支持服务器端渲染
  • 都有Virtual DOM(虚拟dom)
  • 数据驱动视图
  • 都有支持native的方案:VueweexReactReact native
  • 都有自己的构建工具:Vuevue-cliReactCreate React App

区别

  • 数据流向的不同。react从诞生开始就推崇单向数据流,而Vue是双向数据流
  • diff算法不同。react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue 使用双向指针,边对比,边更新DOM

# computed和watch

  • computed
    • computed 是基于其他响应式数据派生出的新的数据。
    • computed 属性会缓存计算结果,只有当依赖的响应式数据发生变化时,才会重新计算。
    • computed 适用于一些复杂的数据计算逻辑,可以提高性能。
    • computed 属性可以是 getter 和 setter 函数。
  • watch
    • watch 用于监听某个数据的变化,并执行相应的回调函数。
    • watch 适用于观察某个值的变化,然后执行异步操作或开销较大的操作。
    • watch 可以深度监听对象的变化,也可以立即执行回调函数。
  • 区别
    • computed 是基于其他响应式数据派生出的新数据,而 watch 是观察某个数据的变化并执行回调。
    • computed 属性会缓存计算结果,而 watch 每次都会执行回调函数。
    • computed 适用于复杂的数据计算逻辑,watch 适用于观察数据变化后的副作用操作。

# Vuex

Vuex是一种状态管理模式,存在的目的是共享可复用的组件状态。

主要包括以下几个模块:

State => 基本数据,定义了应用状态的数据结构,可以在这里设置默认的初始状态。

Mutation => 是唯一更改 store 中状态的方法,且必须是同步函数。

Action => 像一个装饰器,包裹mutations,使之可以异步。用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。

Getter => 从基本数据派生的数据,允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

Module => 模块化Vuex,允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

# vuex为什么只能在mutations中修改

因为state是实时更新的,mutations无法进行异步操作,而如果直接修改state的话是能够异步操作的,当你异步对state进行操作时,还没执行完,这时候如果state已经在其他地方被修改了,这样就会导致程序存在问题了。所以state要同步操作,通过mutations的方式限制了不允许异步。

为了让devtools 工具能够追踪数据变化;

# vuex模块化使用

当我们开发的项目比较大时,store中的数据就可能比较多,这时我们store中的数据就可能变得臃肿,为了解决这一问题,我们就需要将store模块化(module)

前提:创建两份js文件,含有属性与vuex写法相同,需要通过 namespaced:true开启命名空间store/index.js:在modules中引入文件

使用:

访问state数据: 第一种方式:this.$store.state.moduleA.sum 第二种方式: ...mapState('moduleA',['sum','number']) action提交mutation 第一种方式:需要传参this.$store.dispatch('moduleB/addZhang',{name:'小明',age:18}) ,无需传参this.$store.dispatch('moduleB/addServer') 第二种方式:...mapActions('moduleB',['addZhang']) getters计算属性 第一种方式: this.$store.getters['moduleB/firstName'] 第二种方式:...mapGetters('moduleB',['firstName'])

# vue中mixin

mixin(混入): 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。

本质其实就是一个js对象,它可以包含我们组件中任意功能选项,如data、components、methods 、created、computed等等

我们只要将共用的功能以对象的方式传入 mixins选项中,当组件使用 mixins对象时所有mixins对象的选项都将被混入该组件本身的选项中来

具体使用:

创建mixins.js文件

let mixin = {
    created() {
        console.log('我是mixin中的');
    },
    methods: {
        hello() {
            console.log('你好');
        },
    },
}
export default mixin

局部使用

import mixin from "./mixins";
export default {
	mixins: [mixin],
	mounted() {
    	this.hello();//你好
  },
};

全局使用main.js

import { createApp } from 'vue'
import App from './App.vue'
import mixins from "./mixins";
const app = createApp(App)
app.mixin(mixins)
app.mount('#app')

# Vue中给对象添加新属性时,界面不刷新怎么办?

原因:vue2响应式采用object.defineProperty进行劫持,那个添加新属性时,新的属性不会具有get和set方法,不是一个响应式所以界面不刷新

解决:Vue.set() 向响应式对象中添加一个property,并确保这个新 property 同样是响应式的

vue3通过proxy劫持和reflect映射实现响应式,不会有这个问题

# setup可不可以直接写async和await?

可以:setup 语法糖中可直接使用 await,不需要写 async , setup 会自动变成 async setup

<script setup>
import Api from '../api/Api'
const data = await Api.getData()
console.log(data)
</script>

# 说说 Vue 中 CSS scoped 的原理

添加scoped标签后会给组件中所有标签元素,添加一个唯一标识,这个唯一标识就是自定义属性,data-v-xxxxxxxx这样的字眼,同时对应的样式选择器也会添加这个唯一的属性选择器

核心作用

  1. 样式隔离(主要目的)
  • 组件A的样式不会影响组件B

  • 避免全局样式污染

  • 防止样式冲突

  1. 提高可维护性
  • 每个组件自带样式

  • 删除组件时,样式自动删除

  • 易于定位样式问题

  1. 支持 CSS Modules 类似功能
  • 无需手动管理类名

  • 自动生成唯一选择器

注意:每个组件实例的 data-v-xxx相同的(基于文件路径生成的 hash)

.container[data-v-f3f3eg9] .title[data-v-f3f3eg9]

# $nextTick原理

将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false,不会立即更新
Vue.nextTick(function () {
	vm.$el.textContent === 'new message' // true,立即更新
})

# data是函数不是对象

vue是一个单页面应用最终所有的实例都会挂载到app.vue文件,如果data是一个对象那么会导致数据污染。通过函数返回对象的方式,利用函数作用域的限制避免数据污染

# 路由守卫

vue路由守卫分为三种:全局路由守卫,全局解析,组件路由守卫

to: 进入到哪个路由去

from: 来自哪个路由

next:是否跳转

  • 全局路由守卫
import VueRouter from 'vue-router'
const router = new VueRouter({
    routes: [
        {
            path: '/login',
            name: 'Login',
            component: () => import()
        }
    ]
})
// 全局前置路由守卫,表示在每次切换路由之前调用,针对所有的路由
router.beforeEach((to, from, next) => { // to表示去哪个路由,from表示来自哪个路由,next表示放行
    // 可加判断条件进行放行
    if () {
        next()
    }
})
// 全局后置路由守卫,表示在每次切换路由之后调用,针对所有的路由
router.afterEach((to, from)=>{  // to表示去哪个路由,from表示来自哪个路由,注意这里没有next
    document.title = to.meta.title || '测试网站'   // 切换网页标题
})
export default router
  • 全局解析守卫

    router.beforeResolve(async to => {})
    
  • 全局后置钩子

    router.afterEach((to, from) => {})
    
  • 路由独享守卫

import VueRouter from 'vue-router'
const router = new VueRouter({
    routes: [
        {
            path: '/login',
            name: 'Login',
            component: () => import(/* webpackChunkName: "Login" */ '../components/Login'),
            meta: {fangxing: true},
            // 独享路由守卫,独享路由守卫只有前置的,没有后置的
            beforeEnter: (to, from, next) => {
                if(to.meta.fangxing){
                    next()
                }
            }
        }
    ]
})
export default router
  • 组件内守卫(与data同级)
// 通过路由规则,进入该组件时被调用
beforeRouteEnter(to, from, next){},

// 通过路由规则,离开该组件时被调用
beforeRouteLeave(to, from, next){}

# vue插槽

slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。

分类:

  • 默认插槽
    • 又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽
    • 带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
  • 作用域插槽。
    • 默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。

# vue修饰符

表单修饰符

  • .lazy:懒加载,光标离开标签时,才赋值给value
  • .trim:过滤首位空格
  • .number:限制输入类型为数字或转为数字

事件修饰符

  • .stop:阻止事件冒泡
  • .prevent:阻止事件默认行为
  • .once:事件只触发一次
  • .capture:开启事件捕获
  • .self:事件只在自身触发

鼠标按键修饰符

  • left 左键点击
  • right 右键点击
  • middle 中键点击

键值修饰符

  • 普通键(enter、tab、delete、space、esc、up…)
  • 系统修饰键(ctrl、alt、meta、shift…)

v-bind修饰符

  • .async 对props进行双向绑定
  • .prop 设置自定义标签属性,避免暴露数据,防止污染html结构
  • .camel 将命名为驼峰命名法

# history和hash的区别

hash

  • hash 模式是一种把前端路由的路径用井号 # 拼接在真实 URL 后面的模式。当井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 hashchange 事件。

​ 优点:浏览器兼容性较好,连 IE8 都支持 ​ 缺点:路径在井号 # 的后面,比较丑 history

  • history API 是 H5 提供的新特性,允许开发者直接更改前端路由,即更新浏览器 URL 地址而不重新发起请求

​ 优点:路径比较正规,没有井号 # ​ 缺点:兼容性不如 hash,且需要服务端支持,否则一刷新页面就404了

# params和query区别

相同点:params 和 query 都是用于传递参数的,但它们的传参方式和使用场景是不同的。

params:通过路由路径传递参数,在路由配置中使用 :paramName 的形式进行声明

const router = new VueRouter({
    routes: [
        {
            path: '/user/:id',
            component: User,
        },
    ],
})

query:通过 URL 查询字符串(即问号后面的部分)传递参数,在路由地址后面使用 ? 连接多个参数键值对

不需要在router中配置 /search?q=vue 会自动匹配到search组件

区别

params 适合用于必须存在的参数传递,例如用户详情页或文章详情页的访问。 query 适合用于可选的参数传递,例如搜索功能中关键词的传递。

# vue2中assets和vue3中public区别 ?

在 Vue 2 中,assets 目录是默认存在的,可以直接在项目的根目录下创建,它通常用来存放组件需要的图片、样式等静态资源文件。这些文件会被打包到 JavaScript 文件中,在代码中使用相对路径引用。

在 Vue 3 中,可以通过配置 vue.config.js 文件来设置 public 目录,它的作用与 assets 目录类似,用来存放静态资源文件。但是,与 Vue 2 不同的是,public 目录下的文件不会被打包,而是会直接复制到输出目录下

# Vue父组件调用子组件的方法

vue中如果父组件想调用子组件的方法,可以在子组件中加上ref,然后通过this.$refs.ref.method调用

<child ref="child"></child>
调用:this.$refs.child.子组件方法

# 渐进式框架理解

渐进式: 可以理解为没有多做职责之外的事

个人理解:主张最少的,一开始仅基于基础框架构建,随着需求不断扩充

# 页面初始化闪烁

产生原因:当网速较慢,vue.js文件还没有加载完时,在页面上会显示的字样,直到vue创建实例,编译模板时,dom才会被替换,所以这个过程屏幕是闪动的。

方法一:

​ v-cloak:所以解决这个问题,需要在style样式中设置【v-cloak】{display:none}。在一般情况下,v-clock是一个解决初始化慢导致页面闪动的最佳实践,对于简单的项目很实用。

<div id="app" v-cloak>
  {{ message }}
</div>
// css文件中	
[v-cloak] {
  display: none;
}

方法二:**v-ifv-show **

方法三:Skeleton Screens (骨架屏)

方法四:css动画

方法五:Suspense 组件 (Vue 3)

# class和style如何动态绑定

class 与 style 动态绑定一般通过对象或者数组来实现

对象写法:适用于要绑定的样式名字样式确定,但动态决定用不用。

数组写法:适用于要绑定的样式名字样式不确定。

// 动态class
<div v-bind:class="{ active: isActive }"></div> //对象写法
<div v-bind:class="[activeClass, errorClass]"></div> //数组写法
<div v-bind:class="[isActive?active:setFontSize]"></div> // 数组语法:三元表达式
<div v-bind:class="[{'active':isActive}, setFontSize]"><div> // 数组语法:混合写法
// 动态style
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

# vue遇到的坑

data必须是一个函数,而不是一个对象 vue管理的函数不要写成箭头函数 添加属性页面不刷新 子路由path不需要添加**/**,path=‘new’

# vue自带动画组件, transition

组件是 Vue 提供的用于包裹需要动画效果的元素组件。使用组件可以方便地实现元素的进入和离开动画效果。

<template>
    <div>
        <button @click="visible = !visible">Toggle</button>
        <transition name="fade">
            <p v-if="visible">Hello, World!</p>
        </transition>
    </div>
</template>
  
<script>
export default {
    data() {
        return {
            visible: false,
        };
    },
};
</script>
  
<style>
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.5s;
}

.fade-enter,
.fade-leave-to {
    opacity: 0;
}
</style>

# vue-loader工作原理-*

vue-loader 是 Webpack 的一个 loader,用来把 .vue单文件组件(SFC)解析成浏览器可运行的 JS、CSS 和 HTML。

将一个 .vue 文件 切割成 template、script、styles 三个部分。 template 部分 通过 compile 生成 render、 staticRenderFns。 获取 script 部分 返回的配置项对象 scriptExports。 styles 部分,会通过 css-loader、vue-style-loader, 添加到 head 中, 或者通过 css-loader、MiniCssExtractPlugin 提取到一个 公共的css文件 中。 使用 vue-loader 提供的 normalizeComponent 方法, 合并 scriptExports、render、staticRenderFns, 返回 构建vue组件需要的配置项对象 - options, 即 {data, props, methods, render, staticRenderFns…}。

# vue的diff算法-*

diff整体策略为:深度优先,同层比较

当数据发生改变时,订阅者watcher就会调用patch给真实的DOM打补丁

通过比较新旧两个 DOM 树之间的差异,从而只更新 DOM 树中发生变化的部分。这种算法可以提高 Vue.js 2 的性能,因为它只需要更新实际发生变化的部分,而不是整个 DOM 树。

  • Diff 策略: Vue.js 的 diff 算法会根据节点的类型、key 值等因素来确定节点的增删改操作。具体来说,diff 算法会按照以下策略进行比较:
    • 如果节点类型不同,直接替换节点;
    • 如果节点类型相同且有相同的 key 值,进行更新;
    • 如果节点类型相同但没有相同的 key 值,将旧节点移除,新节点插入。

# 对 SPA 单页面的理解

单页面应用(SPA) 多页面应用(MPA)
组成 一个主页面和多个页面片段 多个主页面
刷新方式 局部刷新 整页刷新
url模式 哈希模式 历史模式
SEO搜索引擎优化 难实现,可使用SSR方式改善 容易实现
数据传递 容易 通过url、cookie、localStorage等传递
页面切换 速度快,用户体验良好 切换加载资源,速度慢,用户体验差
维护成本 相对容易 相对复杂

单页应用优点

  • 具有桌面应用的即时性、网站的可移植性和可访问性
  • 用户体验好、快,内容的改变不需要重新加载整个页面
  • 良好的前后端分离,分工更明确

单页应用缺点

  • 不利于搜索引擎的抓取:SSR服务端渲染
  • 首次渲染速度相对较慢

# Vue 的单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。 这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。 额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。 这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。 子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。

# 父组件可以监听到子组件的生命周期吗?

比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:

// Parent.vue
<Child @mounted="doSomething"/>

// Child.vue
mounted() {
  this.$emit("mounted");
}

以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:

//  Parent.vue
<Child @hook:mounted="doSomething" ></Child>

doSomething() {
   console.log('父组件监听到 mounted 钩子函数 ...');
},

//  Child.vue
mounted(){
   console.log('子组件触发 mounted 钩子函数 ...');
},    
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...

当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。

# 什么是 MVVM?比之 MVC 有什么区别?什么又是 MVP ?

MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。 用户与页面产生交互时Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。

MVVM 模式中的 VM,指的是 ViewModel, 它通过双向的数据绑定,将 View 和 Model 的同步更新给自动化了。当 Model 发生变化的时候,ViewModel 就会自动更新;ViewModel 变化了,View 也会更新。

MVP 模式 ,View 层的接口暴露给了 Presenter 因此我们可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新

# vue中hook和react中hook区别

在React中,hook是一种函数,它可以让你在函数组件中添加state、effect等功能。 React 中的hook有useState、useEffect、useContext等。使用hook可以避免使用类组件时可能会出现的繁琐的生命周期方法、this等问题。

在Vue中,hook被称为生命周期钩子函数,它们是在组件实例化过程中自动调用的回调函数。 Vue中的生命周期钩子函数包括beforeCreate、created、beforeMount、mounted等。它们可以用于控制组件的生命周期,以及在组件生命周期特定阶段执行特定的操作。

# vue服务端渲染(SSR),解决了哪些问题?

Vue服务端渲染(SSR)通过在服务器上预先生成Vue组件的HTML字符串,并将其发送到客户端,以实现更快的页面加载速度、更好的搜索引擎优化和更好的用户体验。服务端渲染解决了许多SPA(Single Page Application)应用程序中存在的问题,例如:

SEO(搜索引擎优化)问题:由于传统的SPA应用程序是在浏览器中构建的,因此搜索引擎无法正确地索引它们的内容。使用Vue SSR,可以在服务器上呈现HTML字符串并向搜索引擎提供更好的友好的页面。 性能问题:SPA应用程序需要大量的JavaScript代码来初始化应用程序并交互。这可能导致页面加载时间缓慢,用户体验较差。使用Vue SSR,可以在浏览器中更快地呈现初始HTML的完整标志,并在其中嵌入必要的JavaScript。这样可以加快页面加载速度,并提高用户体验。 首屏渲染问题:传统的SPA应用程序在首次加载时可能会需要大量时间才能呈现第一个屏幕,直到JavaScript代码完成下载并执行。使用Vue SSR,可以在服务器上呈现组件,并将其作为HTML字符串发送到客户端,从而实现快速呈现首屏的目标。

# 虚拟 DOM 的优缺点?

优点:

保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限; 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率; 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。 缺点:

无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

# 虚拟 DOM 实现原理?

虚拟 DOM 的实现原理主要包括以下 3 部分:

用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象; diff 算法 — 比较两棵虚拟 DOM 树的差异;

pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

# Vue 中的 key 的作用

给出一组数据,Vue要把这些数据渲染到页面上,首先要生成【虚拟DOM】,然后根据【虚拟DOM】去生成【真实的DOM】。如果数据发生了改变,Vue会生成【新的虚拟DOM】,注意,这个【新的虚拟DOM】并不会直接生成【新的真实DOM】,否则虚拟DOM一点用处也没有了。Vue的操作是,拿根据新的数据生成的【新的虚拟DOM】与之前的【真实的DOM】去做比较,如果相同,直接延用即可(“拿来主义”);如果不同,则生成新的DOM对象。

[Vue中key的作用及原理_纸照片的博客-CSDN博客](https://blog.csdn.net/cun_king/article/details/120714227?ops_request_misc=&request_id=c729bf1c57fa48b88392b56e5708999f&biz_id=&utm_medium=distribute.pc_search_result.none-task-blog-2~all~koosearch~default-1-120714227-null-null.142^v93^koosearch_v1&utm_term=vue2 key的作用&spm=1018.2226.3001.4187) (opens new window)

# reactive与ref的区别?

处理的数据类型不同

  • reactive:只能处理对象或数组(引用类型),无法直接处理基本类型(number、string、boolean 等)。

  • ref:主要用于处理基本类型(number、string、boolean 等),但也可以处理对象或数组(内部会自动通过 reactive 转换)。

备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。

访问和修改方式不同

  • reactive:直接通过对象属性访问和修改,无需额外语法。
  • ref:需要通过 .value 访问和修改其内部值(模板中使用时可省略 .value)。

响应式丢失问题

  • reactive:当直接替换整个对象时,会丢失响应式(因为响应式对象的引用被改变)。
  • ref:由于内部值通过 .value 维护,即使替换整个值,也不会丢失响应式

# v-on可以监听多个方法吗?

可以一个元素绑定多个事件的两种写法

<a v-on='{click:DoSomething,mouseleave:MouseLeave}'>doSomething</a>
<button @click="a(),b()">点我ab</button>

# vue3中如何获取refs,dom对象的方式?vue2中如何使用?

vue3:

(1) setup函数方法内,获取单个ref属性绑定的dom元素:先定义一个空的响应式数据ref定义的,你想获取哪个dom元素,在该元素上使用ref属性绑定该数据即可,通过ref.value即可获取到dom节点

(2) 获取多个ref属性绑定的dom元素。使用ref绑定一个函数,在函数里把dom添加到数组里面

使用数组收集多个 ref(推荐)

通过 v-for 循环渲染列表时,将 ref 绑定到一个数组,Vue 会自动将每个元素的 DOM 引用存入该数组。

<template>
  <!-- 遍历列表,ref 绑定到同一个数组 -->
  <ul>
    <li 
      v-for="(item, index) in list" 
      :key="index" 
      :ref="el => { if (el) refsArray[index] = el }"  <!-- 存入数组对应位置 -->
    >
      {{ item }}
    </li>
  </ul>
</template>

<script setup>
import { ref, onMounted } from 'vue'

// 定义数组存储 DOM 引用
const refsArray = ref([])
const list = ref(['项目1', '项目2', '项目3'])

// 组件挂载后才能获取到 DOM(此时数组已填充)
onMounted(() => {
  console.log(refsArray.value) // 输出:[li元素1, li元素2, li元素3]
  // 访问第一个元素
  console.log(refsArray.value[0].textContent) // 输出:"项目1"
})
</script>

使用对象收集多个 ref(适合非列表场景)

对于非列表的多个独立元素,可给每个 ref 定义唯一标识,通过对象的键值对存储。

<template>
  <!-- 多个独立元素,ref 绑定到对象的不同属性 -->
  <div :ref="el => { if (el) refsObj.div1 = el }">区域1</div>
  <p :ref="el => { if (el) refsObj.p1 = el }">段落1</p>
  <button :ref="el => { if (el) refsObj.btn1 = el }">按钮1</button>
</template>

<script setup>
import { ref, onMounted } from 'vue'

// 定义对象存储 DOM 引用
const refsObj = ref({})

onMounted(() => {
  console.log(refsObj.value) 
  // 输出:{ div1: div元素, p1: p元素, btn1: button元素 }
  
  // 访问特定元素
  console.log(refsObj.value.div1.textContent) // 输出:"区域1"
})
</script>

关键注意事项

  1. 时机问题:DOM 元素只有在组件挂载后才会被赋值到 ref 中,因此必须在 onMounted 或更晚的生命周期中访问,否则会得到 undefined
  2. 避免直接替换数组 / 对象: 不要直接给 refsArray.valuerefsObj.value 赋值新的数组 / 对象(如 refsArray.value = [1,2,3]),这会丢失响应式关联,导致 DOM 引用无法正确更新。
  3. v-for 配合时的索引对应: 列表渲染时,index 与数组索引严格对应,即使列表项顺序变化(如排序),refsArray 也会自动同步更新。

# 学习 EventBus

首先,在你的项目中创建一个 eventBus.js 文件,并定义一个空的 EventBus 对象:

import Vue from 'vue';
export const EventBus = new Vue();

发送事件

import { EventBus } from './eventBus.js';
// 发送名为 'myEvent' 的事件
EventBus.$emit('myEvent', data);

接收事件

import { EventBus } from './eventBus.js';
EventBus.$on('myEvent', (data)=>{});

取消监听

EventBus.$off('myEvent');

# vue2过滤器(vue3取消)

filter 过滤器是一种很常用的功能,它可以用于对数据进行格式化、排序、筛选等操作。在使用过程中,我们只需要在模板表达式中使用管道符 |,并将要使用的过滤器的名称作为参数传递进去即可。

全局过滤器:Vue.filter(‘过滤器名称’,function(){})

局部过滤器:filter选项

filters: {
	//filterName过滤器名,value是'|'之前的数据
	filterName(value) {
		if (!value) return "";
		return "你好" + value.toString();
	}
}

# vue可以通过计算属性监听计算属性吗

答案:不可以

计算属性依赖于其他属性值,所以我们可以在计算属性中监听这些属性值的变化,并执行一些相关的操作 。 但是,计算属性无法直接监听另一个计算属性的变化,因为一个计算属性的值不是响应式的,它依赖的属性值发生变化时只有它自己才会重新计算,而不会触发其他计算属性的更新

本章目录