前端知识宝典

文章目录


前端目前主流框架:vue,react,angular
前端各大主流UI库:设计风格和适用场景如下

Element UI‌:适用于Vue开发PC端项目,以其清晰的文档和友好的UI受到开发者欢迎‌。
Ant Design‌(阿里蚂蚁金服团队):在React中地位显著,提供大量高质量的React组件,适用于企业级中后台产品设计‌。针对vue和react有2个版本(ant-design-vue,AntDesign of React)
antd。是Ant Design的React实现版本,专注于提供高质量的React组件库。Ant Design(AntD)
常用的: import Antd from ‘ant-design-vue’;
Vant‌:轻巧可靠的移动UI组件库,适用于Vue2和Vue3,支持微信和支付宝小程序‌
AntDesignMobile‌:AntDesign的移动版本,适用于移动终端产品的研发,提供40+基本组件‌。
Bootstrap‌:适用于快速构建响应式布局和网格系统‌,虽然主要用于Web开发,但其Flexbox布局和丰富的资源使其在前端开发中仍有一定影响力‌。
Vuetify‌:渐进式JavaScript框架|Vue.js,微服务架构,采用Material Design风格,适用于需要避免界面同质化的项目‌。 ‌

一、Vue相关知识点

1.vue3和vue2区别详细区别点击查看

1.生命周期
2.多根节点
3.组合式API(composition api)
4.异步组件
5.响应式原理
6.teleport
7.虚拟DOM
8.事件缓存
9.differ算法优化
10.打包优化
11.ts支持
https://www.cnblogs.com/caix-1987/p/17290009.html

1.响应式原理+性能提升:

vue2中是通过Object.defineProperty实现响应式,而Vue3用proxy替代了vue2的响应式原理。原因是vue2中对于添加未声明的属性(不可枚举)或直接修改数组(默认只能监听对象的属性变化,而不是数组元素的变化)是不会触发视图更新的。
Vue 2.x 使用了基于 Object.defineProperty 的响应式系统,它在初始化阶段遍历对象的属性,通过 Object.defineProperty 将每个属性转化为 getter 和 setter,以便在属性访问和修改时触发更新。 对数组的每个元素进行观察会带来性能开销,尤其是在大型数组上。所以在 Vue 2 中,对数组使用 push、pop、shift、unshift、splice
等方法都会触发页面更新。然而,直接通过索引修改数组元素的值是不会触发更新的。其他的通过Vue.set(vm.someObject, ‘newProperty’, ‘New Value’); Vue 3
在内部实现上进行了大量优化,提高了渲染速度和性能,同时降低了内存占用。这得益于对响应式系统的改进,如使用 Proxy 替代 Object.defineProperty 来实现响应式,并采用了静态提升技术以提高渲染性能。此外,还新增了对最长递归子序列的计算,用于最小化修改偏移量的计算,以及对静态标记的支持。

引伸:Vue 的双向绑定的底层原理是什么?

简单点的说就是通过object.defineproperty()去劫持各个属性。object.defineproperty进行数据劫持再结合观察者模式,即发布订阅来实现数据双向绑定,这也是vue2的响 应式原理,而vue3增加了一种响应式实现方法数组和对象使用的proxy,而基础数据类型还是用object.defineproperty实现。
响应式原理底层的实现逻辑是什么:1响应式转化,当vue组件实例创建时候,它会遍历data对象所有的属性值, 对于每个属性用vue.defineproperty()将其转化为getter和settet.当浏览器读取的时候会触发getter,当设置对象属性新值的时候setter会被调用,还会触发通知所有订阅了改属性变化的watcher.它接受到通知后,会重新计算视图需要更新的部分,进而引发dom更新。

2.setup语法糖: vue3采用了组合式 API ,为了使用组合式API,我们需要一个入口,在vue3组件中,称之为setup语法糖。注意:在setup()中定义的数据,方法都需要 return 返回出去,不然会报错。
引伸:setup放组件标签里面和外面有区别吗?
在这里插入图片描述
1当 setup 函数放在组件标签内时,因为 setup 在组件实例创建之后执行。上下文: setup 函数内可以访问到组件实例,this
指向组件实例。 2将 setup 函数放在export default外时,需要显式导入 defineComponent
并使用它包裹组件配置。import { defineComponent } from ‘vue’; 3写在script标签上,setup
函数的上下文(this)不指向组件实例,而是被封装到响应式上下文中。

3.编码方式的改变:组合api Vue 3 引入了 组合式Composition API,它允许开发者更好地组织和复用逻辑代码,从而提高代码的可维护性。精确地按需引入,减少打包体积
1.ref 用于创建一个响应式对象,通常用于包装基本类型的数据。
2.reactive 用于创建一个响应式对象,可以包含嵌套的属性(引用数据)。
3.toRefs 用于将响应式对象转换为包含 ref 的普通对象,方便在解构时使用。
toRefs 函数用于将响应式对象转换为包含 ref 的普通对象。这在解构响应式对象时非常有用,因为解构会使响应式对象的属性失去响应式特性。使用 toRefs 可以保留解构后的对象属性的响应性。
4.onMounted、onUnmounted、onUpdated 等,用于处理组件的生命周期。
5.provide 和 inject
6.directive自定义指令(后台管理系统按钮控制)
7.mixin混入
8.createApp()创建一个应用实例,类似new vue()
9.nextTick()
10.computed 用于创建计算属性,可以基于响应式数据进行计算。
11.watch 用于监听响应式数据的变化,仅在监听的数据源发生变化时才触发回调。
12.watchEffect 是 Vue 3 中引入的一个响应式 API,它会立即执行传入的回调函数,并且在这个过程中自动追踪回调中使用的所有响应式依赖。一旦这些依赖发生变化,watchEffect 将会重新执行回调函数7。这意味着 watchEffect 在其创建时会立刻运行一次,这与 watch 默认的懒执行行为不同,后者仅在监听的数据源发生变化时才触发回调
13.TypeScript支持:

Vue 3 对于 TypeScript 提供了一致的完整类型定义,这对于大型项目尤其有用,因为它可以帮助确保代码的一致性和错误检测。
vue2从版本2.4.0开始,它也增加了对 TypeScript 的支持。
14.新特性的支持: Vue 3 支持更多的新特性,如片段 (Fragments多根节点)、Teleport(传送门)、Suspense
(占位符),双向绑定数据请阅读官网的ref,reactive,torefs等 ,这些都是为了提供更好的开发体验和更高的灵活性。

1.vue3可以实现多根节点Fragments不受限制,vue2是单根节点,外面只能用一个div嵌套
2.Teleport 允许你在 DOM 树的其他部分渲染组件的内容。这在处理模态框、弹出框等需要在 DOM 结构中移动的场景时非常有用。比如将弹框传送到body下面
3.Suspense 允许你在异步组件加载过程中提供一个占位符,直到异步组件加载完成。这对于处理异步组件的加载状态非常有用。

6.生命周期钩子的变化: Vue 3 的生命周期钩子有所调整,例如 beforeCreate 现在是在 setup 函数中执行的,而不是像 Vue 2 那样在实例初始化和数据观测之前。此外,还有 activateddeactivated
两个新的生命周期钩子,它们分别在组件激活和解. Vue3 在组合式API(Composition
API,下面展开)中使用生命周期钩子时需要先引入,而 Vue2 在选项API(Options API)中可以直接调用生命周期钩子,如下所示。
setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地去定义。

在这里插入图片描述

1-1.父子组件生命周期执行顺序

在这里插入图片描述
在这里插入图片描述

创建阶段(Mounting)
父组件 的 beforeCreate 钩子触发。
父组件 的 created 钩子触发。
父组件 的 beforeMount 钩子触发。
子组件 的 beforeCreate 钩子触发。
子组件 的 created 钩子触发。
子组件 的 beforeMount 钩子触发。
子组件 的 mounted 钩子触发。
父组件 的 mounted 钩子触发。
在这个阶段,Vue 首先初始化父组件,然后是子组件。每个组件在其挂载到 DOM 之前都会经历一系列的生命周期钩子。
更新阶段(Updating)
当数据变化引起视图更新时,更新过程会按照以下顺序进行:
父组件 的 beforeUpdate 钩子触发。
子组件 的 beforeUpdate 钩子触发。
子组件 的 updated 钩子触发。
父组件 的 updated 钩子触发。
在这个阶段,Vue 首先检测父组件的变化,然后检查子组件是否有需要更新的地方。只有当子组件的状态依赖于父组件的数据时,才会触发子组件的更新钩子。
当组件被销毁时,销毁过程会按照以下顺序进行:
子组件 的 beforeDestroy 钩子触发。
子组件 的 destroyed 钩子触发。
父组件 的 beforeDestroy 钩子触发。
父组件 的 destroyed 钩子触发。
在销毁阶段,Vue 先销毁子组件,然后再销毁父组件。这确保了所有子组件资源都被正确清理之后,父组件才被销毁。

