前端知识宝典

文章目录


一、针对简历提到的知识点

1.Prettier 统一格式化代码,
2. ESLint(检测代码)、
3.Stylelint (格式化样式),
4.commitint等代码校验规范提升代码质量。
提交代码(提交前会自动执行 lint:lint-staged 命令)
pnpm commit
江门防呆喷码系统:
该项目难点:要求一个月完成项目上线,并且没有产品原型,需出差去现场对接。需要同时开发PC端和APP。
亮点:项目封装了强大的 ProTable 组件,在一定程度上提高开发效率。另外本项目还封装了一些常用组件、Hooks、指令、动态路由、按钮级别权限控制等功能。

项目功能 🔨
*使用 Vue3.4 + TypeScript 开发,单文件组件<script setup>
*采用 Vite5 作为项目开发、打包工具(配置 gzip/brotli 打包、tsx 语法、跨域代理…)
*使用 Pinia 替代 Vuex,轻量、简单、易用,集成 Pinia 持久化插件
*使用 TypeScript 对 Axios 整个二次封装(请求拦截、取消、常用请求封装…)
*基于 Element 二次封装 ProTable 组件,表格页面全部为配置项 Columns
*支持 Element 组件大小切换、多主题布局、暗黑模式、i18n 国际化
*使用 VueRouter 配置动态路由权限拦截、路由懒加载,支持页面按钮权限控制
*使用 KeepAlive 对页面进行缓存,支持多级嵌套路由缓存
*常用自定义指令开发(权限、复制、水印、拖拽、节流、防抖、长按…)
*使用 Prettier 统一格式化代码,集成 ESLint、Stylelint 代码校验规范
*使用 husky、lint-staged、commitlint、czg、cz-git 规范提交信息

一、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.computed 用于创建计算属性,可以基于响应式数据进行计算。
4.watch 用于监听响应式数据的变化,并执行回调函数。
5.toRefs 用于将响应式对象转换为包含 ref 的普通对象,方便在解构时使用。
toRefs 函数用于将响应式对象转换为包含 ref 的普通对象。这在解构响应式对象时非常有用,因为解构会使响应式对象的属性失去响应式特性。使用 toRefs 可以保留解构后的对象属性的响应性。

6.watchEffect 用于创建一个响应式数据的副作用,类似于 watch,但不需要指定要监听的具体属性。
7.onMounted、onUnmounted、onUpdated 等,用于处理组件的生命周期。
8.provide 和 inject
9.directive自定义指令(后台管理系统按钮控制)
10.mixin混入
11.createApp()创建一个应用实例,类似new vue()
12.nextTick()

4.TypeScript支持:

Vue 3 对于 TypeScript 提供了一致的完整类型定义,这对于大型项目尤其有用,因为它可以帮助确保代码的一致性和错误检测。
vue2从版本2.4.0开始,它也增加了对 TypeScript 的支持。
5.新特性的支持: 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 生命周期钩子运行的,所以不需要显式地去定义。

在这里插入图片描述

2.爷传孙的几种方式

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

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

全局性: 事件总线是一个全局实例,可以在整个应用中使用,但这也可能导致不同组件之间的耦合性增加。
2. 通过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 更适合处理副作用。

5.插槽有哪些?

在Vue.js中,插槽(Slot)是一种用于在父组件中向子组件传递内容的机制。插槽允许你定义一个占位符,然后在父组件中填充具体的内容。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提高可维护性:
插槽可以提高代码的可维护性。通过将组件的结构和样式分离,你可以更容易地理解和维护代码,因为父组件和子组件的关注点得到了清晰的分离。

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

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

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

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

解析(Parsing): Babel 使用解析器来将输入的 ES6+ 代码解析成抽象语法树(Abstract Syntax
Tree,AST)。AST 是一种树状结构,它以 JavaScript 代码的形式表示代码的抽象结构,方便后续的处理。

转换(Transformation): 一旦得到 AST,Babel 将应用一系列的转换插件,这些插件会遍历并修改 AST,将 ES6+
语法转换为更低版本的 JavaScript
语法。每个插件通常专注于一个特定的语法转换。例如,有插件用于将箭头函数转换为普通函数、将模板字符串转换为普通字符串等。

Babel
的转换过程并非一成不变的,它是高度可配置的,可以根据用户的需求启用或禁用不同的插件。这使得开发者可以根据项目的特定要求进行定制化的转换。

最终,Babel 将修改后的 AST 转回为 JavaScript
代码。这个生成的代码就是经过转换后的、适用于低版本浏览器的代码。这整个过程使得开发者能够使用最新的 ECMAScript
特性,同时确保其代码能够在不同浏览器中运行,提高了代码的可移植性和兼容性。

8.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)。 调整压缩配置,去除不必要的混淆操作。
确保代码结构简洁,避免不必要的冗余代码。 在开发环境中尽量减少对代码的压缩,以提高构建速度。

那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
是一个被广泛使用的压缩工具,特别适用于构建过程中的代码压缩和优化。

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

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

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

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

按需编译: Vite 仅仅编译你当前需要的文件,而不是整个项目。这样可以避免了不必要的编译,提高了开发环境下的构建速度。

原生 ES 模块支持: Vite 利用了浏览器原生支持的 ES
模块,可以直接运行未编译的代码,而不需要在开发环境下进行打包。这对于现代浏览器的兼容性更好。

无需预构建: Vite
不需要提前构建整个应用,因为它利用浏览器原生支持的模块导入。这使得开发者在开发环境下能够更快地看到变化,不需要等待构建完成。

插件机制: Vite 的插件系统允许开发者更灵活地定制构建过程,以满足项目的具体需求。

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
新特性的良好兼容性,成为官方推荐的状态管理库。但在实际项目中,具体选择哪个库还需要根据项目的规模、团队的技术栈和长期维护计划来考虑。

14.webpack编译流程

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
强大的插件系统使得开发者能够灵活地扩展和优化构建过程。

15.keep-alive的使用场景跟原理分析

16.按钮权限你是怎么实现的?如何封装自定义指令与注册使用?

1.函数方式。本质上就是通过v-if,只不过是通过一个统一的权限判断方法hasPermission: < a-button v-if=“hasPermission([‘20000’, ‘2000010’])” color=“error” class=“mx-4”>
拥有[20000,2000010]code可见 < /a-button>
2.组件方式 除了通过函数方式使用,也可以使用组件方式,Vue vben admin提供了一个Authority组件,使用示例如下: 使用Authority包裹需要权限控制的按钮即可,该按钮需要的权限码通过value属性传入,接下来看看Authority组件的实现。同样还是使用hasPermission方法,如果当前用户存在按钮需要的权限码时就原封不动渲染Authority包裹的内容,否则就啥也不渲染。
3.指令方式。使用示例如下: < a-button v-auth=“‘1000’” type=“primary” class=“mx-4”> 拥有code [‘1000’]权限可见 < /a-button>

17.自定义指令 与v-show,keepalive底层原理。

自定义指令如何封装与使用
在 Vue.js 中,自定义指令允许你封装并复用 DOM 操作行为。下面是如何在 Vue 中定义和使用自定义指令的步骤:
在 Vue 中,通过全局或局部方式注册自定义指令:

全局注册:
// 在你的 main.js 或类似的入口文件中
import Vue from 'vue';

Vue.directive('my-directive', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el, binding, vnode) {
    // ……这里可以对 el(DOM 元素)进行操作
    console.log('指令已插入,元素是:', el);
    // binding.value 是传递给指令的值
    if (binding.value) {
      el.style.color = binding.value;
    }
  },
  // 当被绑定的元素所在模板更新时……
  update: function (el, binding) {
    // ……这里可以对 el(DOM 元素)进行操作
  },
  // 当被绑定的元素所在组件的 VNode 更新时……
  componentUpdated: function (el, binding) {
    // ……这里可以对 el(DOM 元素)进行操作
  },
  // 当被绑定的元素卸载时……
  unbind: function (el) {
    // ……这里可以对 el(DOM 元素)进行操作
  }
});
局部注册:
export default {
  directives: {
    'my-directive': {
      // 相同的钩子函数定义
      inserted(el, binding, vnode) {
        // ...
      },
      // ...
    }
  }
}

使用自定义指令

在模板中使用自定义指令,格式为 v-my-directive="expression",其中 expression
是传递给指令的值:

html <div v-my-directive="'red'">指令生效后的颜色将会变为红色</div>

在这个例子中,当 v-my-directive 指令被插入或更新时,它会把该元素的颜色设为传递的表达式值(这里是 'red')。

钩子函数说明

  • inserted:当元素被插入到 DOM 中时触发。
  • update:当包含指令的组件视图更新时触发,此时指令的值可能发生了变化。
  • componentUpdated:当包含指令的组件完成更新后触发,此时 DOM 已经更新完毕。
  • unbind:当指令与元素解绑时触发,即组件销毁时。

根据实际需求,你可以在相应的钩子函数中编写相应的行为逻辑。

18.如何实现多语言,当其他人修改权限不动配置文件需要怎么做?

实现多语言功能时,为了避免每次添加或修改权限时频繁改动配置文件,可以通过设计合理的架构和数据库支持来实现动态管理多语言内容和权限。以下是一种可能的方法:

  1. 数据库驱动的多语言支持:

    • 创建一个多语言表结构,包含字段如key(用于标识文本键)、language_code(用于表示语言代码,如’en’、‘zh-CN’)、value(对应语言的文本值)。
    • 当需要添加或修改权限相关的多语言文案时,管理员可以直接通过后台管理系统操作数据库,而不是修改配置文件。
  2. 动态权限管理:

    • 使用角色权限模型,将权限规则存储在数据库中,而不是硬编码在配置文件中。
    • 创建角色表、权限表以及角色权限关联表,通过数据库记录和查询来判断用户的角色和对应的权限集合。
    • 开发后台管理系统,允许管理员在无需接触配置文件的情况下,增删改查权限规则和分配给不同角色。
  3. 应用层面实现:

    • 在应用程序中,当需要获取权限相关的多语言文本时,根据当前用户的语言设置查询数据库获取对应语言的内容。
    • 在进行权限验证时,调用权限服务或中间件从数据库加载用户的权限列表进行实时校验。

举例来说:

sql
– 1多语言表 CREATE TABLE translations (
id INT AUTO_INCREMENT PRIMARY KEY,
key VARCHAR(255) NOT NULL,
language_code VARCHAR(10) NOT NULL,
value TEXT );

– 2权限表(代码省略)

– 3角色表(代码省略)

– 4角色权限关联表(代码省略)

这样设计后,无论是多语言文本还是权限规则,都是动态管理的,无需直接修改配置文件,从而实现了维护上的便捷性和灵活性。同时,这样的设计还可以更好地支持团队协作和版本控制。

19.前端如何自动化部署?

前端自动化部署的核心思想是将手动部署的过程自动化,从而提高工作效率、减少人为错误并确保部署流程的稳定性与一致性。以下是一些关键理念:

  1. 持续集成/持续部署(CI/CD): 核心在于开发过程中频繁地将代码集成到主干分支,并通过自动化流程部署到测试和生产环境。每当开发者提交代码,自动化工具就会触发构建、测试和部署流程。

  2. 构建自动化: 使用构建工具(如Webpack、Vite、Gulp、Grunt等)对前端代码进行编译、压缩、打包,生成适合生产的静态资源文件。

  3. 版本控制: 通过版本控制系统(如Git)管理代码,触发自动化部署是在特定分支发生更改时。

  4. 自动化测试: 在部署之前运行自动化测试套件(如单元测试、集成测试、E2E测试),确保代码质量。

  5. 部署工具: 使用CI/CD工具(如Jenkins、GitHub Actions、GitLab CI/CD、Travis CI、CircleCI等)自动执行部署任务。这些工具可以根据预设的配置文件进行构建、推送文件到服务器、重启服务等操作。

  6. 基础设施即代码(IaC): 使用Terraform、Ansible、CloudFormation等工具定义和管理基础设施,确保部署环境的创建和更新也是自动化的一部分。

  7. 容器化部署: 将前端应用及其运行时环境封装到Docker容器中,通过Docker Compose、Kubernetes等容器编排工具进行部署,保证环境一致性。

  8. 滚动更新与回滚: 支持自动化的滚动更新,新版本逐步替换旧版本,同时具备快速回滚机制以应对部署失败或线上问题。

  9. 日志监控与通知: 自动部署过程中收集日志,部署成功或失败后发送通知给相关人员,便于及时了解部署状态。

通过这些方法,前端自动化部署不仅能简化部署流程,还能促进DevOps文化,加速产品迭代速度,提高产品质量和可靠性。

20.vite相较于webpack做了哪些提升?

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

    • Vite 基于 ES 模块,在开发环境下不预先打包全部代码,而是利用浏览器对 ES 模块的原生支持,通过HTTP服务器按需提供源代码,这极大地提高了开发服务器的启动速度和首次页面加载速度。
    • Webpack 在开发阶段需要先进行完整的构建才能启动服务器,而 Vite 则跳过了这一阶段,仅在首次请求时才编译相应的模块。
  2. 热更新(HMR)效率

    • Vite 实现了高效的模块热更新,当开发者修改代码后,它只重新编译改动的部分,并通过HMR机制通知浏览器加载变更的模块,而非整体刷新页面或重新编译整个应用。
    • 相比之下,Webpack 的 HMR 也会尽量做到局部更新,但在大型项目中,尤其是在处理大量依赖关系时,其热更新速度可能会慢于 Vite。
  3. 预构建依赖

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

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

    • Vite 的配置文件相比于 Webpack 更加简洁,尤其是对于小型到中型项目,开箱即用的体验更好。
  6. 打包效率

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