1-2.引伸:最早可以执行异步操作的地方created

created 是用于在组件创建后立即请求数据的好地方,特别是在你需要数据来初始化组件的情况下。
mounted 更适合于那些需要确保DOM 已经准备好的情况,或者你想让用户界面首先展示出来的情形。
watch 适用于当数据依赖于其他状态或参数变化时,可以保证数据总是最新的。
1.created 钩子 适用场景:如果你需要在组件实例创建完成后立即发起一个异步请求来获取数据,并且这个数据对于初始化组件是必要的,那么可以在 created
钩子中发起请求。 优点: 组件还未挂载到 DOM 上,因此不会触发不必要的重渲染。
如果你需要在组件创建时就准备好数据,这通常是最早可以执行异步操作的地方。
2. mounted 钩子 适用场景:当你需要确保组件已经挂载到 DOM 上后再发起请求(例如,依赖于 DOM 元素的操作),或者你希望用户界面先显示出来再加载数据时,应该使用 mounted 钩子。 优点: 组件已经完全挂载,可以访问真实的 DOM
节点。 用户界面已经呈现给用户,可能提供更好的用户体验(如显示加载指示器)

1-3.vue3的响应式相较于vue2具体做了哪些提升

  1. 使用 Proxy 实现代理对象 Vue 2:使用 Object.defineProperty 来实现响应式数据绑定。这种方式有一些限制,比如不能检测到属性的添加或删除(需要使用 $set 或 Vue.set),以及对数组的一些操作无法被拦截。 Vue 3:引入了 ES6 的 Proxy 对象来实现代理,解决了上述问题。Proxy 可以拦截更多的操作类型,如属性的添加和删除,并且对数组的操作也更加友好。此外,Proxy 是深度响应式的,即不需要像 Vue 2 那样显式地递归遍历每个属性。
  2. 更好的性能 更少的依赖追踪:由于 Proxy 的特性,Vue 3 可以更精确地追踪依赖关系,减少了不必要的重新渲染。只有当相关的属性被访问时才会创建依赖,这使得应用在初始渲染时速度更快。
    批量更新机制:Vue 3 改进了批量更新机制,确保在同一个事件循环中发生的多次状态改变只触发一次界面更新,提高了效率。
  3. 细粒度的响应式控制 reactive 和 ref:Vue 3 提供了 reactive 函数用于创建一个响应式对象,而 ref 则用于创建一个包含单个值的响应式引用。通过 toRefs 等辅助函数可以将 reactive 对象中的所有属性转换为ref,从而允许细粒度地控制响应式行为。 computed:新的 computed API 提供了 getter/setter
    的定义方式,与 Vue 2 类似,但结合 Composition API 使用时更为灵活。
  4. 组合式 API (Composition API) 更好的逻辑复用:组合式 API 允许开发者更好地组织代码,通过组合函数(composables)的方式将相关逻辑分组,增强了组件之间的逻辑复用性。 TypeScript支持:组合式 API 更好地兼容 TypeScript,提供了更强的类型推断能力,使代码编写更加安全可靠。
  5. 内存管理 自动清理:Vue 3 的响应式系统能够自动清理不再使用的侦听器,减少了内存泄漏的风险。
  6. 更小的体积 树摇算法优化:得益于更高效的构建工具和打包策略,Vue 3 的核心库体积更小,加载时间更短。
  7. 更好的调试支持 Devtools 支持:Vue 3 的 Devtools 提供了更好的调试体验,包括对组合式 API 的支持,使得开发者可以更容易地追踪响应式数据的变化。

1-4.Proxy 底层如何实现响应式的?

Proxy 是 ES6 引入的一个内置对象,用于定义基本操作的基本行为(如属性查找、赋值、枚举、函数调用等)。通过 Proxy,你可以拦截并自定义这些操作的行为。

  1. 创建响应式对象
    Vue 3 使用 reactive 函数将普通对象转换为响应式对象。这个过程实际上是使用 Proxy 来拦截对对象属性的访问和修改。
//const target = {};
//const proxy = new Proxy(target, handler);//接受2个参数,一个是对象,一个是handler函数
 1. 创建响应式对象
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key); // 追踪依赖
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      trigger(target, key); // 触发依赖更新
      return result;
    }
  });
}
track:追踪依赖关系,即记录哪些依赖(例如计算属性或侦听器)读取了该属性。
trigger:触发依赖更新,当属性发生变化时通知所有相关依赖进行更新。
2.追踪依赖(Track Dependencies)
function track(target, key) {
  if (!activeEffect) return;

  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }

  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set();
    depsMap.set(key, dep);
  }

  dep.add(activeEffect);
  activeEffect.deps.push(dep);
}
targetMap:一个弱映射(WeakMap),它将原始对象映射到它们各自的依赖关系映射表。
activeEffect:当前活动的依赖,通常是一个计算属性或侦听器。
3. 触发依赖更新(Trigger Dependencies)
 当响应式属性发生变化时,Vue 会遍历所有与该属性相关的依赖,并通知它们重新计算或更新。
 function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  const dep = depsMap.get(key);
  if (dep) {
    for (const effect of dep) {
      if (effect !== activeEffect) { // 避免递归触发
        effect();
      }
    }
  }
}
4. 处理嵌套对象
对于嵌套的对象属性,Vue 会递归地将每个子对象也转换为响应式对象,确保整个对象树都是响应式的。
function reactive(target) {
  if (typeof target !== 'object' || target === null) {
    return target;
  }

  if (isReactive(target)) {
    return target;
  }

  const observed = new Proxy(target, {
    get(target, key, receiver) {
      if (isObject(target[key])) {
        return reactive(target[key]); // 递归处理嵌套对象
      }
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) {
        trigger(target, key);
      }
      return result;
    }
  });

  return observed;
}

总结
Proxy 提供了一种强大的方式来拦截对象的操作,使得 Vue 3 能够更高效地实现响应式系统。
依赖追踪 和 触发更新 是响应式系统的核心机制,确保当数据变化时,视图能够自动更新。
递归处理嵌套对象 确保整个对象树都是响应式的,而不仅仅是顶层对象。

1-5.Proxy 底层如何针对数组实现响应式的详细机制

在 Vue 3 中,Proxy 对象不仅用于实现普通对象的响应式,也用于处理数组的响应式。由于数组有自己的一套方法(如 push, pop, shift, unshift, splice, sort, reverse 等),Vue 需要确保这些方法的调用也能触发依赖更新。以下是 Proxy 底层如何针对数组实现响应式的详细机制:

  1. 数组的特殊性

与普通对象不同,数组有一些特定的方法和属性(如 length),它们可能会改变数组的内容或结构。为了确保这些操作能够触发依赖更新,Vue 的 Proxy 实现需要特别处理这些情况。
2. 处理数组方法
Vue 通过拦截常见的数组变异方法来确保它们能触发响应式更新。具体来说,Vue 拦截了以下方法,并为每个方法添加了适当的逻辑以确保响应式行为:

  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse
    示例:拦截 push 方法
const handler = {
   
  get(target, key, receiver) {
   
    if (typeof key === 'string' && key in Array.prototype) {
   
      return new Proxy(Array.prototype[key], {
   
        apply(target, thisArg, argumentsList) {
   
          const result = Reflect.apply(target, target, argumentsList);
          trigger(target); // 触发依赖更新
          return result;
        }
      });
    }

    track(target, key); // 追踪依赖
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
   
    const oldValue = target[key];
    const result = Reflect.set(target, key, value, receiver);
    if (oldValue !== value) {
   
      trigger(target, key); // 触发依赖更新
    }
    return result;
  }
};

function reactive(target) {
   
  return new Proxy(target, handler);
}

在这个例子中,当调用数组的 push 方法时,它会触发依赖更新,因为我们在代理的 get 拦截器中进一步代理了数组原型上的方法,并在每次调用时触发依赖更新。

  1. 处理 length 属性

数组的 length 属性也是一个特殊的属性,因为它可以影响数组的内容(例如,设置 length 可以截断数组)。Vue 通过拦截对 length 属性的访问和修改来确保这种变化也能触发响应式更新。

const handler = {
   
  get(target, key, receiver) {
   
    if (key === 'length') {
   
      track(target, key); // 追踪 length 属性的依赖
    }
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
   
    const oldValue = target[key];
    const result = Reflect.set(target, key, value, receiver);

    if (key === 'length' && value < target.length) {
   
      // 如果新的长度小于当前长度,则触发所有被删除元素的依赖更新
      for (let i = value; i < target.length; i++) {
   
        trigger(target, i);
      }
    } else if (oldValue !== value) {
   
      trigger(target, key); // 触发依赖更新
    }

    return result;
  }
};

4.使用 getOwnPropertyDescriptor 处理不可配置的属性

某些数组方法和属性(如 length)是不可配置的,这意味着你不能直接覆盖它们。为了确保这些方法和属性的行为正确,Vue 使用 getOwnPropertyDescriptor 来获取并保留原始描述符,然后在其基础上应用拦截逻辑。

const handler = {
   
  getOwnPropertyDescriptor(target, key) {
   
    const descriptor = Reflect.getOwnPropertyDescriptor(target, key);
    if (descriptor) {
   
      // 保留原始描述符,但允许拦截其访问和修改
      descriptor.configurable = true;
      descriptor.writable = true;
    }
    return descriptor;
  },
  // 其他拦截器...
};

总结

  • 拦截数组方法:Vue 拦截了常见的数组变异方法(如 push, pop, splice 等),并在每次调用这些方法时触发依赖更新。
  • 处理 length 属性:Vue 特别处理了 length 属性的变化,确保设置 length 也能触发相应的依赖更新。
  • 使用 getOwnPropertyDescriptor:对于不可配置的属性,Vue 保留原始描述符,并在此基础上应用拦截逻辑,以确保响应式行为。

通过这种方式,Vue 3 的 Proxy 实现能够有效地将数组转换为响应式对象,确保任何数组内容或结构的变化都能正确地触发视图更新。这使得开发者可以在 Vue 应用中透明地使用数组,而无需担心响应式问题。

1-6.v-model 可以在组件上使用以实现双向绑定。

从 Vue 3.4 开始,推荐的实现方式是使用 defineModel() 宏:详情查看官网
v-model 在父组件中提供了一种简便的方式来设置和监听子组件的状态。
defineModel() 在子组件中简化了对 v-model 的支持,使得你可以轻松地创建响应式的双向绑定,并且不需要手动编写事件处理器或属性声明。
实际工作流程

  1. 初始渲染:
    父组件将 parentValue 的当前值作为 modelValue 传递给子组件。
    子组件通过 defineModel()接收这个值并将其存储在一个响应式变量 model 中。
  2. 用户交互:
    当用户在子组件的输入框中输入内容时,v-model 自动更新 model 的值。
    defineModel() 检测到 model 发生变化后,触发 update:modelValue 事件并将新值发送回父组件。
  3. 父组件更新: 父组件接收到 update:modelValue 事件后,更新 parentValue 的值。 这个新的值又会作为modelValue 再次传递给子组件,形成闭环。

例如:
父组件:

<!-- ParentComponent.vue -->
<template>
  <ChildComponent v-model="parentValue" />
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const parentValue = ref('Initial Value');
</script>

当你在父组件中使用 <ChildComponent v-model="parentValue" /> 时,实际上发生了以下几件事情:
传递属性:v-model 默认会将 parentValue 的值作为 modelValue 属性传递给子组件。
监听事件:同时,v-model 还会在子组件上监听一个名为 update:modelValue 的自定义事件。当这个事件被触发时,父组件会更新 parentValue 的值。

子组件:

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

// 使用 defineModel() 创建一个双向绑定的 model 引用;
//defineModel() 返回的值是一个 ref。它可以像其他 ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用:
const model = defineModel();
</script>

<template>
  <!-- 可以直接使用 model 或者使用 v-model 绑定 -->
  <input :value="model" @input="model = $event.target.value" />
  <!-- 或者更简洁地使用 v-model -->
  <input v-model="model" />
</template>

子组件中的 defineModel()
在子组件中,defineModel() 宏会自动处理与 v-model 相关的逻辑:
接收 modelValue:defineModel() 自动生成一个响应式引用(类似于 ref),该引用与父组件传递的 modelValue 绑定。
触发更新事件:当你修改通过 defineModel() 获取的模型时,它会自动触发 update:modelValue 事件,并将新的值传递回父组件,从而实现双向绑定。

在这里插入图片描述

2.爷传孙的几种方式

在Vue中,可以通过爷(祖父)组件将数据传递给孙子组件的方式通常有两种:通过中央事件总线(Event Bus) 和 通过Provide / Inject。

  1. 通过中央事件总线(Event Bus):
    在这里插入图片描述使用场景: 适用于简单的、非常频繁的通信。当组件层级较深,或者需要在不直接关联的组件之间传递数据时,事件总线是一种简便的方式。

全局性: 事件总线是一个全局实例,可以在整个应用中使用,但这也可能导致不同组件之间的耦合性增加。
2. 通过Provide / Inject;Vue 3中,provide 和 inject 提供了一种非常便捷的方式来进行跨层级组件通信,允许祖先组件向其所有子孙组件传递数据,而无需通过中间组件显式地传递props。
也就是爷爷组件直接通过provide 传递数据,孙子页面或者儿子页面直接可以通过inject接受。
如果需要爷爷先传给父亲,需要将处理的数据传给儿子,那可以通过provide爷爷传给父亲,父亲inject接受,然后父使用provide传给儿子,儿子inject接受。
在这里插入图片描述使用场景: 适用于需要在组件层级中传递数据,而且通常用于祖父-父亲-子孙这样的层级结构。provide 和 inject 提供了更直接的组件层级关系,适用于复杂的数据传递场景。

局部性: Provide / Inject 的数据传递是局部的,只在 provide 组件的子孙组件中可用。这有助于减少全局状态,使得组件之间的通信更加可控。

3.Vue中组件通信的方式

Vue.js 中组件间通信可以通过多种方式实现,以下是 Vue 组件通信的六种常见方法:

  1. Props 和 Events

    • Props:父组件向子组件传递数据的主要方式。在子组件中声明props选项以接收来自父组件的数据。
      <!-- 父组件 -->
      <child-component :my-prop="parentData"></child-component>
      
      <!-- 子组件 -->
      <script>
      export default {
                
        props: ['myProp'],
        // ...
      }
      </script>
      
    • Events (自定义事件):子组件通过 $emit 触发事件向父组件发送信息。
      <!-- 子组件内部 -->
      <button @click="notifyParent">点击通知</button>
      
      <script>
      export default {
                
        methods: {
                
          notifyParent() {
                
            this.$emit('update-data', someValue);
          }
        },
        // ...
      }
      </script>
      
      <!-- 父组件 -->
      <child-component :my-prop="parentData" @update-data="handleUpdate"></child-component>
      
  2. $refs

    • 当需要直接操作子组件实例或调用其方法时,可以使用 ref 在父组件中引用子组件,并通过 $refs 访问它。
      <!-- 子组件 -->
      <child-component ref="childRef"></child-component>
      
      <!-- 父组件 -->
      <script>
      export default {
                
        mounted() {
                
          this.$refs.childRef.someMethod();
        },
        // ...
      }
      </script>
      
  3. Vuex

    • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。当多个组件之间存在复杂的共享状态时,可以通过创建和操作 Vuex store 来进行跨组件通信。
  4. Event Bus(全局事件总线)

    • 创建一个全局事件总线(通常是新建一个 Vue 实例),允许任何组件通过触发和监听该实例上的事件来进行通信。
  5. provide/inject

    • Vue 提供了一种依赖注入的方式,允许祖先组件将数据提供给任意深度的后代组件,而无需在每一个层级手动传递。这对一些大型应用中组织多级嵌套组件间的通信非常有用。
  6. a t t r s / attrs/ attrs/listeners

    • $attrs 包含了父作用域中未绑定到 prop 的特性绑定(attribute bindings)。$listeners 包含了父作用域中的所有 v-on 事件监听器。这两个属性可以帮助实现非父子组件之间的通信,通常用于封装组件库的情况。