总结起来,Vite
的设计思路更加侧重于利用现代浏览器的能力,优化开发迭代周期,同时保持较高的构建性能,尤其在开发阶段为开发者带来更好的体验。而 Webpack
由于长期积累形成了庞大的生态和高度可定制化的配置选项,在生产环境下的兼容性和灵活性上仍有不可替代的优势。

二、 js知识点

1.闭包

闭包是指函数和其相关的引用环境的组合。当一个函数定义在另一个函数内部时,内部函数就形成了闭包。闭包允许内部函数访问外部函数的变量和参数,即使外部函数已经执行完毕。这是因为内部函数保持了对外部函数作用域的引用。

function a() {
  let outerVariable = 'I am from outer!';
  function b() {
    console.log(outerVariable);
  }
  return innerFunction;
}

const closure = a();
closure(); // 输出:'I am from outer!'

闭包作用优点:
1.函数a执行完并返回后,闭包使得Javascript的垃圾回收机制不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。即使函数外部的执行上下文已经结束,闭包仍然可以访问和修改函数内部的变量。这使得函数可以在后续调用中保留某些状态。从而 延长了生命周期(闭包延长了外部函数中被内部函数引用的变量的生命周期(上面的outerVariable )。这是因为内部函数持有对外部函数作用域的引用,而外部函数的变量无法被销毁,直到内部函数不再被引用。)
2.闭包可以用于实现私有变量和方法,通过将变量和方法封装在闭包内部,外部无法直接访问,从而提高代码的安全性和封装性
实现数据封装:
3.闭包可以实现数据封装,通过隐藏内部实现细节,只暴露需要暴露的接口。这有助于创建模块化的代码,防止全局作用域的污染。

function createPerson(name) {
  let age = 0;

  return {
    getName: function() {
      return name;
    },
    getAge: function() {
      return age;
    },
    increaseAge: function() {
      age++;
    }
  };
}

const person = createPerson('John');
console.log(person.getName()); // 输出:'John'
console.log(person.getAge()); // 输出:0
person.increaseAge();
console.log(person.getAge()); // 输出:1

闭包缺点:内存泄漏
正如上面所写的,闭包会导致js的垃圾回收机制无法回收对应的内存
这虽然方便了获取外层作用域的变量,但同样也会造成内存泄漏
要想知道为什么会造成内存泄漏,就要知道js中的垃圾回收机制是怎么工作的

闭包和垃圾回收与堆内存之间有一定的关系,因为闭包的产生涉及到对变量的引用,而这些引用可能影响垃圾回收机制对堆内存的操作。

  1. 闭包对堆内存的影响: 变量引用: 当一个函数形成闭包时,它可以访问其外部函数的变量。这些变量的引用可能存在于闭包内,因此,这些变量所占用的内存会被保存在堆内存中。

垃圾回收: 如果这些变量在后续的代码执行中不再被引用,垃圾回收机制会识别并释放这些不再使用的变量所占用的堆内存。

  1. 闭包的生命周期与垃圾回收: 生命周期: 闭包的生命周期取决于函数的执行情况以及对闭包内部变量的引用情况。只要闭包存在引用,它就会保持活动状态。

垃圾回收:
当闭包不再被引用时,垃圾回收机制可以检测到这种情况,并释放闭包所占用的堆内存。这确保了不再需要的内存能够被及时回收,防止内存泄漏。

  1. 注意事项: 循环引用: 在闭包中存在循环引用时,可能导致垃圾回收机制无法正确释放内存,造成内存泄漏。例如,一个对象引用了一个闭包,而闭包又引用了该对象,这时需要小心处理。

正确使用: 良好的代码实践和对闭包的正确使用可以帮助避免潜在的内存泄漏问题。及时释放不再需要的引用是保证垃圾回收有效运作的关键。

总体而言,闭包本身并不会导致垃圾回收无法回收堆内存,而是如何使用闭包以及如何处理引用关系的问题。正确使用和管理闭包是防止内存泄漏的重要一步。

问1:闭包一定会导致内存泄漏吗?不
闭包(Closure)在某些情况下不会导致内存泄漏,特别是在以下情况:

1.如果在闭包中引用的变量不再需要,开发者可以通过设置该变量为 null 或者将闭包变量所在的函数引用解除,以及时释放对这些对象的引用,防止内存泄漏。
function createClosure() {
  const data = getData(); // 获取数据
  return function() {
    // 使用 data
    console.log(data);
    // 不再需要 data,释放引用
    // data = null;
  };
}
const closure = createClosure();
closure(); // 使用闭包
// 在不再需要闭包时,确保及时释放引用
// closure = null;j
2.使用事件处理函数:
当闭包作为事件处理函数时,通常会在事件处理函数执行完成后,
不再需要对闭包的引用,可以通过解绑事件处理函数来释放对闭包的引用。
function attachEventHandler() {
  const element = document.getElementById('myElement');
  function handleEvent() {
    // 使用 element
    console.log(element);
    // 不再需要 element,解绑事件处理函数
    element.removeEventListener('click', handleEvent);
  }
  element.addEventListener('click', handleEvent);
}
3.如果闭包的生命周期相对较短,不会长时间存在,也不会持有大量的资源,那么即使闭包存在,其对内存的影响可能较小。
例如这个变量是:const localVar = "I'm a local variable";

问2:闭包一定有return吗?no
闭包可以实现变量私有化,当外部如果想访问闭包内的变量的时候,就将闭包函数return出来,这样外面就可以使用内部变量并且只能使用不能修改。

在这里插入图片描述
闭包场景:节流和防抖,vue中的hooks
JS哪些操作会导致内存泄露
内存泄漏就是本应该释放的内存但是没有得到释放

1.闭包
2.意外的全局变量(意外指的是忘记声明直接赋值使用,或者声明时候变量写错了),建议用indexedDB来存储数据(不问不答:IndexedDB 通常用于需要在客户端存储大量结构化数据的 Web 应用程序,比如离线支持、缓存数据、本地数据库等场景)。
3.没有清理的DOM引用:
var myElement = document.getElementById(‘myElement’);
4.被遗忘的定时器或回调setTimeout
5.控制台日期未关闭,console.log打印到控制台的对象
6.循环引用,对象A引用了对象B,对象B引用了对象A

闭包应用场景:防抖与节流:
https://www.bilibili.com/video/BV1dv4y117mY/?p=7&spm_id_from=pageDriver
防抖:单位时间内,频繁触发事件,只执行最后一次(重新开始)
应用场景: 搜索框输入,文本剪辑器实时保存
在这里插入图片描述
代码实现思路如下:(利用定时器,每次触发先清掉以前的定时器,从新开始)在这里插入图片描述
节流:是指连续触发事件在设定的一段时间内只执行一次函数。
例如:设定1000毫秒执行,那你在1000毫秒触发再多次,也只在1000毫秒后执行一次。(不要打断我)
应用场景: 验证码,高频事件(快速点击,鼠标滑动,懒加载,resize事件,scroll事件)
在这里插入图片描述
在这里插入图片描述
代码实现思路: 利用定时器,等定时器执行完毕,才开启定时器(不要打断)在这里插入图片描述

2.垃圾回收机制

具体到JavaScript中,JavaScript会在我们定义数据时分配内存
对于原始数据(基础数据),JavaScript会直接在栈内存中分配内存
对于复杂数据,JavaScript会在堆内存中开辟一块空间用于存放并返回一个指针给变量
当内存使用完毕后就需要对其进行释放以便腾出更多的内存,这个步骤就叫做垃圾回收(Garbage Collection,简称GC)
具体而言,垃圾回收机制通过算法来实现
1引用计数
每当有一个对象能指向自己时,自己的引用数便+1
当引用数为0时就销毁这个对象
然而当产生循环引用时这个算法就无法处理
循环引用即对象a引用对象b,对象b引用对象a
2标记清除
标记清除的核心就是可达性,如果无法达到就清除
算法会设置一个根对象,垃圾回收器会定期从根对象开始寻找所有引用到的对象
对于不可达对象,就会对其回收
这个算法可以很好解决循环引用问题
在这里插入图片描述在这里插入图片描述

3.v8垃圾回收机制

V8 是 Google Chrome 浏览器中使用的 JavaScript 引擎,也被用于 Node.js 等环境。V8 引擎实现了先进的垃圾回收机制,其中包括以下几个组件:

1.分代垃圾回收(Generational Garbage Collection):

V8 将堆内存分为两个代:新生代和老生代。新生代用于存储短命的对象,老生代用于存储长寿命的对象。V8使用不同的垃圾回收策略来处理不同代的对象。

2·新生代垃圾回收: 使用 Scavenge 算法,将新对象存放在一个称为 “From” 的空间中,当 “From” 空间满时,将存活的对象复制到 “To” 空间,然后交换 “From” 和 “To”,同时清空 “From” 空间。这个过程中可以有效地识别并回收短命的对象。

3·老生代垃圾回收: 使用 Mark-Sweep 和 Mark-Compact 两个阶段的算法。Mark-Sweep
阶段标记并清除不再使用的对象,Mark-Compact 阶段整理内存,减少碎片。

4·增量标记(Incremental Marking):

为了避免长时间的垃圾回收造成的停顿,V8 引入了增量标记。增量标记将整个标记阶段切分为多个小步骤,每执行一小步骤后就让 JavaScript
应用程序执行一会儿,从而减小了垃圾回收对应用的影响。

5·空闲时垃圾回收(Idle-time Garbage Collection):

在 JavaScript 空闲时,V8 会执行垃圾回收,以减少对应用程序的影响。这样可以在不影响用户体验的情况下进行更频繁的垃圾回收。

6·快速对象分配(Fast Object Allocation):

V8 引擎使用了快速对象分配策略,通过将相同类型的对象放在一起,提高了对象的分配和回收效率。

这些特性使得 V8 引擎的垃圾回收机制更加高效和灵活,能够在不同场景下进行优化,

垃圾回收机制主要关注堆内存的管理。由于栈内存是有限的,且在函数调用结束时会自动释放,所以不需要专门的垃圾回收机制来管理栈内存。堆内存中的对象,特别是那些不再被引用的对象,需要垃圾回收机制来检测并释放。
栈内存(Stack Memory):
用于存储基本数据类型和引用类型的地址。在函数调用时,局部变量和函数参数会被存储在栈内存中,函数执行完毕后,栈内存会自动释放。栈内存是一种有限且相对较小的内存区域。
堆内存(Heap Memory):
用于存储引用类型的对象和复杂数据结构。堆内存的大小相对较大,且动态分配和释放。在堆内存中,对象可以被长时间保留,直到没有任何引用指向它,才会被垃圾回收机制回收。

引申1:js数据类型有哪些?怎么判断

在JavaScript中,数据类型主要包括以下几类:

  1. 基本数据类型(也称为“原始类型”或“简单类型”):

    • Undefined: 当声明但未赋值的变量或者访问不存在的对象属性时,返回此类型。
    • Null: 表示空值或无任何对象值,只有一个特殊的值 null。存在栈内存中。
    • Boolean: 布尔类型,只有两种可能的值 truefalse
    • Number: 数字类型,包括整数、浮点数以及特殊的 NaN(非数字)。
    • String: 字符串类型,由单引号或双引号包裹的字符序列。
    • BigInt: 从ES2019开始引入的大整数类型,用于表示超过Number能表示的最大安全整数范围的整数,通常以 n 后缀标识。
    • Symbol: 从ES2015开始引入的唯一标识符类型,用于创建唯一的不可变数据类型。
  2. 引用数据类型(或“复杂类型”):

    • Object: 包括普通对象、数组、函数以及其他内置对象,如 DateRegExpErrorMapSetWeakMapWeakSet 等。
      • Array: 数组类型,有序列表。
      • Function: 函数也是对象,可以作为第一类值被赋值和传递。

判断数据类型的方法:

  • typeof 操作符javascript let value; console.log(typeof value); // "undefined" console.log(typeof 42); // "number" console.log(typeof "Hello"); // "string" console.log(typeof true); // "boolean" console.log(typeof null); // "object" (注意这里是特殊情况,null实际是null类型,但在typeof中表现为"object") console.log(typeof {}); // "object" console.log(typeof []); // "object" (数组在这里也会显示为"object") console.log(typeof Symbol()); // "symbol"

  • instanceof 运算符
    javascript let arr = [];
    console.log(arr instanceof Array); // true
    let date = new Date();
    console.log(date instanceof Date); // true

  • Object.prototype.toString.call()
    javascript console.log(Object.prototype.toString.call([])); // “[object Array]”
    console.log(Object.prototype.toString.call(new Date())); // “[object
    Date]” ```这个方法通常被认为是最准确的方式,因为它可以区分出数组、函数和其他对象的具体类型。

  • value.constructor.name: ```javascript let arr = []; console.log(arr.constructor.name); // “Array” let myFunc =
    function() {}; console.log(myFunc.constructor.name); // “Function”
    注意这种方法在对象被重写构造函数的情况下可能不准确。

另外,在较新版本的JavaScript中,你可以使用 Array.isArray() 来检查一个值是否为数组,以及
Number.isNaN() 来更准确地检查一个值是否为NaN。对于一些特定情况,还有
Object.keys()Object.entries()Reflect.getPrototypeOf()
等方法可以帮助确定对象的类型和结构。

4.堆和栈,队列

栈先进后出,队列先进先出,堆:顺序随意。
栈它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。(可以理解为在杯子里装乒乓球,后面进去的得先拿出来才能往下继续拿,只能在顶部也就是尾部进行操作)
队列是一种特殊的线性表,特殊之处在于它只允许在表的前(front)进行删除操作,而在表的后端(rear)进行插入操作(队列理解为排队买票,先买完票先出门,后来的不能插队只能排后面进行买票)
堆是非线性的,可以理解为树状结构
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

5.js的数据结构有哪些

1.数组(Array)
2.对象(Object)
3.集合(Set):let mySet = new Set([1, 2, 3, 1, 2]);
4.映射(Map):
let myMap = new Map();
myMap.set(‘key1’, ‘value1’);
5.栈(Stack)(后进先出),只允许在栈顶进行插入和删除操作。 let myStack = [];
myStack.push(1);
myStack.push(2);后进,尾部增加
myStack.pop(); 先出,尾部删除
6.队列(Queue)(队列先进先出):只允许在队尾插入元素,在队头删除元素。 ```myQueue.push(2); myQueue.shift(); `删除首部``
7.链表(Linked List) 链表是一种线性数据结构,由节点组成,每个节点包含一个数据元素和指向下一个节点的引用。
8.树(Tree): 树是一种层级结构的数据结构,包含根节点和若干子节点。树常被用于模拟现实中的层次关系。

6.数组常用的方法:

1.push(element1, …, elementN): 向数组末尾添加一个或多个元素,并返回新的长度。
2.pop(): 移除并返回数组的最后一个元素。
3.shift(): 移除并返回数组的第一个元素。
4.unshift(element1, …, elementN): 在数组的开头添加一个或多个元素,并返回新的长度。
5.concat(array1, …, arrayN): 将两个或多个数组合并成一个新数组,不会修改原始数组。
6.splice(start, deleteCount, item1, …, itemN): 从数组中添加或删除元素,可以用于修改原数组。、
7.slice(start, end): 返回数组的一部分,不会修改原数组。
8.indexOf(element, fromIndex): 返回元素在数组中第一次出现的索引,如果没有找到则返回 -1。
let fruits = [‘apple’, ‘banana’, ‘orange’]; let index =
fruits.indexOf(‘banana’); // index 的值为 1
9.forEach(callback): 对数组的每个元素执行一次提供的函数。
let fruits = [‘apple’, ‘banana’, ‘orange’]; fruits.forEach(fruit => { console.log(fruit);
}); // 输出:‘apple’, ‘banana’, ‘orange’
10.reverse 倒序,sort()正序
11.reduce方法。通过迭代数组所有的元素〈从左到右〉来累计结果并最终得到一个单一的输出值,通常用于数组累计求和∑

引伸1:如何将数组中的第二个元素和第四个元素调换位置

1.// 通过解构赋值交换第二个和第四个元素的位置
let myArray = [1, 2, 3, 4, 5];
[myArray[1], myArray[3]] = [myArray[3], myArray[1]];
console.log(myArray);
// 输出:[1, 4, 3, 2, 5]
方法2:// 使用 splice 方法交换位置
let myArray = [1, 2, 3, 4, 5];
myArray.splice(1, 1, myArray.splice(3, 1, myArray[1])[0]);
console.log(myArray);
// 输出:[1, 4, 3, 2, 5]

引伸2:for,foreach,map区别

foreach对于原数组是基础数据,原数组不会改变,但是对于引用类型是可以修改原数组的。
// 基本类型数组,forEach 修改无效
let basicArray = [1, 2, 3];
basicArray.forEach((value, index, array) => {
value = 10; // 这里不会改变原数组
});
console.log(basicArray); // 输出:[1, 2, 3]

// 引用类型数组,forEach 修改有效
let objArray = [{value: 1}, {value: 2}, {value: 3}];
objArray.forEach((item, index, array) => {
item.value = 10; // 这里会改变原数组中对象的属性
});
console.log(objArray); // 输出:[{value: 10}, {value: 10}, {value: 10}]

map不会修改原数组但会创建并返回一个新的数组
得看情况:如果是基础数据类型就不改变原数组,如果是引用数据类型,原数组会改变。如果不想改变的话需要对数据进行拷贝处理。
总之foreach就是就是遍历所有的元素,并执行某种动作。而map用来数组的变换并产生新数组,所以map比foreach更快,速度快70%
for 循环是最基本的循环结构,提供了最大的灵活性;forEach 是数组提供的方法,适用于对数组的每个元素执行相同的操作;map 也是数组提供的方法,适用于生成一个新的数组,其中包含对原数组每个元素执行操作后得到的结果。选择哪个取决于具体的需求和代码的简洁性。

array.forEach(function(element) {
  console.log(element);
});
let newArray = array.map(function(element) {
  return element * 2;
});

引伸3:数组去重的几种实现方法:

在这里插入图片描述

7.原型和原型链

原型链是 JavaScript 中的一种对象继承机制
在这里插入图片描述原型: 每个函数都有一个prototype属性,称之为原型。
因为这个属性的值是个对象,也称为原型对象。
原型的作用: 存放一些属性和方法,在js中实现继承
原型链: 对象都有_proto_属性,这个属性指向它的原型对象,原型对象也是对象,也有_proto_属性,指向原型对象的原型对象,这样一层一层形成的链式结构称为原型链,最顶层找不到则返回null.
原型链作用. 对象继承:
原型链在对象继承中发挥着关键作用。每个对象都有一个原型对象,而原型对象也可以有自己的原型,形成一个继承链。当访问对象的属性或方法时,如果该对象自身没有找到,JavaScript 引擎就会沿着原型链向上查找,直到找到对应的属性或方法或者到达原型链的顶端
在这里插入图片描述

8.浅拷贝深拷贝

更多查看我另一篇:浅拷贝和深拷贝详解https://blog.csdn.net/qq_37759901/article/details/135680788#comments_30976519

浅拷贝是拷贝了引用对象的地址,其实还是操作一个对象。 深拷贝

  1. 浅拷贝(Shallow Copy): 浅拷贝是复制对象的一层结构,而不会递归地复制嵌套在其中的对象。浅拷贝通常使用一些简单的方法来创建一个新对象,并将原对象的属性复制到新对象中。
    使用方法如 Object.assign(复杂类型数据)、Array.slice ,展开运算符等。 Array.slice
    方法会返回一个新的数组,而旧数组不会改变,所以在Vue2中这个方法不能触发响应式更新
  2. 深拷贝(Deep Copy): 深拷贝是复制对象的所有层结构,包括嵌套的对象。深拷贝会递归地复制所有对象的属性,确保原对象和新对象之间没有引用关系。
    JSON.parse(JSON.stringify(originalObject)); 递归,Lodash 库: Lodash 提供了
    _.cloneDeep 方法,可以实现深拷贝。

在这里插入图片描述

9.ES6新特性

1.let,const

const obj = { prop: 1 };
// 可以修改对象属性的值
obj.prop = 2;//写法正确
obj = { prop: 2 };//写法错误
console.log(obj.prop); // 输出 2
---------------------------------
const num = 1;
// num = 2;// 这是不合法的,会导致错误.对于基本数据类型,const 会更严格地防止变量的重新赋值。

const 用于基本数据类型时,确保变量的值不可重新赋值。 (栈内存)
const 用于引用数据类型时,确保变量的引用地址不可重新赋值,但对象本身的属性是可变的。 (对象值在堆内存,地址在栈中)

10.虚拟DOM,谈谈对vdom的理解。

1.构建虚拟节点树,当应用状态发生变化时,框架首先会根据新的状态重新计算新的组件输出,生成一个代表ui界面的js对象树,这就是虚拟dom。
2.差异比较算法,新的dom树生成后,框架会与上一次生成的虚拟dom树进行比较(diff算法),找出最小的变更集,也就是找出需要更新的节点和他们变化的部分。
3.批量更新,找到差异后并不会立马进行更新,而且将这些变化累计起来批量操作从而减少与浏览器的交互操作,提高性能。
最终最小化将变更集更新到真实Dom上,提高了页面了重绘和回流效率,从而提高了性能。

怎么优化内存的问题。
vdom的优点在于提升性能,但是它也会增加内存的使用,在处理大量和频繁更新数据的时候,如何降低内存?

1.避免过度渲染,使用v-if/show条件渲染来控制,确保不渲染那些不需要立即显示或者永远不会变化的组件。在vue3中的〈suspense〉组件配合异步数据加载,延迟非关联内容的渲染。
2合理的依赖跟踪。 在vue中使用合理的组合式api可以更精确的管理组件内部的状态和响应式依赖,只对实际需要响应变化的数据进行监听。
3静态标记和懒加载
vue3会对静态节点进行优化,不会为这些节点产生额外的观察者,从而节省内存。对于大型列表采用动态组件,懒加载如teleport或者v-lazy或其他分页技术。

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

11.模板编译

是前端框架(如Vue.js、React等)中的一项关键技术,它将用户编写的HTML模版转化为可执行的JavaScript代码。Babel插件的思想就雷同。在Vue.js中,模板编译过程主要包括以下步骤:

1.解析阶段: Vue.js通过模板解析器将模板字符串转换为抽象语法树(AST)。这个阶段会识别并处理指令(如v-if、v-for等)、插值表达式、元素属性和组件标签等内容。
2.优化阶段: AST会被进一步优化,包括静态节点标记(用于提高运行时性能,减少不必要的DOM操作),以及对动态节点进行特殊处理以支持响应式更新。
3.代码生成阶段: 根据优化后的AST生成渲染函数(render function)。在Vue 2.x版本中,这个函数会根据数据模型返回一个VNode(虚拟节点)树结构;而在Vue 3.x版本中,则使用新的编译器输出更高效且与Composition API紧密结合的渲染逻辑。 对于Vue.js来说,具体例子可以这样描述: 当定义一个Vue组件时,其模板部分被编译成一个渲染函数。 这个渲染函数会根据组件的状态(props、data、computed
properties等)计算出当前应该渲染的DOM结构表示——即VNode树。
当状态发生变化时,Vue会重新调用渲染函数,并通过高效的VDOM diff算法找出最小化更新实际DOM所需的差异,从而实现视图的快速更新。
在Vue源码中,模板编译相关的文件可能包括但不限于: 对于Vue
2.x,模板编译的核心模块位于vue-template-compiler包内,涉及多个文件协同工作,如parser/html-parser.js(HTML模板解析)、optimizer.js(优化)和codegen/index.js(代码生成)等。
对于Vue 3.x,模板编译功能主要由@vue/compiler-dom包提供,其中包含了编译器的各种细节实现。

12.js事件循环

前端为什么要用事件循环?
首先js是单线程的,存在阻塞问题,那浏览器是怎么解决这个问题呢?由于js是单线程的,会造成阻塞问题,所以引发出异步任务概念来分担。
比如说网络请求和settimeout()它是通过异步事件来做的,如果都用异步事件来做的话,异步是放在队列里面,它没有优先级,所以为了更灵活,增加了事件循环。
什么是事件循环?
首先js分同步任务和异步任务,它先执行同步再执行异步,异步又分为微任务和宏任务,分别存在微任务和宏任务队列,一般是同步任务执行完毕再执行微任务,再执行宏任务。每次执行宏任务之前会先检查微任务是否执行完毕,如果微任务队列有微任务会先执行完成清空微任务队列之后再去执行下一个宏任务,所以向这样依次执行循环,这就是事件循环。

js执行顺序:同步任务-异步任务(微任务-宏任务)
同步任务就是正常的自上而下执行的比如console打印
异步任务-微任务:promise他本身是同步任务,但是他里面的.then()成功和失败的回调函数是异步任务。 async()本身是同步的,但是遇到await()之后,在函数里面await后面的代码全部变成微任务了(就是异步了)
异步任务-宏任务:定时器(settimeout,setinterval),script标签也算

下面这个resolve标记为成功,不阻止代码继续运行,所以继续向下,直到出现和他最近的then然后进入微任务
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
今日头条题:
在这里插入图片描述

13.this.$nextTick的原理

1.在created直接操作修改dom的值是会报错的,但是加上它之后就不会报错,并且修改了页面的值,它的原理就是等DOM完成渲染之后执行。
2.当数据发生变化后,页面上并没有发生改变,就会用它获取。
它的核心原理是结合了Vue的更新队列和浏览器的异步任务队列(微任务和宏任务)。当数据发生变化时,Vue会将DOM更新操作放入一个异步任务队列中,等待下一次事件循环时执行。nextTick方法则是将一个回调函数推入到异步任务队列中,等待DOM更新完成后执行。12345

14.v-if和v-for优先级

在vue2中for>if,虽然 Vue 2.x 中的优先级规则依然有效,但Vue 3引入了新的语法和优化,使得开发者可以更直观地控制这些指令的执行顺序。现在可以通过 < script setup> 和 v-for 与 v-if 结合使用的API如 v-for-ref 或 v-for-else 等来更好地管理循环和条件渲染。

15.作用域与作用域链

通俗来讲就是变量可使用的范围。一般分为块,函数,全局作用域

1.全局变量就是在外面声明一个变量,在任何地方都能打印。
2.块作用域粗暴理解在{}里。比如if判断语句,循环语句里面。
3.函数作用域就是在函数内部声明的变量。
当多个函数嵌套一层层都声明同一个变量,打印出来的结果是就近原则。先用当前作用域,如果没有就一层层往上找就近的。所谓作用域链就是作用域嵌套下,由内向外,一层一层往上的链式规则,这就是作用域链。

16.点击空白地方隐藏弹框的几种实现方式

1. 使用事件委托与DOM层级关系判断