总结起来,在实际开发中,应根据项目需求和组件间的关系选择合适的通信方式。对于简单场景,通常首选 Props 和
Events;对于复杂状态管理,则可能需要引入 Vuex;对于不相关的组件间通信或者跨层级通信,可以考虑 Event Bus 或
provide/inject。

4.computed与watch方法区别

computed 计算属性: 用途: 用于根据其他响应式数据的变化计算出一个新的值,并将其缓存,只有依赖的响应式数据发生变化时,计算属性才会重新计算。
watch 观察者: 用途: 用于观察指定的数据,当数据发生变化时执行一些操作。可以用于执行异步操作、复杂逻辑等。 语法: 定义在 watch 对象内,可以监听一个数据或多个数据。 在选择使用 computed 还是 watch
时,主要取决于你的需求。如果需要根据其他数据计算得到一个新值,而且希望这个值具有缓存特性,可以使用
computed。如果需要在数据变化时执行一些操作,比如异步请求或者复杂的逻辑,可以使用 watch。通常情况下,computed
更适合处理派生数据(派生数据是指从应用状态或其他数据中计算而来的新数据),而 watch 更适合处理副作用。
1.使用watch监听computed里的属性
虽然你可以直接使用watch来监听一个computed属性的变化,但这并不是最常见的情况,因为computed属性本身已经能够很好地处理依赖数据的变化并作出反应。不过,在某些特定情况下,你可能确实需要监听computed属性的变化。
新的 computed API 提供了 getter/setter 的定义方式
计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:
比如,用watch监听computed里面的count属性:
computed: {
count: {
get() {
return this.internalCount;
},
set(newValue) {
this.internalCount = newValue;
}
}
},
watch: {
count(newVal, oldVal) {
console.log(count changed from ${oldVal} to ${newVal});
}
}
// 在模板或其他地方使用 count
// 当用户修改 count的值时,会自动更新 internalCount 的值

4-1.watch如何取消监听

通过保存 watch 或 watchEffect 的返回值并调用它,你可以在任何需要的时候取消监听。组合式 API 中工作,还可以利用 Vue 提供的生命周期钩子如 onUnmounted 来确保监听器在组件销毁时被正确地取消。

// 在某个方法中或生命周期钩子中调用以取消监听
function someMethod() {
  // 假设某些逻辑后决定不再监听
  stopWatch();
}
// 或者在组件卸载时取消监听(例如在 onUnmounted 钩子中)
import { onUnmounted } from 'vue';

export default {
  setup() {
    const message = ref('Hello World');
    const stopWatch = watch(message, (newValue, oldValue) => {
      console.log(`Message changed from ${oldValue} to ${newValue}`);
    });

    // 组件卸载时取消监听
    onUnmounted(() => {
      stopWatch();
    });

    return {
      message,
    };
  }
};
watchEffect 类似于 watch,但它会立即执行一次,并且自动追踪其内部使用的响应式数据。它也返回一个取消监听的函数。
import { ref, watchEffect, onUnmounted } from 'vue';

export default {
  setup() {
    const count = ref(0);

    // 创建一个 watchEffect 监听器,并保存取消监听的函数
    const stopWatchEffect = watchEffect(() => {
      console.log(`Count is: ${count.value}`);
    });

    // 组件卸载时取消监听
    onUnmounted(() => {
      stopWatchEffect();
    });

    return {
      count,
    };
  }
};

4-2.watchEffect与watch区别?

1.自动依赖收集 vs 显式指定依赖

watch 则要求明确指出要监视的数据源。这可以是一个 ref、一个计算属性、一个 getter 函数(有返回值的函数)、一个响应式对象或者上述类型的数组。通过这种方式,watch 提供了更精细的控制,允许开发者指定具体的监听目标,并且可以选择是否进行深度监听。
watchEffect 的核心特点之一是它能够自动追踪回调函数中使用的响应式数据,而不需要开发者显式地指定要监听的数据源。这意味着你可以直接在 watchEffect 的回调函数中访问任何响应式的属性或对象,API 会自动识别这些依赖,并在它们发生变化时重新运行回调。

2.回调函数参数的不同

// 如果你需要获取到变化前后的新旧值来进行某些操作,使用 watch 获取新旧值;
watch(question, (newQuestion, oldQuestion) => {
  console.log(`Question changed from ${oldQuestion} to ${newQuestion}`);
});
// 使用 watchEffect 不接收新旧值;watchEffect 的回调函数并不提供这样的参数,而是依赖于闭包来捕获外部状态的变化:
watchEffect(() => {
  console.log(`current question: ${question.value}`);
});

3.执行时机不同

watchEffect 在创建时会立即执行一次回调函数,并在此过程中收集所有依赖项。一旦任何一个依赖项发生改变,watchEffect
就会再次触发回调。这种立即执行的行为使得 watchEffect 成为初始化阶段同步任务的理想选择7。

而 watch 默认情况下是懒惰执行的,即只有当被监听的数据首次发生变化时才会触发回调。不过,可以通过配置选项 { immediate:
true } 来改变这一行为,使 watch 在定义时也立即执行。此外,watch 还支持更多高级配置,比如设置 flush
模式来决定副作用函数相对于 DOM 更新的时间点 (pre, post, 或 sync),这对于优化应用性能非常有用

总结 watchEffect:适用于希望快速实现监听效果且不需要关心新旧值的情况;自动追踪依赖,简化代码编写;立即执行。 watch:适合那些需要精确控制监听范围和条件的场景;可以接收新旧值作为参数;懒加载,默认不立即执行,除非设置了 immediate 选项。

4-3.详解watchEffect

watchEffect 可以直接在 watchEffect 的回调函数中访问任何响应式的属性或对象;用于创建一个响应式数据的副作用,类似于 watch,但不需要指定要监听的具体属性。watchEffect 它会立即执行传入的回调函数,并且在这个过程中自动追踪回调中使用的所有响应式依赖。一旦这些依赖发生变化,watchEffect 将会重新执行回调函数。这意味着 watchEffect 在其创建时会立刻运行一次,这与 watch 默认的懒执行行为不同,后者仅在监听的数据源发生变化时才触发回调。
1:立即执行的行为;当 watchEffect 被调用时,它会在组件渲染时立即调用一次回调函数。这使得它可以用来处理那些需要在初始化阶段就同步执行的任务,比如根据初始状态设置某些值或进行 DOM 操作。此外,如果回调函数产生了副作用,那么 watchEffect 会自动追踪这些副作用所依赖的响应式属性,并在这些属性变化时重新执行回调
import { reactive, watchEffect } from ‘vue’;
const state = reactive({ count: 0 });
watchEffect(() => {
console.log(count is now ${state.count});
});

2:watchEffect 的另一个特点是它能够自动地追踪所有在回调函数内部访问到的响应式数据源。开发者不需要显式地指定要监听哪些属性,只需要确保在回调函数内使用了所需的响应式变量即可。这种方式简化了开发流程,减少了手动配置的工作量。例如,如果我们有一个包含多个响应式属性的对象,我们可以直接在 watchEffect 的回调中引用这些属性,而无需单独为每个属性设置监听器:
const obj = reactive({
name: ‘Alice’,
age: 25
});
watchEffect(() => {
console.log(name is ${obj.name}, age is ${obj.age});
});

这里,watchEffect 不仅会跟踪 obj.name 和 obj.age 的变化,还会在任何一个属性更新时重新运行回调函数。
3:配置选项;虽然 watchEffect 默认是立即执行并开启深度监听,但你仍然可以通过传递一个选项对象来调整其行为。例如,你可以通过设置 flush 选项来控制副作用函数的执行时机,如同步执行 (sync)、DOM 更新前 (pre) 或 DOM 更新后 (post) 执行12
watchEffect(
() => {
// 回调逻辑
},
{
flush: ‘post’ // 在 DOM 更新之后执行
}
);
综上所述,watchEffect 提供了一种简单而强大的机制来响应数据的变化,并且由于它会立即执行回调函数,因此非常适合用于那些需要即时反应的应用场景;

5.插槽有哪些?

在Vue.js中,插槽(Slot)是一种用于在父组件中向子组件传递内容的机制。插槽则是用来传递 DOM 结构或片段。插槽允许你定义一个占位符,然后在父组件中填充具体的内容。Vue.js提供了多种类型的插槽,包括默认插槽、具名插槽、作用域插槽等。以下是一些常见的插槽类型:

<!-- ChildComponent.vue -->
<template>
  <div>
    <h2>子组件内容</h2>
    <slot name="header"></slot> <!-- 具名插槽 -->
    <slot></slot> <!-- 默认插槽 -->
     <slot :data="childData"></slot> <!-- 作用域插槽 -->
  </div>
</template>


插槽的意义如下:

1可复用性: 插槽允许你在父组件中注入内容,这使得组件更加灵活和可复用。通过插槽,你可以在不修改组件内部代码的情况下,向组件中插入不同的内容,从而实现更广泛的复用性。

2布局控制: 插槽允许父组件控制子组件的布局和结构。父组件可以通过插槽来定制子组件的外观,使得组件在不同的上下文中可以以不同的方式呈现。

3灵活的组件结构:
插槽允许你在组件内定义占位符,并在父组件中填充内容。这种机制使得组件可以更容易地适应不同的设计和布局需求,从而提高了组件的灵活性。

4分发内容:
插槽允许子组件向父组件分发内容,这在某些情况下非常有用。例如,子组件可以通过插槽将某些状态信息传递给父组件,使得父组件能够动态地响应子组件的状态变化。

5提高可维护性:
插槽可以提高代码的可维护性。通过将组件的结构和样式分离,你可以更容易地理解和维护代码,因为父组件和子组件的关注点得到了清晰的分离。

Vue 的插槽机制主要是为了实现父组件向子组件注入内容,而不是让子组件向父组件分发内容
作用域插槽:有时我们希望从子组件传递数据给父组件,这时可以使用作用域插槽。通过这种方式,子组件可以将数据作为属性传递给父组件提供的插槽模板。

<!-- 子组件 -->
<template>
  <div>
    <slot :user="user"></slot>
  </div>
</template>
<script>
export default {
  data() {
    return {
      user: { name: 'John Doe' }
    }
  }
}
</script>

<!-- 父组件 -->
<ChildComponent v-slot:default="slotProps">
  <p>{
  { slotProps.user.name }}</p>
</ChildComponent>

具名插槽:当需要多个插槽时,可以使用具名插槽来区分它们。每个插槽都有一个名称,父组件可以根据这个名称来指定要插入哪个插槽。

<!-- 子组件 -->
<template>
  <div>
    <slot name="header"></slot>
    <slot></slot> <!-- 默认插位 -->
    <slot name="footer"></slot>
  </div>
</template>

<!-- 父组件 -->
<ChildComponent>
  <template v-slot:header>
    <h1>这里是头部</h1>
  </template>
  <p>这是主体内容。</p>
  <template #footer>
    <p>这里是底部</p>
  </template>
</ChildComponent>

6.Vue2中的data()为啥是函数不是对象

在 Vue 2 中,data 选项为函数而不是对象的设计是为了解决组件实例之间数据共享的问题。Vue的组件是可以复用的,当多个组件实例共享同一个对象时,如果该对象是引用类型,就可能导致一个组件的状态变化影响到其他组件。
使用函数返回一个新的对象,确保每个组件实例都拥有独立的数据对象。这样,每个组件实例的 data 都是一个独立的数据作用域,不会互相影响。当组件实例被创建时,data 函数会被调用,返回一个新的数据对象。
总之:如果是对象的话,当对象是引用类型数据他是一个地址指向另一个指针,当数据发送改变的时候会影响整个对象的属性发生改变。而作为一个函数被其他对象调用的时候,就各自有自己的作用域,修改属性不会对其他地方造成影响

7.bable底层是如何将es6语法转为被低版本浏览器识别的?

安装 Babel 及其相关的插件和预设,以及 Babel Loader:
npm install --save-dev @babel/core @babel/preset-env babel-loader
这里 @babel/core 是 Babel 的核心库,@babel/preset-env 是一个智能预设,可以根据你指定的目标环境自动决定需要转译的 JavaScript 特性。babel-loader 是 Webpack 专用的 loader。

Babel 是一个用于 JavaScript 编译的工具,它的主要功能之一是将 ECMAScript 2015+(ES6+)的代码转换为向后兼容的 JavaScript 代码,以便在低版本的浏览器中运行。Babel 实现这个转换的方式主要涉及4个步骤:

  1. 解析 (Parsing) 首先,Babel 使用解析器(默认是 @babel/parser)将源代码解析成 AST。这个过程会根据输入的代码生成一个树形的数据结构,其中每个节点代表代码中的一个元素(例如变量声明、函数调用等)。解析器理解现代
    JavaScript 语法,因此它可以正确地处理 ES6+ 特性。

  2. 遍历 (Traversing) 一旦有了 AST,Babel 就开始遍历这棵树。它会访问每一个节点,并根据配置好的插件和预设决定是否需要对该节点进行转换。Babel 使用 @babel/traverse
    来执行这一操作,它提供了一种灵活的方法来遍历和操作 AST。

  3. 转换 (Transforming) 在遍历过程中,当遇到符合特定条件的节点时,相应的插件会被触发来修改 AST。例如,如果有一个箭头函数表达式(ES6+),那么对应的转换插件就会将这个表达式替换为传统的函数表达式(ES5)。这种转换是基于规则的,每个插件都定义了自己的一套规则来处理特定类型的节点。

  4. 生成 (Generating) 完成所有必要的转换后,Babel 使用 @babel/generator 将修改后的 AST 再次转换回字符串形式的代码。这个步骤还会添加必要的注释或源映射信息,以便调试。

插件与预设

插件:单独的 Babel插件通常只负责一种或几种特定的转换任务。例如,@babel/plugin-transform-arrow-functions
只用于转换箭头函数。
预设:预设是一组插件的集合,旨在一次性应用多个相关的转换。比如,@babel/preset-env可以自动确定所需的插件,以支持目标环境的特性,并且可以根据指定的目标浏览器或 Node.js 版本来优化输出代码。

示例流程

假设你有以下 ES6 代码:
const add = (x, y) => x + y;
经过 Babel 处理后,这段代码可能会被转换为:
var add = function(x, y) {
  return x + y;
};
在这个例子中,=> 箭头函数语法被转换成了传统的函数表达式,使得这段代码能够在不支持 ES6 的环境中运行。

8.webpack编译流程

  • Webpack是一个模块打包工具,它会递归地解析依赖关系,并将所有模块打包成一个或多个bundle。
    - 通过配置entry、output、loader和plugin,可以自定义打包过程。
    - 优化方法包括:使用Tree Shaking去除未使用的代码,使用Code Splitting进行代码分割,启用Source Maps进行调试,以及使用缓存提高构建速度。

Webpack 的编译流程可以简化概括为以下几个关键步骤:
在这里插入图片描述

  1. 初始化参数 (Initialization)

    • 读取配置:Webpack 从配置文件(如 webpack.config.js)和命令行参数中读取配置信息,并将其合并为最终的配置对象。
    • 参数校验与补充:使用诸如 yargs 等工具处理CLI参数,形成完整的配置项,包括入口(entry)、输出(output)、loader、插件等。
  2. 初始化编译器 (Compiler Creation)

    • 根据上一步骤得到的配置,初始化一个 Compiler 对象,加载所有配件的插件,这个对象是整个编译过程的核心,负责管理和协调编译任务。
    • 注册插件:加载所有配置的插件,并调用插件的 apply 方法,这样插件就能监听并参与到编译生命周期的各个阶段。
  3. 编译阶段 (Compilation)

    • 寻找入口文件:根据配置中的 entry 属性,Webpack 找到应用程序的入口点。
    • 构建模块依赖图:从入口文件开始,Webpack 递归地解析每个模块及其导入的依赖关系,形成一个模块依赖图。
    • 模块转换:通过Loader,对每个模块的内容进行转换,如将ES6模块转换为CommonJS模块,将SCSS转换为CSS,将TypeScript转换为JavaScript等。
    • 优化和处理资源:Webpack在编译过程中,会执行一系列优化操作,例如Tree Shaking、Scope Hoisting等,并且通过插件和loader处理图片、字体等资源。
  4. 输出阶段 (Output)

    • 分割代码块(Chunks):根据模块的依赖关系和SplitChunksPlugin等配置,将模块分组成多个代码块(chunks),比如common
      chunk、vendor chunk等。
    • 生成最终资源:Webpack依据输出配置,将每个chunk转换成单独的文件(如bundle.js),同时生成manifest数据以便按需加载和映射关系。
    • 写入文件系统:将编译后的资源输出到指定的文件夹中。
  5. 后期处理与插件介入

    • 在整个编译过程中,插件可在不同阶段的生命周期钩子中执行自定义操作,比如清理旧文件、生成资产清单、注入环境变量等。