document.addEventListener('click', function handleClick(event) {
  const dialog = document.getElementById('dialog');
  const backdrop = document.getElementById('backdrop'); // 如果有遮罩层的话

  if (!dialog.contains(event.target) && !backdrop?.contains(event.target)) {
    dialog.style.display = 'none';
    // 如果有遮罩层,同时隐藏遮罩层
    backdrop?.style.display = 'none';
  }
});

在上述代码中,我们在整个文档上添加了一个点击事件监听器。当用户点击任何地方时,检查点击的目标元素是否是弹框(dialog)或其子元素。如果不是,则隐藏弹框及其遮罩层(如果有的话)。

17.http状态码:

HTTP状态码(HyperText Transfer Protocol Status Code)是服务器在响应客户端HTTP请求时返回的一个三位数字代码,用于表示请求是否成功,以及失败的具体原因。以下是HTTP状态码五个类别及其常用的状态码:

  1. 信息响应 (1xx):表示临时响应,请求还在继续处理中。

    • 100 Continue:客户端应当继续发送请求的剩余部分(如果有的话)。
    • 101 Switching Protocols:服务器已理解请求,并同意切换协议。
  2. 成功响应 (2xx)

    • 200 OK:请求已成功,请求的数据被正确返回。
    • 201 Created:请求已经被满足,通常用于创建新资源。
    • 204 No Content:请求已成功处理,但没有实体内容返回。
  3. 重定向 (3xx)

    • 301 Moved Permanently:永久性重定向,请求的资源已被分配新的URI。
    • 302 Found307 Temporary Redirect:临时重定向,请求的资源临时位于新的URI下。
    • 304 Not Modified:资源未修改,客户端仍然可以使用缓存的版本。
  4. 客户端错误 (4xx)

    • 400 Bad Request:客户端提交的请求存在语法错误。
    • 401 Unauthorized:需要验证身份后才能访问资源。
    • 403 Forbidden:服务器理解请求,但是拒绝执行它。
    • 404 Not Found:服务器找不到所请求的资源。
    • 405 Method Not Allowed:服务器不支持请求所使用的HTTP方法。
  5. 服务器错误 (5xx)

    • 500 Internal Server Error:服务器遇到意外情况,未能完成请求。
    • 502 Bad Gateway:作为网关或代理服务器,从上游服务器收到了无效的响应。
    • 503 Service Unavailable:服务器暂时无法处理请求,可能是过载或维护中。
    • 504 Gateway Timeout:作为网关或代理服务器,未能及时从上游服务器收到请求。

以上列举的是常见的HTTP状态码,实际上还存在其他较少见的状态码,分别对应各种特定的情况。随着HTTP协议的发展,还有一些状态码是在后续RFC文档中添加的,比如RFC 7540(HTTP/2)和RFC 7538(Additional HTTP Status Codes)。

18.一个url在浏览器输入到呈现出来发生了什么?

1.输入网址,浏览器交给进程开始处理输入内容 不是网址,则开始跳到默认搜索引擎,执行搜索。 是网址,就执行beforeUnload事件,卸载当前页面。
2.判断是否有缓存; 是:读取并渲染 否:向服务器发送请求。
3.之后把控制权交给网络进程。
4.网络进程开始执行DNS解析,获取IP地址,开始建立连接。
5.请求会发送到对方服务器,然后交给nginx进行处理(有负载均衡,会发送到各地对应的服务器进行处理)
6.建立连接(3次握手), 发生HTTP请求:a.客户端发送SYN到服务器。b服务器收到SYN,生成SCK,给客户端,C客户端收到SYN和ACK,建立连接。
HTTPS:1.客户端发送支持加密协议和版本给服务器,2服务器找到适合的加密协议,3服务器返回证书+公钥。4.客户端返回根证书验证合法性,生成对称加密给服务器。5,服务端使用私钥解密,获取对称加密。6.解密数据,建立SSL连接。
7.连接建立完成后,浏览器开始读取服务器并返回数据。
8.读取HTTP/S响应状态码。
9.所有数据传输完成,断开连接,开始HTTP/S四次挥手。
10.渲染,解析,生成DOM树,解析css,jss.

19.前端中 JS 发起的请求可以暂停吗?

在JavaScript中,标准的XMLHttpRequest (XHR) 或 Fetch API发起的HTTP请求一旦发出,通常情况下是不能被直接暂停或取消的。然而,它们都提供了取消请求的能力,但不是真正意义上的“暂停”然后“恢复”,而是停止进一步处理请求并释放相关资源。

对于Fetch API,可以通过AbortController来实现请求的取消:

let controller = new AbortController();
let signal = controller.signal;

fetch(url, { signal })
  .then(response => {
    // 处理响应...
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    }
  });

// 在需要的时候取消请求
controller.abort();

而对于XMLHttpRequest,你可以调用其abort()方法:

var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);

xhr.onload = function() {/*...*/};
xhr.onerror = function() {/*...*/};

xhr.send();

// 取消请求
xhr.abort();

这两种方式都是立即终止请求,而不是将其挂起等待后续恢复。如果需要类似“暂停”和“继续”的功能,可能需要在应用层面上设计相应的逻辑,比如缓存中间状态,以便在重新发起请求时能够接续之前的操作。

三、 性能优化

1.前端常见的性能优化

前端性能优化是一个综合性的话题,涵盖了多个层面的优化措施,以下是一些常见的前端性能优化方法:

  1. 减少HTTP请求

    • 合并CSS和JavaScript文件:通过将多个CSS或JS文件合并成一个,减少HTTP请求的数量。
    • 图片合并和雪碧图(CSS Sprite):将多个小图标合并到一张大图中,通过CSS定位显示各个图标,减少图片请求。
    • 使用HTTP/2或HTTP/3:1.这些协议支持多路复用,HTTP/2允许在一个TCP连接上同时发送多个请求和响应。可以更有效地处理多个请求,降低请求开销。2.服务器能够预测客户端可能需要的资源,并主动将其推送给客户端,减少了延迟和往返时间,提升了用户体验。
  2. 资源压缩和合并资源

    • 文本资源压缩:启用Gzip或Brotli等压缩算法压缩HTML、CSS和JavaScript文件。
    • 图片压缩与优化:通过合适的图片格式(如WebP)、压缩率、尺寸裁剪等方式减小图片体积。
      -资源文件合并
  3. 缓存策略
    1.HTTP缓存:强缓存,协商缓存
    2.数据缓存:内容缓存,浏览器缓存,本地存储

    • 设置合理的HTTP缓存头信息,让浏览器缓存静态资源,减少重复请求。
  4. 内容分发网络(CDN)

    • 将静态资源部署到CDN上,让用户从最近的CDN节点获取资源,缩短加载时间。CDN网络中的边缘节点(也称为缓存服务器)会缓存原始服务器上的静态内容,比如网页中的图片、视频、样式文件、脚本等。当用户请求这些内容时,CDN会优先将缓存中的内容直接返回给用户,而不是每次请求都回源到源站获取,这样大大减少了网络传输的距离和时间。
  5. 懒加载与预加载

    • 对非首屏内容采用懒加载策略,只有当内容进入可视区域时才加载相应的资源。
    • 对重要资源如CSS、JavaScript和关键图片进行预加载,提前发起请求。
    • 路由懒加载则是指将这些路由相关的组件代码分割成多个较小的、按需加载的代码块,仅在用户实际访问特定路由时才去下载并执行对应的组件代码。这样做可以显著减少初始加载时所需的下载量,加快首屏渲染速度,提高用户体验。
      const routes = [
      {
      path: ‘/about’,
      component: () => import(/* webpackChunkName: “about” */ ‘./views/About.vue’)
      },
      // 其他路由…
      ]
      上述代码中,About.vue组件并不会在应用启动时就被加载,而是在用户导航至/about路由时才异步加载对应的chunk文件。
  6. 代码分割与按需加载

    • 使用webpack、rollup等工具进行代码分割,将大的bundle拆分为多个chunk,按需加载。
    • 使用异步模块加载(AMD)或动态导入(ES6 import())等功能。
  7. CSS与JavaScript优化

    • 将CSS放在文档头部,JavaScript放在文档底部,确保页面内容尽早渲染。
    • 使用内联关键CSS(Critical CSS),其余CSS异步加载。
    • 删除不必要的CSS和JavaScript代码,避免冗余。
  8. DOM优化

    • 减少DOM操作,特别是引起布局重排(layout thrashing)和重绘(repaint)的操作。使用css sprites
    • 使用虚拟DOM技术(如React/Vue/Angular)提高DOM更新效率。
  9. 资源域名分摊

    • 使用多个子域名,突破浏览器对同一域名下的并发连接数限制。
  10. 代码中优化

    • 使用keep-alive组件缓存,对于 那些频繁切换的组件,避免不必要的销毁和创建。
    • 合理使用v-if(完全销毁和重建组件),v-show,(简单的切换与隐藏)
      ssr

以上十点只是众多前端性能优化手段中的一部分,实际优化时需要根据项目具体情况制定有针对性的优化方案。

在这里插入图片描述

2.首次白屏如何优化

Vue.js应用首次加载时出现白屏,通常是因为浏览器在等待JavaScript文件、CSS样式表和数据请求等资源加载完成之前无法渲染页面内容。白屏的本质就是一次渲染太多内容,页面没有及时解析渲染出来,大量的js遍历计算导致性能上的损耗。针对这一问题,可以采用以下几种策略来优化:
1.代码分割按需加载分包与懒加载:
使用vue-router的路由懒加载功能,仅在路由切换时异步加载对应的组件。
对于大型库或非首屏必需的模块使用动态导入(import())
2.预渲染(Prerendering)或服务器端渲染(SSR):

预渲染可以把静态HTML直接输出到客户端,减少JavaScript执行时间带来的空白期。
服务器端渲染能够快速生成初始HTML,并将它发送给客户端,当JavaScript执行时,Vue会接管并绑定交互逻辑。
3.CDN加速与缓存优化:
使用CDN服务托管静态资源,如JS、CSS、图片等,提高加载速度。
设置合理的HTTP缓存策略,利用浏览器缓存减少重复请求。
4.资源压缩与合并:
使用Webpack或Vite构建工具对JS和CSS进行压缩和合并,减小文件大小。
启用Gzip压缩传输。
5.骨架屏(Skeleton Screen):

在数据加载前展示简单的占位图,让用户感知到页面正在加载,提升用户体验。也可以在首页加个蒙版,转圈,当内容出来隐藏掉
6.优化CSS和JavaScript加载方式:**

将关键CSS内联在HTML中以确保优先渲染布局相关的样式。
使用async或defer属性优化脚本加载顺序,使HTML解析不受阻塞。
7.合理设置网络请求:

避免首页加载时一次性发起大量请求,可采用分页加载、延迟加载等方式优化。
对于需要的数据请求,尽可能将其延后到关键路径渲染之后。
8.使用defer优化白屏时间。
写个函数按帧加载,假如页面渲染100个组件一起渲染时间就会很长,其实组件再多,用户能看见的部分只有一小部分,所以先去渲染对用户比较重要的组件,,假设一分钟60帧,页面渲染60次,将这些组件首先渲染作为第一帧,通过v-if来根据帧递增来增加渲染。当n到达总数的时候停止递增
9.SSR
1.SSR包含更好的SEO:搜索引擎爬虫可以直接抓取服务器生成的完整HTML内容,有利于提升网站在搜索引擎中的排名和可见性。
2.搜索引擎爬虫可以直接抓取服务器生成的包含所有可见内容的HTML用户能够更快地看到页面主要内容,提升用户体验,特别是对那些网络环境不佳的用户来说。无需等待JavaScript执行,即使用户的设备不支持或禁用了JavaScript,也能展示部分静态内容。
3.降低客户端压力:部分计算和渲染任务转移到服务器端,尤其在弱网环境或低端设备上,可以减轻客户端的压力,使页面加载更为流畅。

引伸1:Vue如何实现路由懒加载?

Vue.js 中实现路由懒加载的常见方法是利用动态导入(import())语法,结合 Vue Router 的异步组件功能

// 导入 Vue Router
import Vue from 'vue';
import Router from 'vue-router';

Vue.use(Router);

// 定义各个路由组件,但不直接导入它们
const Home = () => import('./views/Home.vue');
const About = () => import(/* webpackChunkName: "about" */ './views/About.vue');
const Contact = () => import('./views/Contact.vue');

// 创建并配置路由实例
export default new Router({
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: About },
    { path: '/contact', component: Contact },
    // 更多路由...
  ]
});

上述代码中的 () => import(…) 是一个返回Promise的函数,当对应的路由被访问时,Vue Router会触发这个函数执行,从而动态地加载对应的组件。

通过这种方式,每个路由对应的组件会被分别打包成不同的chunk文件,在用户首次访问相应路由时按需加载,而不是一次性加载所有组件,这有助于减小应用初始化时的体积,并加快页面加载速度。同时,还可以配合Webpack的webpackChunkName注释来指定打包后的chunk名称,方便查看和管理。

引伸2:HTTP缓存策略

HTTP缓存策略是Web开发中用于提高网页加载速度和减少服务器负载的重要机制。它允许浏览器在本地存储(即缓存)经常请求的资源,当用户再次请求相同资源时,浏览器可以依据缓存策略决定是否直接使用缓存中的资源,或者向服务器发起请求以检查是否有更新。
HTTP缓存主要分为两大类:

1.强制缓存(也称绝对缓存或无条件缓存):