总的来说,Webpack
的编译流程是一个高度可定制的过程,通过处理模块依赖关系、转化和合并资源,最终输出满足项目需求的静态资源。在整个过程中,Webpack 强大的插件系统使得开发者能够灵活地扩展和优化构建过程。
loader和plugin区别
loader主要用于转换某些类型的模块,它是一个转换器。如将ES6模块转换为CommonJS模块
plugin是插件,它是对webpack本身的扩展,是一个扩展器.比如打包优化,文件压缩等等.

8-1.Webpack是怎么进行代码压缩的,为什么比较慢?与vite有啥区别**

Webpack 使用 UglifyJS(或 Terser,取决于配置)来进行代码压缩。UglifyJS/Terser 是一个用于
JavaScript 的压缩和混淆工具,它能够删除不必要的空格、重复的代码、缩短变量名等,从而减小文件体积。下面是一些
UglifyJS/Terser 进行代码压缩的主要步骤:
1.解析(Parsing): 将源代码解析成抽象语法树(AST),这是一个树状结构,表示代码的抽象结构。

2转换(Transformation): 对 AST 进行变换和优化,包括删除不必要的代码、简化表达式、提取公共部分等。

3生成(Code Generation): 将经过转换的 AST 重新生成为压缩后的 JavaScript 代码。

4混淆(Obfuscation): 可选的步骤,通过缩短变量名、替换字符串等方式,增加代码的难以阅读性。

代码压缩在 Webpack 中默认是开启的,但为什么有时候会感到比较慢呢?

原因可能包括:

代码量较大: 如果项目中有大量的 JavaScript代码需要压缩,这个过程就会相对耗时。尤其是在大型项目中,压缩工具需要处理大量的文件和代码。

混淆操作: 如果开启了混淆,混淆操作可能会引入更多的计算,导致压缩时间增加。

启用 Source Map: 如果开启了 Source Map 选项,UglifyJS/Terser
会生成与源代码映射的文件,以便调试。生成 Source Map 也需要一定的时间。

CPU 资源: 压缩操作是 CPU 密集型任务,如果计算机的 CPU 资源较为有限,也可能导致压缩速度较慢。

为了提高代码压缩的速度,可以考虑以下几个方面:

使用 Terser 替代 UglifyJS(Webpack 5 默认使用 Terser)。 调整压缩配置,去除不必要的混淆操作。
确保代码结构简洁,避免不必要的冗余代码。 在开发环境中尽量减少对代码的压缩,以提高构建速度。

8-2.那Terser 有什么性能上的提升呢?

Terser 是一个用于 JavaScript 的压缩工具,是 UglifyJS 的一个分支,用于进行代码压缩和混淆。相较于
UglifyJS,Terser 在性能和功能上进行了一些改进,带来了一些性能上的提升和优势:

更好的压缩率: Terser 在压缩算法方面进行了改进,对代码进行更优化的压缩,可能会产生更小的文件体积。

更快的压缩速度: Terser 在处理大型项目时可能比 UglifyJS 更快,这对于大型前端应用的构建过程是一个显著的优势。

ES6+ 支持: Terser 更好地支持 ECMAScript 2015+(ES6+)语法,包括箭头函数、模板字符串等新特性,使得现代
JavaScript 项目能够更好地受益于代码压缩。

维护性: Terser 是在 UglifyJS 的基础上进行的改进,它会继续受到维护和更新,确保在将来能够适应新的 ECMAScript
标准和代码规范。

Tree-shaking 的优化: Terser 在 Tree-shaking
方面进行了优化,能够更好地消除未使用的代码,减小最终生成文件的体积。

总体而言,Terser 提供了更先进的压缩算法、更好的性能、更好的 ES6+ 支持和更好的维护性。在现代前端开发中,Terser
是一个被广泛使用的压缩工具,特别适用于构建过程中的代码压缩和优化。

8-3.那vite相对于webpack做了哪些性能上的提升呢

以下是 Vite 相对于 Webpack 在性能上的一些优势:

1.快速的冷启动: Vite 使用了一种基于浏览器原生 ES 模块(ESM)的开发服务器,称为 “esbuild”,Vite 利用了原生 ES 模块导入的功能,能够实现极速的冷启动和热更新速度,非常适合开发阶段使用。由于只需编译和处理当前正在编辑的文件,而不是整个项目,因此启动时间更短。

2.HMR(热模块替换)性能优化: Vite 利用了浏览器原生的模块系统,支持 HMR 的性能更好。在开发环境下,Vite 不需要像
Webpack 那样通过 WebSocket 将更新的模块推送到客户端,而是直接使用浏览器的 ESM 特性,实现更快的 HMR

8-1.vite相较于webpack做了哪些提升?

  1. 启动速度与冷启动时间

    Vite 使用原生 ES 模块导入(ESM)来提供快速的冷启动时间。它通过浏览器原生支持的 ESM 动态导入特性,使得开发服务器几乎可以立即启动。
    Vite 在开发模式下不需要打包整个应用,而是按需编译单个模块,这大大减少了启动时间和热更新的时间。
    Webpack 在启动时需要对整个项目进行打包,这可能需要几秒到几十秒不等,尤其是在大型项目中。
    Webpack 的 HMR(Hot Module Replacement)虽然可以实现模块热替换,但在某些情况下配置和性能可能不如 Vite。

  2. 热更新(HMR)效率

    • Vite 实现了高效的模块热更新,当开发者修改代码后,它只重新编译改动的部分,并通过HMR机制通知浏览器加载变更的模块,而非整体刷新页面或重新编译整个应用。
    • 相比之下,Webpack 的 HMR 也会尽量做到局部更新,但在大型项目中,尤其是在处理大量依赖关系时,其热更新速度可能会慢于 Vite。
  3. 构建速度
    Vite 在生产构建时使用 Rollup 进行打包。Rollup 是一个专注于库打包的工具,生成的代码通常更小且更高效。
    Vite 的构建速度相对较快,特别是在处理大型项目时。
    Webpack 的构建速度取决于项目的复杂性和配置。对于大型项目,尤其是那些包含大量依赖和复杂配置的项目,构建时间可能会较长。
    Webpack 提供了多种优化手段(如 Tree Shaking、代码分割等),但这些优化有时会增加构建时间。

  4. 预构建依赖

    • Vite 利用了 esbuild 进行快速的预构建处理,esbuild 由 Go 语言编写,具有非常高的编译性能,能够迅速解析和转换依赖模块。
    • 而 Webpack 依赖 JavaScript 编写的插件体系,虽然功能强大但速度相对较慢,特别是在项目规模较大时。
  5. 开发体验

    • Vite 提供了更流畅的开发体验,减少了等待构建时间,使开发者能够更快地看到代码更改的效果。
    • 因为 Vite 不依赖 CommonJS 规范,它鼓励使用 ESM 进行模块导入,进一步优化了现代浏览器的加载性能。
  6. 配置复杂度

    • Vite 的配置文件相比于 Webpack 更加简洁,尤其是对于小型到中型项目,开箱即用的体验更好。Vite 的配置文件 vite.config.js 通常比 Webpack 的配置文件更简洁;Webpack 的配置较为复杂,特别是对于复杂的项目。你需要手动配置各种插件和加载器,以满足特定的需求
  7. 打包效率

    • Vite 在生产环境中依然可以选择使用 Rollup 进行最终的优化打包,Rollup 作为 tree-shaking 性能优秀的打包器,结合 Vite 的开发环境特性,使得整体项目的构建效率也有所提高。

总结起来
Vite 适合追求快速开发体验和简化配置的项目,特别是对于中小型项目或需要快速迭代的项目。
Webpack 适合需要高度定制化和复杂配置的大型项目,以及需要广泛生态系统支持的项目。Webpack 经过多年的发展,已经非常稳定,被广泛应用于各种规模的项目中
拓展:
ESM 提供了更现代、更灵活的模块化方式,支持静态导入/导出、动态导入、严格模式、作用域隔离、循环依赖处理等特性。这些特性使得 JavaScript 代码更加模块化、可维护和高效。随着浏览器和 Node.js 对 ESM 支持的不断增强,ESM 已经成为现代 JavaScript 开发的标准模块系统。

8-2.Vite 用Rollup 进行打包,那webpack用什么打包的?

Webpack 是一个模块打包器,它将应用程序中的所有资源(如
JavaScript、CSS、图片等)视为模块,并通过依赖关系图将这些模块打包成一个或多个最终的输出文件(通常是 JavaScript
文件)。Webpack 本身就是一个完整的构建工具,它负责处理以下任务:

  • 模块解析:识别不同类型的资源(如 JS、CSS、图片等),并将其转换为模块。
  • 依赖分析:构建模块之间的依赖关系图。
  • 代码转换:使用各种加载器(Loaders)对模块进行转换,例如将 TypeScript 转换为 JavaScript,将 SCSS 转换为 CSS 等。
  • 代码优化:通过插件(Plugins)进行代码优化,如 Tree Shaking、代码分割、压缩等。
  • 输出:将处理后的代码打包成最终的输出文件。

Webpack 的核心是其运行时(Runtime),它负责在运行时动态加载和执行模块。Webpack
使用自己的内部机制来处理上述所有步骤,不需要依赖其他外部打包工具。

Vite

Vite 在开发模式下利用浏览器原生支持的 ES 模块(ESM)特性,提供快速的冷启动和热更新体验。但在生产构建时,Vite 使用
Rollup 进行打包。Rollup 是一个专注于库打包的工具,具有以下特点:

  • Tree Shaking:Rollup 可以自动移除未使用的代码,生成更小的包。
  • 代码分割:Rollup 支持代码分割,可以将代码分割成多个小包,按需加载。
  • 优化:Rollup 提供了多种优化手段,如代码压缩、变量名优化等。

Vite 的生产构建过程如下:

  1. 模块解析:Vite 仍然使用 ESM 来解析模块。
  2. 依赖分析:Vite 构建依赖关系图。
  3. 代码转换:Vite 使用 Rollup 插件系统来处理代码转换,类似于 Webpack 的加载器。
  4. 代码优化:Rollup 提供了丰富的插件来优化代码,包括 Tree Shaking、代码分割、压缩等。
  5. 输出:Rollup 将处理后的代码打包成最终的输出文件。

总结

  • Webpack:使用自己的内部机制进行模块解析、依赖分析、代码转换、代码优化和输出。它是一个完整的构建工具,不依赖于其他打包工具。尽管它依赖于插件比如插件(Plugins)进行代码优化,来扩展功能,但这些插件是 Webpack 生态系统的一部分,而不是独立的打包工具。
  • Vite:在开发模式下利用浏览器原生的 ESM 特性,在生产构建时使用 Rollup 进行打包。Vite 通过 Rollup 的插件系统来处理代码转换和优化。

8-3.如何配置Webpack以实现模块打包、代码分割、热更新等功能?

在Webpack中配置模块打包、代码分割和热更新(Hot Module Replacement, HMR)是一项常见的任务。以下是一个基本的配置示例,演示如何设置这些功能:

1. 模块打包

Webpack的核心功能就是将模块进行打包,这是默认行为,只需要正确配置入口(entry)和出口(output)即可。

//webpack.config.js
const path = require(‘path’);

module.exports = { // 入口文件 entry: ‘./src/index.js’,
// 输出目录及文件名 output: {
filename: ‘[name].bundle.js’,
path: path.resolve(__dirname, ‘dist’), }, };

2. 代码分割

为了实现按需加载和代码分割,可以使用import()动态导入语句,并结合SplitChunksPlugin或optimization配置项来提取公共chunk。
// webpack.config.js
const path = require(‘path’); const
HtmlWebpackPlugin = require(‘html-webpack-plugin’);
// 用于生成HTML并自动插入分割后的JS chunk