当浏览器发起对某一资源的请求时,首先会检查本地缓存中是否存在该资源,并且判断其是否仍然有效。
强制缓存的有效性通过:
1.Expires响应头来确定资源过期时间(设定一个具体的过期时间),在此时间之前,浏览器认为缓存有效,无需向服务器发送请求。
如果缓存资源未过期,则浏览器直接从本地缓存加载资源,整个过程不涉及与服务器之间的通信,网络请求不会发到服务器。
在开发者工具(如Chrome DevTools)的Network面板中,对于命中强缓存的资源,状态码通常显示为200 (from cache)
2.Cache-Control响应头中的max-age
Cache-Control响应头字段更为强大且优先级高于Expires。它可以设置多种缓存策略,如max-age指定相对时间(秒),在此时间段内缓存被视为新鲜;public表示任何缓存都可以存储响应;private则指示响应只应被单个用户缓存,通常应用于包含用户特定数据的响应。
2.协商缓存(也称对比缓存或有条件缓存):
当强制缓存未命中的时候,浏览器将向服务器发起请求确认缓存资源是否仍然最新。
协商缓存依赖于特定的HTTP头部信息,例如Last-Modified / If-Modified-Since和ETag / If-None-Match机制。
Last-Modified:服务器在首次响应时告诉浏览器资源最后修改的时间戳,后续请求时浏览器会在If-Modified-Since头中携带这个时间,询问服务器资源是否已更改。
ETag:服务器提供一个唯一标识符(实体标签),浏览器在下次请求时通过If-None-Match头将其发送给服务器进行比对。
如果服务器确认资源未改变(根据Last-Modified比较结果或ETag匹配结果),则返回304 Not Modified状态码,并告知浏览器可以继续使用缓存中的资源内容。
若资源已更新,服务器则会返回完整的新的资源内容以及最新的缓存控制头信息。
开发者可以根据实际需求配置这些头部信息,使得HTTP缓存机制能够更高效地服务于应用。此外,还可以结合服务端配置、CDN缓存策略以及客户端的localStorage或其他持久化存储方式来优化整个缓存流程。

强制缓存与协商缓存有什么区别?
强制缓存与协商缓存是HTTP缓存策略中的两种不同处理方式,它们的主要区别在于是否需要向服务器发送请求以验证缓存资源的有效性:总结来说,强制缓存完全基于客户端决定是否使用缓存,而协商缓存则要求客户端与服务器进行交互以确认缓存的有效性。

3.WebSocket和Web Worker的区别:

1.功能和用途:WebSocket用于实现实时通信和双向通信,允许在客户端和服务器之间进行实时数据传输。它适用于需要实时性和即时通信的应用。而Web Worker用于在后台运行脚本,独立于主线程,用于执行一些耗时的任务和计算密集型操作,从而提高应用程序的响应性。

2.线程模型:WebSocket运行在主线程中,它使用单个持久的连接进行通信。而WebWorker可以创建多个后台线程,在这些线程中执行任务,与主线程并行运行。Web Worker通过消息传递与主线程通信。

通信机制:WebSocket使用WebSocket协议进行通信,它建立在TCP上,提供了双向通信的能力。WebWorker通过消息传递与主线程通信,使用postMessage方法发送消息,通过监听onmessage事件接收消息。

应用场景:WebSocket适用于实时聊天、在线游戏、实时协作等需要实时通信的应用场景。Web Worker适用于执行一些耗时的操作,如图像处理、数据计算、文件解析等。

具体搞清WebSocket和Web Worker的区别点击

4.如何收集程序报错信息并上传给服务器,记录报错日志:

在前端项目中收集程序报错信息并上传给服务器,可以按照以下步骤实现:

  1. 全局错误捕获: 使用全局的 window.onerror 事件处理器捕获未被捕获的 JavaScript 错误。在 Vue、React 或 Angular 等现代框架中,也可以在根组件或全局错误处理器中捕获错误。

lineNumber, columnNumber, error) {
// 对错误信息进行处理
var errorInfo = {
message: errorMessage,
file: fileName,
line: lineNumber,
column: columnNumber,
stack: error && error.stack
};

   // 保存错误信息到本地变量或状态管理器(如果是框架应用)
   storeErrors(errorInfo);

   // 发送错误信息到服务器
   sendErrorToServer(errorInfo);
   return true; // 表示已处理错误    };    ```
  1. 捕获Promise错误: 为了捕获Promise的reject未处理错误,可以使用 window.addEventListener('unhandledrejection', handler)

function(event) {
const reason = event.reason;
const promise = event.promise;
const detail = {
reason: reason.toString(),
promise: promise.toString()
};

   storeErrors(detail);
   sendErrorToServer(detail);    });    ```
  1. 收集错误信息: 将错误信息整理成易于处理和传输的格式,例如JSON对象。

  2. 发送错误信息到服务器: 使用AJAX或Fetch API将错误信息发送到服务器端。

        fetch('/api/report-error', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(errorInfo)
        }).catch(err => {
            // 如果发送失败,可以尝试本地存储,待下次有机会再发送
            storeForLater(errorInfo);
        });    }    ```
    
    
  3. 服务端处理: 服务端需要提供一个接收错误报告的接口,并将接收到的错误日志存储到数据库或日志文件中。

  4. 跨域处理: 如果涉及到跨域问题,需要在服务器端设置CORS策略以允许前端发送错误日志。

  5. 补充错误收集: 对于SPA应用,可以使用专门的前端监控工具,如 Sentry、Bugsnag 或者自建的前端错误监控库,它们通常提供了更丰富的错误追踪和分析功能。

注意,实际项目中还需要考虑错误信息的隐私保护和合规性,对敏感信息进行脱敏处理。同时,错误上报应当采取适当的节流和防抖策略,避免短时间内大量错误造成的网络拥堵或服务器压力过大。

四、计算机网络安全

1.常见的网络攻击

XSS:Cross Site Scripting 又叫做跨站脚本攻击(跨站->顾名思义就是我们从一个网站跑到了另外一个网站上,脚本->也就是我们往页面中写了脚本内容),本身应该叫做CSS,但是由于CSS被占用,无奈下叫做XSS.
XSS:侧重脚本,千方百计注入并执行恶意脚本
CSRF:不注入恶意脚本,侧重于请求伪造,借刀杀人,在用户不知情的情况下借用户名义干坏事。

xss攻击危害:
1.窃取cookie
2.劫持流量
3.插入广告
4.植入木马
5.获取用户信息

主要通过:url参数注入和输入框注入,所以一切用户端输入都是不安全的。url参数注入:http://xxx?id=“< script>恶意代码< script>”

xss分类:
1.反射型(url参数注入):浏览器提交恶意代码到服务器->服务端将恶意代码传回客户端。服务端返回代码包含恶意代码。
2.DOM型(url参数注入):恶意代码仅在客户端运行。服务端返回正常代码。这就意味着恶意代码一直存在于客户端
3.储存型(输入框注入):浏览器提交恶意代码到服务器->将恶意代码储存到数据库(危害最大,最持久)。在输入框输入:< script>恶意代码< script>,表单提交到服务器储存到数据库,以后用户每次访问,都会拉取到恶意代码并执行。

XSS危害

1.利⽤虚假输⼊表单骗取⽤户个⼈信息。
2.利⽤脚本窃取⽤户的Cookie值,被害者在不知情的情况下,帮助攻击者发送恶意请求。 3显示伪造的⽂章或图⽚。
例如: 我们在登录了一个网站之后,一般都会把登录状态保存在cookie中,当我们去访问另外一个网站的时候,就会读取到cookie

如何预防:防止注入,防止执行,对用户端输入和输出做严格的把控。所以对输入进行过滤,对输出进行转义。

具体做法如下:
1.校验:对输入格式进行校验(比如是不是邮箱格式)
2.过滤:过滤< script>< iframe>等特殊标签,过滤onclick,onerror,onfocus等js事件属性。
3.编码转义:对要渲染的内容做编码转义,防止他执行
4.限制:限制输入长度,cookie设置成http only;增强攻击难度。

2.CSRF
CSRF(Cross Site Request
Forgery),即跨站请求伪造,是⼀种常⻅的Web攻击,它利⽤⽤户已登录的身份,在⽤户毫不知情的情况下,以⽤户的名义完成⾮法操作.
跨站还是指从一个网站指向另外一个网站,于XSS不同的是,他是请求,指我们在别的网站上发出一个请求,而这个请求是伪造出来的 攻击类型
1、ET型:如在页面的某个 img 中发起一个 get 请求 2、POST型:通过自动提交表单到恶意网站 3、链接型:需要诱导用户点击链接
CSRF危害
1.利⽤⽤户登录状态 ​2.⽤户不知情 ​3.完成业务请求4盗取⽤户资⾦(转账,消费)
4.冒充⽤户发帖背锅4 损害⽹站声誉
如何防止:
1CSRF Token 校验:将CSRF Token输出到页面中(通常保存在Session中),页面提交的请求携带这个Token,服务器验证Token是否正确
​2.双重cookie验证:

3.sql注入 就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗数据库服务器执行恶意的SQL命令,从而达到和服务器
进行直接的交互

1.常见的前端安全策略:

前端开发中涉及到一些安全策略,以确保应用程序的安全性。以下是一些常见的前端安全策略:

1.HTTPS协议: 使用HTTPS协议来加密前端和服务器之间的通信,防止敏感信息被中间人攻击截获。

内容安全策略 (CSP)
CSP 本质上就是建⽴⽩名单,由浏览器进行拦截。开发者明确告诉浏览器哪些外部资源可以加载和执⾏。我们只需要配置规则,如何拦截是由浏览器⾃⼰实现的。我们可以通过这种⽅式来尽量减少 XSS 攻击。
跨站请求伪造 (CSRF) 防护:
使用CSRF令牌来验证请求的合法性,防止恶意站点发送伪造请求。

跨站脚本 (XSS) 防护:
对用户输入进行合适的验证和转义,以防止恶意脚本的注入。使用安全的编码函数如encodeURIComponent和htmlspecialchars来处理用户输入。
特别解释: encodeURIComponent 主要用于确保在 URI 中传递参数时不会破坏 URI 结构,特别是处理包含特殊字符的参数。这有助于避免由于特殊字符引起的 URL 解析问题,提高了 URL 参数的可靠性。
HTTP Only 和 Secure Cookie:
在设置Cookie时,使用HttpOnly标记确保Cookie只能通过HTTP协议传递,防止被JavaScript访问。使用Secure标记确保Cookie只能通过HTTPS传递。
response.addHeader(“Set-Cookie”, “uid=112; Path=/; HttpOnly”)

子资源完整性 (SRI):
使用SRI来确保引入的第三方资源在加载时不被篡改。通过在script>、link>等标签中添加integrity属性,指定资源的哈希值。

html Copy code

2.Cookie与Session区别

cookie:
1.服务器通过 HTTP 响应头中的 Set-Cookie 字段将 Cookie 发送到客户端。该字段包含了要设置的 Cookie 的名称、值以及一些可选的属性(如过期时间、域、路径等)。浏览器在接收到带有 Set-Cookie 头的 HTTP 响应后,将 Cookie 存储在客户端。
2.当浏览器发起对同一域名的服务器的 HTTP 请求时,会自动将该域下的所有 Cookie 信息通过 Cookie 头发送给服务器。 服务器通过解析请求头中的 Cookie 字段,可以获取客户端发送的 Cookie 信息。
过期和持久性:

Cookie 可以设置过期时间,浏览器会在过期时间后自动删除该 Cookie。 如果未设置过期时间,Cookie 就是会话性的,将在浏览器关闭时被删除。 持久性 Cookie 可以在过期时间前一直保存在客户端,即使浏览器关闭。 安全标志和
HttpOnly 标志:

secure 标志指示浏览器只有在使用安全连接(HTTPS)时才发送 Cookie。 HttpOnly 标志表示该 Cookie 不能通过
JavaScript 访问,从而防止跨站脚本攻击(XSS)。
----------------------------------------------------->
Session:

3.三次握手与四次挥手

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/3e5e4f150dc74907860803a56e419991.png)![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/63a480502a804e7e9e937e21aae68fe3.png)

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

DNS解析全过程;
在这里插入图片描述

五、网络请求

1.ajax,Fetch,axios区别。

Ajax是一种思想。指异步的javascript和XML。XMLHttpRequest
是应用浏览器提供的api,允许JavaScript在不重新加载页面的情况下与服务器交换数据。这意味着开发者可以在网页上创建异步的HTTP请求,发送和接收数据,并根据响应更新页面内容,从而实现动态、交互式的Web应用。使用
XMLHttpRequest 的基本步骤包括:
1.创建 XMLHttpRequest 对象: var xhr = new XMLHttpRequest();
2.打开一个到服务器的连接: xhr.open(‘GET’, ‘https://api.example.com/data’, true); // 第三个参数为true表示异步请求
3.设置请求状态改变时的回调函数: xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
// 请求完成且成功
console.log(xhr.responseText); } };
4.发送请求: xhr.send(null); // GET请求不需要传入请求体,POST请求则可能需要传递数据
5.可选)设置请求头和其他属性,例如: xhr.setRequestHeader(‘Content-Type’, ‘application/json’);
Fetch是es6提出的一个原生api,应用方式就是.then链式调用。用法雷同promise。
axios是基于XHR的二次封装
Axios 是一个基于 Promise 的 HTTP 库,用于浏览器和 node.js 中。它提供了更简洁的 API 和丰富的功能来处理前端与后端之间的数据交互,可以方便地进行Ajax请求。
相较于原生的 XMLHttpRequest 对象,Axios 提供了如下的优点:

  1. Promise 基础:所有 Axios 的请求都返回一个 Promise,这使得异步操作更容易管理和组合。

2.跨域支持:内置对CORS(Cross-Origin Resource Sharing)的支持,可以在不同源之间发送请求。