module.exports = { // … 其他配置不变 …

optimization: {
splitChunks: {
// 配置chunks分割策略
chunks: ‘all’, // 所有同步和异步chunk都进行分割
minSize: 30000, // 分割的最小大小
maxSize: 0, // 不限制最大大小
minChunks: 1, // 最少被引用次数
maxAsyncRequests: 5, // 最大异步请求次数
maxInitialRequests: 3, // 最大初始化请求次数
automaticNameDelimiter: ‘~’, // 连接符
name: true, // 自动生成名称
cacheGroups: { // 定义缓存组
vendors: { // 第三方库
test: /[\/]node_modules[\/]/,
priority: -10,
},
default: { // 默认缓存组
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
}, },

plugins: [
new HtmlWebpackPlugin({
template: ‘./src/index.html’, // HTML模板路径
inject: ‘body’, // JS注入位置,默认为head
}), ], };
````

3. 热更新(HMR)

要在开发环境下启用Hot Module
Replacement,你需要在webpack-dev-server的配置中开启HMR,并确保你的项目支持HMR。对于大部分基于React/Vue/Angular等现代前端框架的应用,通常需要额外的loader或plugin配合。

HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack =
require('webpack');

module.exports = {
       // ... 其他配置不变 ...

  // 开发服务器配置   devServer: {
    
    hot: true, // 开启HMR
    contentBase: path.join(__dirname, 'dist'), // 服务静态资源的目录
    publicPath: '/', // 设置构建后文件的访问路径
    compress: true, // 是否启用gzip压缩
    port: 3000, // 设置服务器端口   },

  // 使用webpack.HotModuleReplacementPlugin插件   plugins: [
    new HtmlWebpackPlugin({
     /* ... */ }), // 保持不变
    new webpack.HotModuleReplacementPlugin(), // 启用HMR插件   ],

  // 如果你使用的是TypeScript或Babel,还需要确保它们的配置支持HMR   module: {
    
    rules: [
      // TypeScript支持HMR
      {
    
        test: /\.tsx?$/,
        use: [
          'babel-loader',
          {
    
            loader: 'ts-loader',
            options: {
    
              transpileOnly: true,
              experimentalWatchApi: true,
              hmr: true, // 如果支持的话
            },
          },
        ],
        exclude: /node_modules/,
      },
      // Babel也需要相应的插件支持HMR
      {
    
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
    ],   }, };

// 对于React应用,还需在入口文件或者根组件中添加对HMR的支持 if (module.hot) {  
module.hot.accept(); } ```

请注意以上只是一个简化的示例,实际配置可能需要根据项目的具体需求进行调整。同时,有些特性可能随着Webpack版本升级而有所变化,请参考最新的官方文档进行配置。

9.vue3中hooks

主要让功能模块细分,项目维护性更高。hooks是封装了一些函数方法,以供外部调用。

1.在Vue 3中,引入了Composition API(组合式API),它提供了一种使用函数而不是基于对象的方式来组织组件的状态、计算属性和方法。虽然Vue并没有直接采用React中的“hooks”概念,但Composition
API的设计思想与React Hooks有异曲同工之妙。

Vue 3的Composition API提供了以下几个关键功能,它们可以被视为Vue中的“hooks”:
1.setup() 函数:
2.响应式API: reactive() 和 ref():用来创建响应式数据,分别对应于对象和基本类型的值。 readonly():创建只读响应式对象
3.computed(),watch()
4.生命周期钩子调用:
2自定义Hooks: 虽然Vue 3不直接支持自定义Hook的概念,但是由于Composition API的灵活性,开发者可以封装一些可复用的功能模块,通过组合不同的API函数来达到类似React Hooks的效果。

10.vue2中的this指什么?

在Vue 2中,this关键字的含义取决于它在哪个上下文中被引用。在Vue组件内部,this通常指代当前组件实例对象。
1.在methods、生命周期钩子函数和计算属性(computed):在这些地方使用this时,它指向当前Vue组件实例。
2.在模板表达式中: Vue模板中的表达式也会隐式地绑定到当前组件实例的上下文中,因此在模板内可以直接使用this访问数据和方法。


{ { this.message }}
< button @click=“this.showMessage”>Show Message< /button> < /div>
3.在不被Vue管理的上下文中: 如果在一个常规的JavaScript函数中使用this,或者在事件处理程序、定时器回调等非Vue上下文中直接使用,this可能不会指向Vue组件实例。在这种情况下,你可能需要通过箭头函数来保持this上下文,或者使用.bind(this)等方式确保this指向正确。
总结来说,在Vue
2的组件内部大部分情况下,this都是用来引用当前组件实例的,但在某些原生JavaScript环境中需特别注意this的指向问题。

引申:vue3中setup如何写类似与vue2中的this?
在Vue3中应尽量避免模拟Vue 2中的this行为,转而采用Composition API提供的更加直观和模块化的编程方式。尽管不推荐直接依赖,但在某些情况下可以通过getCurrentInstance()获取组件实例。
在Vue 3的Composition API(组合式API)中,由于setup()函数是一个纯函数,并且没有实例上下文,因此不能直接使用this关键字来访问组件实例。非要写的话就写在标签内部?

11.Vue 常用的修饰符有哪些应用场景

Vue.js 中的修饰符主要用于增强或修改组件绑定行为、事件处理等。以下是一些常用的Vue修饰符及其应用场景:比如控制事件传播、格式化用户输入以及父子组件间的数据同步等。
打个比方caputer:冒泡是从里往外冒,捕获是从外往里捕。
当捕获存在时,先从外到里的捕获,剩下的从里到外的冒泡输出。

1.事件修饰符:
1.stop:阻止事件冒泡,用于防止事件向上级元素传播。
< div @click=“parentClick”>
< button @click.stop=“childClick”>点击我不会触发父级事件
< /div>
2.prevent:阻止默认行为,如表单提交、链接跳转等。
3.capture:添加事件监听器时使用捕获阶段。
4.once:只触发一次事件处理器。
5.键盘事件修饰符:keyCode:监听特定键盘按下 .right:右键
.enter:当用户按下回车键时触发事件。
.space、.arrowLeft、.arrowRight 等:根据特定按键触发事件。
6.self :将事件绑定在自身身上,相当于阻止事件冒泡
8.passive:事件的默认行为为立即执行,无需等待事件 回调执行完毕
打个比方caputer:冒泡是从里往外冒,捕获是从外往里捕
当捕获存在时,先从外到里的捕获,剩下的从里到外的冒泡输出。
2.表单修饰符:
1.lazy:在 v-model 绑定的 input 元素上使用时,会在 change 事件而非 input 事件发生时更新数据,即懒惰更新(仅在失去焦点后更新)。
< input v-model.lazy=“message” type=“text”>
2.number:自动将用户的输入转换为数值类型,对于需要数字输入的情况非常有用
< input v-model.number=“age” type=“number”>
3.trim:自动过滤用户输入两端的空白字符
< input v-model.trim=“username” type=“text”>
4.动态指令修饰符:
.sync:在子组件中同步一个 prop 的变化到父组件,适用于 Vue 2.x(Vue 3 使用 v-model:propName 或 modelValue/update:modelValue 配对来代替)。

12.事件委托原理与优缺点

事件委托,也称为事件代理,在JavaScript编程中是一种技术,用于处理DOM元素的事件绑定。它的基本原理是利用事件冒泡(event bubbling)特性:当一个事件在DOM树中的某个元素上触发时,该事件会向上逐级传播到其所有祖先元素。
具体实现方式例如:假设有一个包含多个<li>元素的<ul>列表
使用事件委托,则只需在<ul>元素上绑定一个click事件监听器,当用户点击任意一个<li>时,该事件会冒泡到<ul>,然后在<ul>的事件处理器中通过event.target确定是哪个<li>被点击。
优点:- 减少内存消耗,避免对大量元素进行独立事件绑定。- 对于动态添加或删除的子元素,无需重新绑定或移除事件处理器。- 简化代码,特别是对于大型或动态生成的DOM结构。
缺点:- 需要额外的逻辑来判断事件源,对于某些复杂的交互场景可能会增加处理难度。- 对于不支持冒泡的事件类型,无法使用事件委托机制。

13.vuex和pinia区别

Vuex 和 Pinia 是 Vue.js 生态系统中两个不同的状态管理库,虽然它们的目标都是为了在大型单页应用中更好地管理和组织状态,但是它们在设计理念、API 设计、易用性以及与 Vue.js 版本的适配上有所不同。以下是两者的主要区别:

  1. 架构设计

    • Vuex 采用集中式的设计,有一个单一的状态树,并强调严格的 mutation 机制来更新状态。状态变更必须通过提交 mutation 或 dispatching actions 来完成。
    • Pinia 更偏向于去中心化设计,允许分布式的状态存储,每个 store 拥有自己的状态和操作逻辑,没有强制的 mutation 概念,可以直接修改状态,但仍然建议通过 actions 来执行副作用操作。
  2. API 设计

    • Vuex 使用 mutations、actions、getters 以及 modules 这些概念,其中 mutations 用于更改状态,actions 包含异步逻辑,getters 提供计算属性式的状态读取。
    • Pinia 提供 state、getters 和 actions,省去了 mutations,直接在 actions 内部同步或异步地修改 state。同时没有模块化概念,而是通过定义多个 store 来分割状态。
  3. 易用性和复杂性

    • Vuex 功能强大,适合大型和复杂的项目,但由于其严格的流程和较多的概念,对于小型项目或新手开发者可能会显得较为复杂。
    • Pinia 设计上更为简洁和直观,易于快速上手,特别是在 Vue 3 组合式 API 中,Pinia 的 API 更加契合现代 Vue 开发体验,其体积也相对较小。
  4. TypeScript 支持

    • Vuex 在 Vue 2 中支持 TypeScript 需要配合额外的插件,而在 Vue 3 中已经改进了对 TypeScript 的支持。
    • Pinia 从设计之初就完全拥抱 TypeScript,内置了对类型系统的优秀支持,无需额外配置即可获得很好的类型提示和检查。
  5. 与 Vue.js 版本的关联

    • Vuex 是早期 Vue.js(尤其是 Vue 2)项目的标准状态管理解决方案,而随着 Vue 3 的推出,Vue 团队推出了 Pinia 作为 Vuex 的进化版,专为 Vue 3 打造,具有更好的性能和集成性。

综上所述,Pinia 可以视为 Vuex 的现代化替代品,尤其是在 Vue 3 中,Pinia 凭借其简洁的 API 和对 Vue 3
新特性的良好兼容性,成为官方推荐的状态管理库。但在实际项目中,具体选择哪个库还需要根据项目的规模、团队的技术栈和长期维护计划来考虑。

13-1.vuex核心概念

State: State 是 Vuex 中的单一状态树,它是整个应用程序的唯一数据源。所有状态都保存在这个对象中,可以通过
this.$store.state 在组件中访问。
Getter
Getter 类似于 Vue 组件中的计算属性(computed)。用于从 state 中派生出一些状态, 比如对列表进行过滤、统计等。 可以在 store 中定义
getter 来处理复杂的状态逻辑,类似于 Vue 组件内的计算属性。 Mutation
Mutation 是更改 Vuex store 中 state 的唯一方法。每个 mutation 都有一个字符串类型的事件类型 (type) 和 一个 回调函数
(handler),这个回调函数就是我们实际进行状态更改的地方。 Mutations 必须是同步事务。
Action
Actions 类似于 mutations,不同之处在于: Action 提交的是 mutation,而不是直接变更状态。
Action可以包含任意异步操作。 Actions 通过 context.commit 方法来提交 mutation。
Module: 当应用变得非常大时,将所有的状态逻辑放在一个地方会使 store 文件变得臃肿难以维护。Vuex 允许我们将 store分割成模块(modules),每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块。

13-2.Vuex 底层实现的简化概述:

  1. 创建 Store 当你创建一个新的 Vuex store 时,Vuex 会将你提供的 state 对象转换为一个响应式的对象。这主要是通过 Vue 的 Vue.observable() 方法(在 Vue 2 中是通过
    Object.defineProperty 或 ES6 的 Proxy)来完成的,使得 state 中的数据变化能够被侦测到。

  2. 响应式系统 当 state 发生变化时,所有依赖于该 state 的组件都会自动更新。这是因为 Vuex 使用了 Vue 的响应式机制,任何对 state 的访问或修改都受到 Vue 的监控。

  3. 单一状态树 Vuex 强调使用单一的状态树,即整个应用程序只有一个 store 实例,所有的状态都被存储在这个单一的对象中。这种方式使得状态更加易于追踪和调试。

  4. Getter 计算属性 Getter 类似于 Vue 组件中的计算属性,它们的结果会被缓存起来,只有在其依赖的状态发生变化时才会重新计算。这有助于提高性能,尤其是在处理复杂的状态逻辑时。

  5. Mutation 提交更

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值