3.拦截请求/响应:允许你在请求或响应被发送到服务器或从服务器接收到之前对其进行拦截和修改。

  1. 转换请求/响应数据:自动将请求体序列化为JSON格式,并能自动解析从服务器返回的JSON数据。

5.取消请求:可以取消正在进行的请求。

6.配置默认值:可以设置全局的默认配置,如基础URL、超时时间等。

使用 Axios 发起一个 GET 请求的基本示例:

import axios from 'axios';

axios.get('https://api.example.com/data')
  .then(response => {
    console.log(response.data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

六、css

1.在 HTML 中引用样式时使用 和 @import 它们有什么区别:

1.加载方式: link>:在页面加载时,样式表会并行加载,不会阻塞页面的渲染。 @import:样式表会在页面加载完毕后加载,会阻塞页面的渲染。

2.浏览器兼容性:

link>:是 HTML 标签,得到广泛支持,适用于所有现代浏览器。 @import:是 CSS 提供的语法规则,不被一些古老的浏览器(如
IE5 和 IE6)支持。

3.使用位置: link>:可以用于引入其他资源,如图标等。而且可以放在文档的 head> 或 body> 中。 @import:只能用于引入样式表,并且必须放在样式表的最前面。

4.JavaScript 操作: link>:可以通过 JavaScript 动态创建或删除 元素,实现动态切换样式。 @import:由于是在 CSS 中使用的语法,JavaScript 不直接操作 @import。
总体来说,一般推荐使用link>,因为它更灵活、性能更好,而且更好地支持异步加载和并行下载。而 @import 更适合在样式表内部引入其他样式,但在现代开发中,很多项目更倾向于使用 。

2.垂直居中

1.Flex 布局:
.container {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh; /* 可根据实际情况设置容器高度 */
}
2.Grid 布局:
.container {
  display: grid;
  place-items: center;
  height: 100vh; /* 可根据实际情况设置容器高度 */
}
3.绝对定位和 transform
.container {
  position: relative;
  height: 100vh; /* 可根据实际情况设置容器高度 */
}
.centered {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

3.Flex 布局底层逻辑

主轴和交叉轴: Flex 布局中有一个主轴(main axis)和一个交叉轴(cross axis)。主轴是 Flex
容器的主要方向,而交叉轴是与主轴垂直的轴
主轴上的排列:

Flex 容器可以使用 justify-content 属性来设置子元素在主轴上的排列方式。 可选值包括
flex-start、flex-end、center、space-between、space-around 等。
交叉轴上的对齐: Flex 容器可以使用 align-items 属性来设置子元素在交叉轴上的对齐方式。 可选值包括
flex-start、flex-end、center、baseline、stretch 等。

4.position的几种定位

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

5.CSS3的新特性有哪些?

CSS3引入了许多新的特性和改进,以下是一些关键的新特性概览:

  1. 弹性布局(Flexbox)

    • display: flex; 可以创建灵活的容器,使得子元素能够自动填充可用空间、调整自身大小和顺序。
    • 弹性属性如 flex-grow, flex-shrink, flex-basis, flex-direction, justify-content, align-items, align-content 等提供了对项目布局的强大控制。
  2. 网格布局(Grid Layout)

    • display: grid; 创建了一个二维网格系统,用于更精细的页面布局。
    • 网格属性包括 grid-template-columns, grid-template-rows, grid-gap, grid-auto-flow, grid-auto-columns, grid-auto-rows,
      grid-column-start, grid-column-end, grid-row-start,
      grid-row-end 等,可以精确地定位和调整网格项。
  3. 选择器增强

    • 更复杂的选择器:例如 :nth-child(), :nth-last-child(), :not(), :target, :checked, :empty, :required, :optional, :valid, 和
      :invalid 等。
    • 属性选择器:如 [attr=value], [attr~=value], [attr|=value], [attr^=value], [attr$=value], [attr*=value] 等。
  4. 边框与圆角

    • border-radius 可以设置元素的边角为圆角或椭圆角。
    • 边框图像(Border Images)允许使用图像作为元素边框样式。
  5. 背景与渐变

    • linear-gradient()radial-gradient() 可以创建线性渐变和径向渐变背景。
    • 多背景(Multiple Backgrounds)允许多个背景图像在一个元素上层叠显示。
    • 背景尺寸与位置 (background-size, background-position) 提供了更多的自定义选项。
  6. 文本与字体

    • text-shadow 添加文本阴影效果。
    • word-wrap 以及 overflow-wrap 控制换行规则。
    • font-face 规则支持自定义字体,并通过 @font-face 引入外部字体文件。
    • text-overflow 控制溢出文本内容的显示方式。
  7. 过渡与动画

    • transition 属性提供简单过渡效果,可以在不依赖JavaScript的情况下实现元素在改变状态时平滑过渡。
    • animation 动画模块结合 @keyframes 规则,可以创建复杂的 CSS 动画。
  8. 变换(Transforms)

    • transform 属性允许进行旋转、缩放、位移和倾斜等2D/3D变换操作。
    • translate, rotate, scale, skew 等变换函数。
  9. 过滤器与模糊效果

    • filter 属性提供了滤镜效果,如模糊(blur)、灰度(grayscale)、饱和度调整等。
  10. 盒模型修改

    • box-sizing 允许选择标准盒模型或者边界盒模型来计算元素尺寸。
  11. 多列布局(Columns)

    • column-count, column-width, column-gap, column-rule 等属性用于将内容划分为多列布局。

这些新特性极大地增强了CSS的功能,使得开发人员能够更好地控制网页布局、视觉效果以及交互体验。

七、代码规范

eslint代码规范
commitint约定式提交,规范提交

ESLint:
是一个强大的JavaScript静态代码分析工具,可以检测出潜在的错误和不符合编码规范的地方。通过配置不同的规则集(如Airbnb、Google等),它能够帮助团队保持一致的代码风格。
Prettier:
是一款广泛使用的代码格式化工具,支持多种语言包括JavaScript、HTML、CSS/SCSS/LESS、Vue、React
JSX等。它可以自动按照预设的样式标准格式化代码。 stylelint:

stylelint 类似于 ESLint,但专门用于 CSS 和 SCSS/Less 代码的
linting。它提供了一套详尽的规则以确保样式表代码的一致性和质量。 EditorConfig:

EditorConfig
插件并不是一个直接进行代码规范检查的工具,但它有助于在不同编辑器和IDE之间统一代码风格,例如缩进、换行符和字符编码等基础设置。

八、工程化与工具

1.如何配置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版本升级而有所变化,请参考最新的官方文档进行配置。

2. 如何构建可维护性和可扩展性高的前端架构?

构建可维护性和可扩展性高的前端架构是一个涉及多个方面的系统工程,以下是一些关键的实践和原则:

  1. 组件化:将界面拆分为独立、可复用的组件。每个组件应具有清晰的职责和封装良好的内部状态与逻辑,遵循单一职责原则。在现代前端框架(如React、Vue或Angular)中,组件化开发是核心思想,允许团队成员各自负责不同组件的开发与维护。

  2. 模块化:通过模块系统(如CommonJS、ES6 Modules)组织代码,确保各个功能单元能够独立加载、更新和测试。模块化的代码更易于理解和维护,并且有利于并行开发和性能优化。

  3. 分层设计:应用可以按照表现层、业务逻辑层和数据访问层进行分层设计,每一层关注不同的任务,降低耦合度。

  4. 状态管理:采用统一的状态管理模式(例如Redux、Vuex或MobX),集中管理应用的状态和数据流,提高复杂状态下的可预测性和调试能力。

  5. 路由规划:合理设计路由体系,使得各个页面和功能模块之间的导航结构清晰,便于新增和调整页面布局。

  6. 依赖管理和自动化工具:使用包管理器(npm、yarn等)管理项目依赖,结合构建工具(Webpack、Rollup等)进行资源编译、打包和优化。

  7. 代码规范与审查:遵循一致的编码规范,利用ESLint等工具进行静态检查,以及实施代码审查制度以保证代码质量。

  8. 微前端架构: 微前端是一种架构模式,它允许在一个Web应用中运行多个相互独立的小型前端应用(子应用)。这些子应用可以有不同的技术栈、版本和团队管理,它们之间通过约定或API实现通信和集成。微前端的主要优点包括:

    • 独立部署:各团队可独立开发、部署和迭代自己的子应用。
    • 技术异构:支持多种前端框架共存,利于遗留系统的渐进式重构和技术升级。
    • 降低风险:一个子应用的问题不会影响到整个系统的稳定运行。
    • 更好的可维护性:由于每个子应用都是相对独立的单元,因此更容易维护和扩展。

    实践经验方面,在微前端架构中,常见的实现方案有:

    • 基于框架/库的解决方案:比如Single-SPA、Qiankun等,提供了用于管理和加载微前端应用的标准机制。
    • 自定义入口策略:通过修改浏览器的HTML导入或动态脚本注入等方式,让每个子应用可以在主应用容器内按需加载和卸载。
    • 共享服务与状态管理:为所有子应用提供公共的服务层和状态存储,实现跨应用的数据共享和交互。

综上所述,构建高可维护性和可扩展性的前端架构需要综合运用上述策略,并根据具体项目的需求和规模灵活调整和优化。对于大型企业级应用而言,微前端架构尤其适用于解决复杂度高、团队协作需求强、技术栈多样化的场景。

九、项目难点亮点

1.大文件上传

分片上传。

要实现大文件分片上传,通常需要以下几个步骤:

1.通过DOM获取文件对象,并且对文件进行MD5加密(文件内容+文件标题形式),采用SparkMD5进行文件加密;

2.进行分片设置,利用Blob的slice方法进行文件分片处理,并且依次进行上传;

3.当分片文件上传完成后,请求合并接口后端进行文件合并处理即可。
代码实现点击

那么怎么计算文件的hash值呢?可以通过一个工具:spark-md5,所以我们得先安装它。
在这里插入图片描述

2.微前端应用

项目背景:几个分公司之间需要定制化开发某些功能,但是他们有通用的公共基础模块,所以考虑引用微前端来进行主应用+子应用来进行。主应用就写公共模块,子应用分别写对应分公司的业务逻辑。分布式打包部署,实现不同的公司去访问不同的内容。好处就是子应用相互独立,不受技术栈影响,分布式部署。
难点就是之前搭建微前端框架的是vue2.版本,vue3以上版本并不适用,需要重新搭建。Qiankun官网目前还不支持vue3使用例子,官方API方法行不通。
qiankun常见的坑
微前端的主要优点包括:

1.独立部署:各团队可独立开发、部署和迭代自己的子应用。
2. 技术异构:支持多种前端框架共存,利于遗留系统的渐进式重构和技术升级。
3.降低风险:一个子应用的问题不会影响到整个系统的稳定运行。
4.更好的可维护性:由于每个子应用都是相对独立的单元,因此更容易维护和扩展。

1.项目难点(Difficulties):
**技术挑战:**例如,在项目中遇到的技术难题,如实现响应式布局、流式布局、复杂动画效果,或者处理高并发状态管理等。你可以详细描述问题的具体场景,以及你是如何调研、选择解决方案的,比如采用何种布局策略,使用Flexbox或Grid布局系统解决自适应问题。

性能优化:如果项目中有性能瓶颈,可以谈谈你如何进行性能优化,包括但不限于代码分割与懒加载、减少渲染树重绘、优化图片资源加载、压缩代码和CSS、使用CDN加速等措施。

兼容性问题: 如果你面对的是跨浏览器兼容问题,可以阐述不同浏览器下的表现差异,以及你使用了哪些工具和技术手段进行调试和修复,比如PostCSS、Babel转换、polyfill库等。

数据交互与API调用: 如果项目涉及到大量异步数据请求和状态管理,可以提及如何设计和优化API接口调用流程,如何处理错误边界,以及可能使用到的状态管理库(如Redux、Vuex或React Hooks等)。

团队协作与工程化: 讨论项目中关于组件化、模块化开发的困难,以及为提高团队效率而引入的一些工具和最佳实践,如Webpack配置、ESLint规范、Git工作流、CI/CD集成等。

2.项目亮点(Highlights):
创新点:突出你在项目中采用的独特技术和方法,如使用Web Worker进行后台计算、利用Service Worker实现离线访问、采用PWA技术提升用户体验等。

用户体验优化:强调对用户交互体验的改善,如通过流畅的过渡动画增强页面切换效果,或使用懒加载、骨架屏等减少白屏时间。

架构设计:分享项目的架构亮点,比如采用了微前端架构、SPA(单页应用)、SSR(服务器端渲染)等高级技术,并说明这些技术选择如何帮助项目取得成功。

业务逻辑处理:如果项目包含复杂的业务逻辑,介绍你是如何抽象模型、编写可复用组件,以及如何保证代码的可维护性和扩展性。

测试与质量保障:指出项目中的自动化测试方案,如单元测试、E2E测试等,以及对于代码覆盖率、性能监控等方面的贡献。

十、uniapp相关

1.uniapp优势

1.跨平台性,只需要开发一套代码,即可编译生成多端应用程序,大大提高了开发效率和复用率,节约时间成本。
2.遵循vue生态系统,好上手。
3.丰富的组件库于ui支持。

2.uniapp底层如何实现多端适配的?

UniApp 是一个基于 Vue.js 的跨平台开发框架,它通过一系列技术和设计原则实现了多端适配:

  1. 统一的开发语法和API: UniApp 使用 Vue.js 作为基础开发框架,开发者可以使用熟悉的 Vue 语法来编写代码。通过在 Vue.js 的基础上封装一套统一的 API 和组件库,确保开发者在编写代码时无需关注具体平台差异。

  2. 运行时编译和渲染引擎

    • UniApp 采用了运行时编译机制,它会根据目标平台的不同,将相同的 Vue SFC(单文件组件)源码编译成对应平台的原生代码或 Web 版本代码。
    • 在渲染引擎方面,UniApp 会对各平台的渲染方式进行封装和适配,确保一份代码可以在不同平台渲染出正确的界面。
  3. 条件编译

    • UniApp 提供条件编译指令,允许开发者根据目标平台编写特定的逻辑或样式。例如,可以通过 @ifdef@endif 等条件编译指令来区分不同平台的实现差异。
  4. API 适配层

    • 不同平台的原生 API 存在很大差异,UniApp 在底层通过封装和适配层来解决这个问题。它将各个平台的原生API抽象成统一的标准API,开发者只需调用统一接口即可实现跨平台功能。
  5. 组件化和UI库

    • UniApp 提供了一系列跨平台的UI组件,这些组件在设计时就考虑到多端适配的问题,可以根据目标设备和屏幕尺寸自动调整布局和样式。
  6. 资源配置和打包

    • 在打包阶段,UniApp 会根据不同平台的需求,生成针对性的资源配置文件和包体,确保应用在各平台上的性能和兼容性。

通过以上方式,UniApp 让开发者能够使用一套代码实现
iOS、Android、Web、以及各种小程序平台的应用开发,大大降低了跨平台开发的成本和复杂度。

十、ts相关知识

1.ts和js有什么区别

TypeScript(TS)和JavaScript(JS)是两种广泛应用于前端开发的编程语言,它们之间最根本的区别在于类型系统、编译过程以及一些语言特性:

  1. 类型系统

    • JavaScript 是一门动态类型的弱类型语言。在JavaScript中,变量可以在运行时改变类型,编译器或解释器不会在编译阶段检查类型错误。
    • TypeScript 是JavaScript的超集,增加了静态类型系统。在TypeScript中,开发者可以为变量、函数参数、函数返回值等指定类型,编译器会在编译阶段进行类型检查,提前发现类型错误,提高代码质量和可维护性。
  2. 编译过程

    • JavaScript 通常不需要编译过程,可以直接在支持JavaScript的环境中运行。
    • TypeScript 需要通过编译器(如tsc)将.ts文件编译成.js文件,然后在浏览器或Node.js环境中运行。编译过程可以进行类型检查、转换ES6+语法为ES5(或其他目标环境)等操作,确保代码在目标环境兼容。
  3. 语言特性

    • TypeScript 引入了许多JavaScript标准未来才会有的特性,比如类(classes)、接口(interfaces)、枚举(enums)、类型注解、泛型、装饰器(decorators)、命名空间(namespaces)等,这些特性在一定程度上借鉴了传统的面向对象编程语言。
    • JavaScript 虽然也在不断演进并逐渐加入类似特性(如ES6以来的类、模块等),但在TypeScript出现时,许多现代编程结构并不原生支持。
  4. 工具链支持

    • TypeScript 由于其静态类型特性,与VS Code等现代IDE配合紧密,提供出色的代码提示、自动完成、类型检查等功能,极大提升了开发效率和代码质量。
    • JavaScript 也有很好的IDE支持,但相比TypeScript在类型提示方面略逊一筹,除非借助第三方库或工具(如JSDoc、TypeScript类型注解等)。

总之,TypeScript是为了解决JavaScript在大规模工程中的不足而设计的,它提供了一种更加强大和严谨的编程模型,适用于大型项目开发,有助于提高团队协作效率和软件质量。而JavaScript则更加灵活,更适合小型项目或对快速开发迭代有较高要求的场合。随着JavaScript标准的发展,许多TypeScript的特性已经被或正在被纳入ECMAScript标准之中。

2.TypeScript 中的枚举(Enum)主要有几种类型:

TypeScript 中的枚举(Enum)主要有以下几种类型:

  1. 数字枚举(Numeric Enums): 默认情况下,枚举成员是数字类型,且从0开始自动递增。当然,也可以手动为每个枚举成员指定具体的数字值。

      Red, // 等价于 Red = 0
      Green = 1,
      Blue = 2,
      Yellow = 3 // 如果省略Yellow的值,则默认为3    }    ```
    
    
  2. 字符串枚举(String Enums): TypeScript 2.4 引入了字符串枚举,其中每个枚举成员的值必须是字符串,或者是另一个枚举成员。

      Red = "red",
      Green = "green",
      Blue = "blue"    }    ```
    
    
  3. 常数枚举(Const Enums): 常数枚举在编译时会被完全内联展开,不会在编译后的JavaScript代码中产生实际的枚举对象。这对于性能优化和减小代码体积很有帮助,但如果枚举值被多次使用,可能导致重复代码。

      Red = "red",
      Green = "green",
      Blue = "blue"    }    ```
    
    
  4. 复合枚举(Computed Members): 枚举成员可以是计算出来的值,但这个特性仅适用于数字枚举,并且需要手动指定初始值。

      Red = 1 << 0, // 0b0001
      Green = 1 << 1, // 0b0010
      Blue = 1 << 2, // 0b0100    }    ```
    
    
  5. 反向映射(Reverse Mappings): TypeScript 枚举不仅允许通过名称访问枚举值,还允许通过枚举值反向查找名称。例如:

      Red,
      Green,
      Blue    }
    
    let colorName = Color[Color.Red]; // "Red"    ```
    
    
  6. 混合枚举(Mixed Enums): 枚举中可以混合使用数字和字符串,但这种情况下只有数字枚举成员可以拥有反向映射。

总的来说,TypeScript
枚举提供了强类型的安全性,并且在开发过程中可以使代码更具可读性和自解释性。通过枚举可以方便地管理和使用一组固定的、有限的、命名良好的值集合。

2.1.TypeScript 中的枚举在代码中的应用

TypeScript 中的枚举(Enums)在代码中有多种用途,可以增强代码的可读性和安全性,减少硬编码的魔法数字和字符串。以下是一些应用场景:

  1. 状态标识: 枚举可以用来表示某个变量的不同状态,比如订单的状态、用户账户的状态、游戏中的玩家角色状态等。

      Pending = 'pending',
      Processing = 'processing',
      Shipped = 'shipped',
      Delivered = 'delivered'    }
    
    let orderStatus: OrderStatus = OrderStatus.Pending;    ```
    
    
  2. 错误代码: 在错误处理中,枚举可以用来表示各种预定义的错误代码,避免直接使用难以理解的数字或字符串。

      NotFound = 404,
      Unauthorized = 401,
      BadRequest = 400    }
    
    function handleResponse(errorCode: ErrorCode) {
      switch (errorCode) {
        case ErrorCode.NotFound:
          // 执行404错误处理逻辑
          break;
        case ErrorCode.Unauthorized:
          // 执行401错误处理逻辑
          break;
        // ...
      }    }
    
    
  3. 颜色、主题或模式选择: 在UI开发中,枚举可以用来表示界面的颜色、主题风格或显示模式。

      Light,
      Dark    }
    
    class AppSettings {
      theme: Theme = Theme.Light;
      toggleTheme() {
        this.theme = this.theme === Theme.Light ? Theme.Dark : Theme.Light;
      }    }
    
    
  4. API 请求路径或参数: 在API客户端代码中,枚举可用于标准化URL路径或请求参数,防止拼写错误和不一致。

      Users = "/api/users",
      Products = "/api/products"    }
    
    function fetchResource(endpoint: Endpoint) {
      return fetch(endpoint);    }
    
    
  5. 指令或动作类型: 在事件驱动的应用中,枚举可以用作事件类型或指令类型。

      Increment,
      Decrement,
      Reset    }
    
    interface Action {
      type: ActionKind;
      payload?: any;    }    ```
    
    

通过枚举,我们可以更清晰地表达代码意图,同时也利用TypeScript的类型系统确保在编译时就能捕获到类型不匹配的错误,提高代码质量和维护性。

3.TypeScript 重载

TypeScript 中的函数重载(Overloads)允许一个函数具有多个签名,这意味着函数可以根据传入参数的不同类型或数量表现出不同的行为。这种机制有助于提高代码的可读性和类型安全性,编译器可以根据传递给函数的实际参数类型来决定调用哪个函数版本。

下面是一个使用函数重载的简单示例:

// 重载示例:一个加法函数可以处理数字相加和字符串拼接
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: number | string, b: number | string): number | string {
  if (typeof a === 'string' || typeof b === 'string') {
    return String(a) + String(b); // 如果任一参数为字符串,则执行字符串拼接
  } else {
    return a + b; // 否则执行数字相加
  }
}

console.log(add(1, 2)); // 输出:3
console.log(add('Hello ', 'World')); // 输出:'Hello World'

在这个例子中,add 函数有两个重载声明:

  • 第一个重载版本接收两个 number 类型的参数,返回一个 number 类型的结果。
  • 第二个重载版本接收两个 string 类型的参数,返回一个 string 类型的结果。

实际的函数实现会处理所有可能的重载情况,编译器在编译时会根据传入参数类型自动选择合适的重载版本。在运行时,TypeScript
编译器转换后的 JavaScript 代码并不会保留重载的原始形式,而是会根据重载逻辑合成一个通用的实现。

4.Type与interface区别

TypeScript 中的 typeinterface 都是用来定义类型的工具,它们有很多相似之处,但也有一些关键区别。下面是它们之间的异同点:

  1. 定义与使用:

    • Type(类型别名)type 关键字用于创建类型别名或定义新的复杂类型,如元组类型、联合类型、字面量类型等。
    • Interface(接口)interface 关键字用于定义对象类型的形状,包括属性、方法和其他接口的组合。
  2. 扩展与实现:

    • Type:类型别名本身不支持继承或扩展,但可以通过类型操作符(如交叉类型 &、联合类型 |)来组合不同类型。
    • Interface:接口可以使用 extends 关键字从其他接口或基类继承,也可以被类实现(使用 implements 关键字)。
  3. 声明合并(Declaration Merging)

    • Type:类型别名不支持声明合并,如果有多个相同名称的类型别名声明,将会报错。
    • Interface:接口支持声明合并,若在不同位置定义了同名接口,它们的成员会被合并在一起。
  4. 使用场景与限制:

    • Type:类型别名在需要定义非对象类型(如基本类型别名、元组类型、联合类型)时特别有用,或者当你想要为现有类型赋予一个易于理解和记忆的新名称时。
    • Interface:接口更适合用于描述对象结构,尤其是在定义复杂对象模型、接口约束、强制实现特定方法等方面。
  5. 类型兼容性检查:

    • TypeInterface 在类型兼容性检查上遵循同样的规则,它们都可以用于类型注解,以确保变量、函数参数和返回值具有正确的类型。
  6. 实例类型获取:

    • Type:可以使用 typeof 关键字获取已存在的变量或函数的类型,并为其创建一个新的类型别名。
    • Interface:虽然不能直接从实例获取类型,但可以通过接口描述实例的形状,类可以实现接口来满足接口的要求。

综上所述,typeinterface
在大多数场景下可以互换使用,具体选择哪种取决于项目的实际需求和个人偏好。但在需要利用接口特有的特性,如声明合并或类实现时,应优先选择
interface。而在需要定义非对象类型或进行类型运算时,则应该使用 type

5.interface如何实现继承

在TypeScript中,interface可以通过extends关键字实现继承,type可以通过交叉类型(intersection types)或联合类型(union types)模拟继承效果。以下是如何在TypeScript中实现interfacetype继承的例子:

5.1. Interface 继承
// 定义基础接口
interface Animal {
  name: string;
  age: number;
  speak(): void;
}

// 基础接口继承
interface Dog extends Animal {
  breed: string;
  barkVolume: number;
  fetch(toy: string): void;
}

// 实现Dog接口的对象
let myDog: Dog = {
  name: 'Rex',
  age: 3,
  breed: 'Golden Retriever',
  barkVolume: 5,
  speak() {
    console.log('Woof!');
  },
  fetch(toy: string) {
    console.log(`Fetching ${toy}!`);
  }
};
5.2. Type 继承/组合
使用交叉类型(Intersection Types)模拟继承
// 基础类型
type Base = {
  name: string;
  age: number;
};

// 模拟继承
type Derived = Base & {
  occupation: string;
};

// 实例化Derived类型
let person: Derived = {
  name: 'Alice',
  age: 30,
  occupation: 'Engineer',
};
使用联合类型(Union Types)模拟多态

联合类型并不是严格意义上的继承,但它可以表示一个值可以是多种类型之一,类似于面向对象编程中的多态性。

type Shape = Square | Circle;

interface Square {
  kind: 'square';
  size: number;
}

interface Circle {
  kind: 'circle';
  radius: number;
}

// 实例化Shape类型
let square: Shape = {
  kind: 'square',
  size: 10,
};

let circle: Shape = {
  kind: 'circle',
  radius: 5,
};

尽管type本身不支持像interface那样的继承,但是可以通过类型组合的方式来达到类似的效果。实际上在许多场景下,typeinterface的功能是可以相互替换的,选择使用哪一个取决于项目需求和编程习惯。

6.es5和es6分别如何实现继承

ES5 实现继承的方式
  1. 原型链继承
    原型链继承是最基本的继承方式,通过将子类的原型设置为父类的一个实例,从而让子类能够访问父类的原型链上的属性和方法。

    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.sayHello = function() {
      console.log('Hello, I am ' + this.name);
    };
    
    function Child(name, age) {
      Parent.call(this, name); // 继承属性
      this.age = age;
    }
    // 继承原型链
    Child.prototype = Object.create(Parent.prototype);
    // 修复constructor属性,否则Child.prototype.constructor将是Parent
    Child.prototype.constructor = Child;
    
    // 添加子类独有的方法
    Child.prototype.sayAge = function() {
      console.log('My age is ' + this.age);
    };
    
    let child = new Child('Tom', 10);
    child.sayHello(); // 输出:Hello, I am Tom
    child.sayAge();   // 输出:My age is 10
    
  2. 借用构造函数继承(经典继承或伪类继承):
    通过在子类构造函数内部调用父类构造函数,并将this绑定到子类实例,以此来复制父类实例上的属性。

    function Parent(name) {
      this.name = name;
    }
    
    function Child(name, age) {
      Parent.call(this, name); // 继承属性
      this.age = age;
    }
    
    let child = new Child('Tom', 10);
    child.name; // 输出:Tom
    
  3. 组合继承(原型链+借用构造函数)
    结合了原型链继承和借用构造函数的优点,既能继承属性又能继承方法,同时避免了原型链继承中引用类型属性的共享问题。

    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.sayHello = function() {
      console.log('Hello, I am ' + this.name);
    };
    
    function Child(name, age) {
      Parent.call(this, name); // 继承属性
      this.age = age;
    }
    // 继承原型方法
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
    
    Child.prototype.sayAge = function() {
      console.log('My age is ' + this.age);
    };
    
    let child = new Child('Tom', 10);
    child.sayHello(); // 输出:Hello, I am Tom
    child.sayAge();   // 输出:My age is 10
    
ES6 实现继承的方式

ES6引入了class关键字,使得类的声明和继承变得更加直观和规范。

class Parent {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    console.log('Hello, I am ' + this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 使用super关键字调用父类的构造函数
    this.age = age;
  }

  sayAge() {
    console.log('My age is ' + this.age);
  }
}

let child = new Child('Tom', 10);
child.sayHello(); // 输出:Hello, I am Tom
child.sayAge();   // 输出:My age is 10

在ES6中,class继承是基于原型继承原理之上的一种更易读的语法糖,实质上仍然是对原型链进行了操作。通过extends关键字表明继承关系,并在子类构造函数中通过super关键字调用父类构造函数。

7.泛型有哪些?

TypeScript 中的泛型(Generics)是一种强大的工具,用于创建可以适应多种类型的数据结构和函数。以下是在TypeScript中泛型的主要形式:

  1. 泛型函数
    泛型函数允许你定义一个函数,其参数和返回值可以接受任意类型,如下所示:

    function identity<T>(arg: T): T {
      return arg;
    }
    

    在此示例中,<T> 是泛型类型参数,表示函数接受并返回同一类型的值。

  2. 泛型类
    泛型类允许你在类级别定义类型参数,使其属性或方法可以使用该类型:

    class Container<T> {
      value: T;
      constructor(item: T) {
        this.value = item;
      }
    }
    let myContainer = new Container<string>('hello');
    
  3. 泛型接口
    泛型接口可以定义含有类型参数的接口,使得其他类型满足接口时不必固定为单个类型:

    interface Pair<T, U> {
      first: T;
      second: U;
    }
    let pair: Pair<number, string> = { first: 1, second: 'one' };
    
  4. 泛型约束
    通过使用接口约束或类类型约束,可以限制泛型参数必须符合某种类型:

    interface Lengthwise {
      length: number;
    }
    
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
      console.log(arg.length);
      return arg;
    }
    
  5. 泛型类型别名
    泛型类型别名用于为复杂类型创建简短的名字:

    type PairOf<T> = [T, T];
    let pair: PairOf<number> = [1, 2];
    
  6. 泛型上下文
    泛型上下文指的是在类、接口或函数内部的泛型定义,它可以被外部的泛型参数捕获:

    class Box<T> {
      contents: T[];
      add(item: T): void {
        this.contents.push(item);
      }
      
      // 内部泛型上下文
      protected process<U>(item: U): U {
        // ...处理item...
        return item;
      }
    }
    
  7. 泛型约束和通配符类型
    泛型约束可以使用 extends 关键字来限制泛型参数必须继承自某个类型或实现某个接口,而通配符类型(如 unknownanynever)也可以在泛型上下文中使用,以表达更广泛的类型范围。

通过这些泛型机制,TypeScript 允许开发者创建更加灵活、可复用并且类型安全的代码组件。

十一、Electron相关知识

1.进程之间如何通信

在Electron应用中,主进程(Main Process)和渲染进程(Renderer Process,即浏览器窗口中的JavaScript环境)之间可以通过IPC(Inter-Process Communication,进程间通信)来进行通信。以下是几种主要的通信方式:

  1. 使用 ipcMainipcRenderer 模块

    • 在主进程中,通过 electron 模块暴露的 ipcMain 接口来监听渲染进程的消息。 javascript // 主进程 const { ipcMain } = require('electron'); ipcMain.on('from-renderer', (event, arg) => { console.log(arg); // 进行相应处理后,可以通过event对象发送回应给渲染进程 event.reply('to-renderer', '这是从主进程返回的消息'); });

    • 在渲染进程中,通过 electron 模块暴露的 ipcRenderer 接口向主进程发送消息并接收回复。 javascript // 渲染进程(在renderer.js等文件中) const { ipcRenderer } = require('electron'); ipcRenderer.send('from-renderer', '这是从渲染进程发送的消息'); ipcRenderer.on('to-renderer', (event, arg) => { console.log(arg); });

  2. 使用 contextBridge(安全通信): 对于安全要求更高的场景,可以使用 contextBridge 来实现安全通信,防止恶意代码注入。这种方法主要用于在渲染进程中安全地暴露一部分主进程API给渲染进程调用,而无需直接使用
    ipcRenderer

  3. 旧版的 remote 模块: 注意:新版的 Electron 已经废弃了 remote 模块,因为它会导致性能问题和安全风险。但在旧版中,可以通过 remote 模块让渲染进程直接调用主进程的方法或访问主进程的对象。
    javascript // 旧版 Electron 中的渲染进程(不推荐) const { remote } = require('electron'); const mainProcessObject = remote.require('./main-process-module.js'); mainProcessObject.someFunction();

  4. 共享全局变量(不推荐): 在极少数情况下,可能会通过全局变量在主进程和渲染进程间共享数据,但这不是一个推荐的做法,因为它破坏了封装性,且容易引发竞态条件和数据同步问题。

  5. 改进的远程模块 (@electron/remote): 新版 Electron 引入了 @electron/remote 模块作为 remote
    的安全替代品,需要在主进程和渲染进程中分别配置和使用,以提供安全可控的远程调用功能。

总之,在 Electron 中,推荐使用 ipcMainipcRenderer
进行主进程与渲染进程间的通信,以确保最佳性能和安全性。对于需要在渲染进程中访问主进程功能的情况,请优先考虑是否可以通过IPC通信实现,并尽可能遵循沙箱原则,确保应用的安全性。

1-1.electron子进程和子进程之间如何通信

在 Electron 中,子进程通常是通过 Node.js 的 child_process 模块创建的,比如通过 spawnfork 等方法启动。子进程之间的通信并不直接,而是通过主进程作为中介间接进行通信。这是因为 Electron 主进程负责管理和协调所有的子进程。

以下是一个简单的例子说明如何通过主进程作为中继实现子进程间通信:

// 主进程中
const { spawn, fork } = require('child_process');
const cp1 = spawn('node', ['subprocess1.js']);
const cp2 = fork('subprocess2.js');

// 子进程1(subprocess1.js)向主进程发送消息
cp1.stdout.on('data', (data) => {
  // 主进程接收到子进程1的消息
  const messageFromSubprocess1 = data.toString();
  
  // 主进程转发消息给子进程2
  cp2.send(messageFromSubprocess1);
});

// 子进程2向主进程注册消息接收器
cp2.on('message', (message) => {
  // 子进程2接收到主进程转发过来的消息
  console.log(`Received from subprocess1: ${message}`);
});

// 同样地,也可以在子进程2向主进程发送消息,主进程再转发给子进程1

为了实现更复杂且直接的子进程间通信,可以考虑利用主进程建立一个消息队列或使用内存文件系统(如 memorystream 库)等方式共享数据。不过一般情况下, Electron 应用中更常见的需求是主进程与渲染进程或主进程与其他辅助子进程之间的通信。对于子进程间复杂的通信需求,可能需要重新审视架构设计,避免过多的进程间通信开销和复杂性。

2.electron启动项目的时候电脑卡顿,占用过多CPU如何优化?

将数据处理方面在子进程处理,主进程只渲染UI.

当 Electron 应用在启动时造成电脑卡顿、占用过多 CPU 资源时,可以从以下几个方面着手进行优化:

  1. 减少主进程加载负担

    • 优化主进程初始化逻辑:检查主进程中是否有复杂的同步操作,尤其是那些消耗大量计算资源的初始化工作,考虑将其异步化或延迟加载。
    • 控制一次性加载资源:如果应用在启动时加载大量数据或资源,尝试分批加载或者按需加载,避免一次性加载造成资源瓶颈。
  2. 限制渲染进程资源占用

    • 合理分配页面数量:如果你的应用包含多个页面或标签页,确保它们不会同时加载大量的内容或执行密集型JavaScript运算。
    • 优化渲染性能:针对前端界面,检查是否存在CSS动画、布局计算复杂度过高、DOM元素过多等问题,采用Web Workers进行后台计算,优化CSS选择器和布局策略。
  3. 资源释放

    • 适时释放不再使用的资源:确保不再使用的网络连接、文件流或定时器得到及时清理,避免资源泄漏。
  4. 硬件加速与渲染优化

    • 检查是否开启了硬件加速,有时候不当的硬件加速反而会导致性能下降,可以根据实际情况开启或关闭。
    • 使用 chrome://gpu 查看GPU相关信息,确保GPU渲染管线正常工作。
  5. 禁用不必要的模块和服务

    • 检查并移除应用中非必要的第三方模块或组件,特别是那些启动时即运行且占用资源较大的模块。
    • 避免在应用启动时就开始执行耗时长或CPU密集型的服务。
  6. 更新Electron版本

    • 确保使用的是最新的稳定版Electron,新的版本往往包含了性能优化和bug修复。
  7. 性能监控与调试

    • 使用Electron自带的性能分析工具,如DevTools中的Performance面板,找出性能瓶颈所在。
  8. 使用Electron的节能特性

    • 利用Electron的电源管理API,比如在闲置状态下降低CPU使用率。
  9. 自定义Chrome浏览器标志

    • 可以通过修改Electron的app.commandLine.appendSwitch方法添加特定的Chrome标志,调整渲染器进程的行为,比如限制渲染器进程的数量。
  10. 打包优化

    • 在打包阶段,确保只包含应用所需的最小依赖集,避免无用的代码被引入。

通过上述的一系列排查和优化措施,可以有效缓解Electron应用启动时对CPU资源的过度占用,提高用户体验。同时,持续地性能测试和优化是十分必要的,特别是在不同平台和设备上验证应用性能表现。

3.electron打包到不同平台有遇到什么兼容问题,怎么处理的.

在使用 Electron 打包桌面应用并部署到不同平台(如 Windows、macOS、Linux)时,可能会遇到以下兼容性问题及其解决方案:

  1. 图标和启动画面兼容性

    • 不同平台对图标的格式和尺寸要求不同,例如Windows需要.ico,macOS需要.icns,Linux可能需要多个.png尺寸。解决方案是在打包时,根据目标平台提供相应的图标文件。
  2. 菜单栏和快捷键

    • 不同操作系统有不同的菜单栏和键盘快捷键约定。例如,macOS应用程序菜单位于屏幕顶部,而Windows/Linux应用程序菜单通常位于窗口内部。处理方法是编写条件语句,根据process.platform来确定当前平台并展示相应的菜单结构。
  3. 文件路径差异

    • 不同操作系统对路径分隔符有不同的规定,Windows使用\,而Linux和macOS使用/。使用path模块的path.seppath.join()方法来构建跨平台的路径。
  4. 剪贴板、对话框和通知支持

    • 不同平台对剪贴板、打开文件/保存文件对话框、通知系统的API略有不同。使用Electron提供的跨平台API如electron.clipboardelectron.dialogelectron.notification来确保兼容性。
  5. 系统交互差异

    • 如macOS有Dock、TouchBar和Menu Bar等特殊交互元素,需要额外适配。使用Electron提供的相关API进行定制。
  6. 硬件加速与GPU兼容性

    • GPU驱动和硬件加速可能在不同平台上造成渲染差异。有时需要调整Electron的启动参数或使用disableHardwareAcceleration选项。
  7. 原生依赖库兼容性

    • 如果你的应用使用了原生Node.js模块(.node扩展的C/C++模块),确保这些模块在目标平台上编译和链接正确,可能需要预编译为各个平台的版本。
  8. 系统权限和安装路径

    • 不同操作系统对应用安装位置和访问权限有不同的规范。例如,macOS应用通常安装在/Applications目录下,并且需要签署证书以获得一些系统权限。

处理这些问题的方法通常包括:

  • 使用Electron的内置API来代替直接调用原生API,这些API已经封装好跨平台兼容层。
  • 在代码中编写条件语句,根据目标平台选择合适的实现。
  • 对于原生模块,使用electron-rebuild工具重建为目标平台可用的二进制模块。
  • 在打包阶段确保使用正确的配置和资源文件。

最后,测试是非常重要的环节,确保在目标平台进行充分的测试以验证应用的兼容性。使用如electron-builder之类的打包工具可以帮助自动化构建跨平台的安装包,并处理大部分上述提到的兼容性问题。

流程视图

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

案例片段

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

  • 28
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值