2023年(js,css3,es6,Vue,react)面试常问面试题总结

1.vue和react的区别是什么?

  1. 模板语言:

Vue使用基于HTML的模板语言,允许您在HTML中直接编写Vue代码,并且可以轻松地与现有的HTML、CSS和JavaScript代码集成。React没有自己的模板语言,它使用JavaScript的JSX语法来描述UI组件。

  1. 数据绑定:

Vue使用双向数据绑定,这意味着当数据发生变化时,视图会自动更新。React则使用单向数据流,只能从父组件向子组件传递数据,不能在组件之间共享数据。

  1. 组件:

Vue和React都使用组件来构建复杂的UI。Vue组件的结构更加简单明了,因为它将JavaScript和CSS代码封装在一个文件中。React则将这些代码分开管理,需要在不同的文件中进行编写和维护。

  1. 性能:

Vue和React都是非常快速的,但Vue在性能方面更加强大,因为它使用模板编译来提高渲染速度。React使用虚拟DOM来优化性能。

2.vue diff算法,react diff算法?

Vue 和 React 都使用虚拟 DOM 和 diff 算法来实现高效的 UI 渲染。

Vue 的 diff 算法:

Vue 的 diff 算法被称为 "渐进式的近似算法"。它首先将新的虚拟 DOM 树和旧的虚拟 DOM 树进行比较,然后只对差异进行更新。它不会直接操作真实的 DOM,而是将更新操作放在一个队列中,并异步执行队列中的所有更新操作。

Vue 的 diff 算法主要有以下几个步骤:

  1. 通过深度优先遍历,比较新旧虚拟 DOM 树的节点,找到需要更新的节点。
  2. 对需要更新的节点进行更深层次的比较,找到具体的差异。
  3. 根据差异类型,进行相应的更新操作。例如,添加、移动或删除节点。
  4. 对于子节点的比较,递归地执行上述步骤。

React 的 diff 算法:

React 的 diff 算法也被称为 "双指针算法" 或 "Fiber 算法"。React 的 diff 算法将新的虚拟 DOM 树和旧的虚拟 DOM 树进行比较,然后只对差异进行更新。与 Vue 不同的是,React 直接操作真实的 DOM,而不是将更新操作放在队列中异步执行。

React 的 diff 算法主要有以下几个步骤:

  1. 对比新旧虚拟 DOM 树的根节点,找到需要更新的节点。
  2. 根据节点类型和 key 属性进行比较,找到具体的差异。
  3. 根据差异类型,进行相应的更新操作。例如,添加、移动或删除节点。
  4. 递归地执行上述步骤,直到完成整个虚拟 DOM 树的比较。

3.vue虚拟dom,react虚拟dom?

Vue 的虚拟 DOM:

Vue 的虚拟 DOM 是基于 渐进式的近似算法 实现的。每个 Vue 组件都有自己的虚拟 DOM 树,当组件的状态发生变化时,Vue 会使用新的状态生成新的虚拟 DOM 树,然后使用 diff 算法比较新旧虚拟 DOM 树的差异,最终只更新需要更新的部分。在更新虚拟 DOM 树时,Vue 会将所有操作都放在一个队列中,然后异步执行队列中的所有更新操作,以提高性能。

React 的虚拟 DOM:

React 的虚拟 DOM 是基于 Fiber 实现的。每个 React 组件也都有自己的虚拟 DOM 树,当组件的状态发生变化时,React 会使用新的状态生成新的虚拟 DOM 树,然后使用 diff 算法比较新旧虚拟 DOM 树的差异,最终只更新需要更新的部分。在更新虚拟 DOM 树时,React 会直接操作真实 DOM,而不是将更新操作放在队列中异步执行。为了避免阻塞 UI 线程,React 使用 Fiber 架构实现了异步渲染,并提供了一种优先级机制,以确保重要的更新优先被执行。

4.怎么实现深浅拷贝?

浅拷贝:浅拷贝只复制对象或数组的第一层内容,而不复制嵌套的子对象或数组。

实现浅拷贝的方法:

  1. 使用 Object.assign() 方法
const newObj = Object.assign({}, obj);
const newArr = Object.assign([], arr);
  1. 使用展开运算符(spread operator)

深拷贝:深拷贝会复制对象或数组的所有层级内容,包括嵌套的子对象或数组。

实现深拷贝的方法:

  1. 递归实现
function deepClone(obj) {
  // 首先判断拷贝的数据类型
  if (typeof obj !== 'object' || obj === null) {
    return obj; // 如果是基本数据类型或null,直接返回
  }

  // 创建一个新的目标对象或数组
  const newObj = Array.isArray(obj) ? [] : {};

  // 递归拷贝对象或数组的属性和元素
  for (let key in obj) {
    newObj[key] = deepClone(obj[key]);
  }

  return newObj;
}
  1. 使用 JSON 对象的 stringify() 和 parse() 方法

但是,需要注意的是,这种方法无法拷贝函数和循环引用的对象,因为 JSON 不支持这些数据类型。

5.es6新特性有哪些?

  1. let 和 const 关键字:用于声明块级作用域变量和常量。
  2. 箭头函数:使用箭头(=>)语法定义函数,可以更简洁地写出函数表达式和匿名函数。
  3. 模板字符串:使用反引号(`)包裹的字符串,可以包含变量和表达式,可以在字符串中换行,提高可读性和代码编写效率。
  4. 解构赋值:可以从数组和对象中解构出变量,使代码更简洁易懂。
  5. 默认参数:函数可以设置默认参数值,当函数调用时不传递参数时,将使用默认参数值。
  6. 展开运算符:使用三个点(...)运算符可以将数组或对象中的元素展开为单独的值。
  7. class 和继承:使用 class 和 extends 关键字定义类和实现继承。
  8. Promise:Promise 是一种处理异步操作的方法,可以更好地处理回调地狱和错误处理。
  9. 模块化:ES6 引入了模块化的语法,使用 import 和 export 关键字可以将代码模块化,提高代码的可维护性和可重用性。
  10. Symbol:Symbol 是一种新的原始数据类型,用于创建唯一的标识符,可以用于对象属性的命名。
  11. Map 和 Set:ES6 引入了 Map 和 Set 数据结构,分别用于存储键值对和一组唯一的值,提供了更灵活的数据操作方式。
  12. Generator:Generator 是一种特殊的函数,可以在执行过程中暂停和恢复,可以用于异步编程和迭代器的实现。

还有一些其他的特性,如 Proxy、Reflect、for...of 循环、字符串和数字的扩展方法等,这些特性一起构成了 ES6 的新特性。

6.async,await和promise有什么区别?

Promise、async/await 是两种处理异步操作的方式,它们与 JavaScript 的回调函数相比,可以更好地处理异步代码,并使其更加易于理解和维护。

Promise 是一种异步操作的表示方法,它可以在异步操作完成后,使用 resolve() 和 reject() 方法返回结果或错误。在使用 Promise 时,可以通过 then() 方法或 catch() 方法处理异步操作的结果或错误。

async/await 是基于 Promise 的语法糖,它让异步代码看起来更像同步代码,使其更易于理解和维护。使用 async/await 时,可以使用 async 关键字定义一个异步函数,然后在函数中使用 await 关键字等待异步操作的结果。在等待异步操作的过程中,async 函数会挂起并等待异步操作完成后再继续执行。在 async 函数中使用 try/catch 语句可以捕获异步操作的错误。

以下是它们之间的区别:

  1. 语法:使用 Promise 时需要创建一个 Promise 实例,并使用 then() 和 catch() 方法处理异步操作的结果或错误;使用 async/await 时,可以使用 async 关键字定义一个异步函数,然后使用 await 关键字等待异步操作的结果。
  2. 错误处理:Promise 使用 then() 方法和 catch() 方法处理异步操作的结果和错误;async/await 使用 try/catch 语句处理异步操作的错误。
  3. 可读性:使用 async/await 可以让异步代码看起来更像同步代码,易于理解和维护;Promise 的语法相对于回调函数来说更加简洁明了,但仍然需要使用 then() 和 catch() 方法。
  4. 功能:Promise 可以链式调用多个异步操作,可以使用 Promise.all() 和 Promise.race() 方法处理多个异步操作的结果;async/await 不能直接链式调用多个异步操作,但可以在 async 函数中调用其他的异步函数。

7.箭头函数在那些场景不能使用?

  1. 作为构造函数:箭头函数没有自己的 this 值,也没有 prototype 属性,因此不能被用作构造函数。
  2. 对象方法:如果要将箭头函数作为对象的方法使用,箭头函数中的 this 值会被绑定到当前作用域中的 this 值,而不是对象本身。
  3. 回调函数中的 this:如果箭头函数作为回调函数使用,箭头函数中的 this 值会被绑定到当前作用域中的 this 值,而不是回调函数所在的对象。
  4. 函数体内使用 arguments 对象:箭头函数没有自己的 arguments 对象,如果需要使用 arguments 对象,可以使用 rest 参数代替。
  5. 事件处理函数:如果将箭头函数作为事件处理函数使用,箭头函数中的 this 值会被绑定到当前作用域中的 this 值,而不是触发事件的元素。

8.有哪些请求方式?

  1. GET:获取资源。使用 GET 请求时,请求参数会被附加到 URL 后面,因此可以被浏览器缓存。GET 请求通常用于获取数据,不应该用于修改数据。
  2. POST:提交数据。使用 POST 请求时,请求参数会被包含在请求体中,因此不会被浏览器缓存。POST 请求通常用于提交数据,例如表单数据或者 JSON 数据。
  3. PUT:更新资源。使用 PUT 请求时,请求参数会被包含在请求体中,用于更新服务器上的资源。
  4. DELETE:删除资源。使用 DELETE 请求时,请求参数会被包含在 URL 中,用于删除服务器上的资源。
  5. HEAD:获取资源头部信息。与 GET 请求类似,但是只获取资源头部信息,不返回实际的资源内容。
  6. OPTIONS:获取资源支持的请求方法。用于查询服务器支持的请求方式,通常在跨域请求时使用。
  7. PATCH:部分更新资源。与 PUT 请求类似,但是只更新部分资源内容。

9.get和post请求有什么不同

  1. 使用方式:GET 请求和 POST 请求的使用方式不同。GET 请求通常用于从服务器获取数据,请求参数会被附加到 URL 的末尾,以 ? 开头,多个参数之间使用 & 连接。POST 请求通常用于向服务器提交数据,请求参数会被包含在请求体中,不会出现在 URL 中。
  2. 请求语义:GET 请求和 POST 请求的请求语义不同。GET 请求表示客户端希望从服务器获取资源,请求参数通常用于限定获取的资源范围或者提供查询条件。POST 请求表示客户端希望向服务器提交数据,请求参数通常用于传递提交的数据。
  3. 安全性:GET 请求和 POST 请求的安全性不同。GET 请求不具有安全性,因为请求参数暴露在 URL 中,容易被其他人窃取和篡改。POST 请求具有一定的安全性,因为请求参数不会被暴露在 URL 中,而是包含在请求体中。
  4. 请求大小:GET 请求和 POST 请求的请求大小有限制。GET 请求对 URL 的长度有限制,因此请求参数的大小也受到限制。POST 请求对请求体的大小有限制,但是相对于 GET 请求而言,请求参数可以更大。

10.什么是服务端渲染?

服务端渲染(SSR)是一种将服务器端的数据和页面模板组合成完整的 HTML 页面并将其发送到客户端的技术。相比于传统的客户端渲染)技术,服务端渲染可以提升首屏渲染速度、SEO 友好、更利于移动设备访问等优点。

服务端渲染的基本思路是,当用户发起请求时,服务器端根据请求的 URL 路径和参数等信息获取到所需的数据,并使用服务器端的模板引擎将数据和 HTML 模板进行组合,生成完整的 HTML 页面,并将其发送到客户端。客户端收到 HTML 页面后,直接进行展示,无需再进行数据获取和页面渲染等操作,从而提高了页面渲染的速度和性能。

11.节流防抖?

  1. 节流:指定时间间隔内只执行一次函数,多次调用函数会在时间间隔内合并为一次调用,适用于需要频繁触发的事件,例如页面滚动、鼠标移动、窗口大小变化等。
  2. 防抖:等待指定时间间隔后执行函数,如果在等待时间间隔内再次触发事件,则重新计时,适用于用户输入、搜索框输入等场景,可以避免频繁的 API 请求。

下面分别介绍一下节流和防抖的实现方法:

  1. 节流实现方法:
function throttle(func, wait) {
  let timer = null;
  return function() {
    const context = this;
    const args = arguments;
    if (!timer) {
      timer = setTimeout(function() {
        func.apply(context, args);
        timer = null;
      }, wait);
    }
  }
}
  1. 防抖实现方法:
function debounce(func, delay) {
  let timerId;

  return function(...args) {
    clearTimeout(timerId);

    timerId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

12.语义化标签有哪些?

  1. <header>:表示文档的页眉,通常包含网站的标题、导航栏等内容。
  2. <nav>:表示文档的导航部分,通常包含网站的导航链接。
  3. <main>:表示文档的主要内容部分,通常包含页面的核心内容。
  4. <article>:表示一个独立的、完整的内容单元,通常包含一篇文章、一段新闻等内容。
  5. <aside>:表示与主要内容相关的辅助信息,通常包含侧边栏、广告、相关文章等内容。
  6. <section>:表示文档中的一个区块,通常包含一组相关的内容。
  7. <footer>:表示文档的页脚,通常包含版权信息、联系方式等内容。
  8. <h1> ~ <h6>:表示标题,通常用于标识文档的章节和子章节。
  9. <p>:表示段落,通常用于显示一段文字内容。
  10. <ul>、<ol>、<li>:表示无序列表、有序列表和列表项。

使用语义化标签可以提高文档的可读性和可维护性,同时也可以为搜索引擎优化和无障碍阅读提供帮助。

13.怎么让一个元素水平垂直居中

可以使用 CSS 实现让一个元素水平垂直居中,以下是几种常用的方法:

  1. 使用 flexbox 布局:

设置父容器的 display 属性为 flex,并设置 align-items: center 和 justify-content: center,即可让子元素水平垂直居中。

.container {
  display: flex;
  align-items: center;
  justify-content: center;
}
  1. 使用绝对定位:设置子元素的 position 属性为 absolute,并设置 top: 50% 和 left: 50%,然后再设置 transform: translate(-50%, -50%),即可实现水平垂直居中。
  2. 使用表格布局:设置父容器的 display 属性为 table,并设置子元素的 display 属性为 table-cell,然后再设置 vertical-align: middle 和 text-align: center,即可实现水平垂直居中。

以上三种方法都可以实现让一个元素水平垂直居中,具体使用哪种方法,可以根据具体的需求和场景来选择。

14.usestate是同步还是异步

在 React 中,useState 是一个异步函数,它并不是同步更新状态的。当我们调用 useState 修改状态时,React 并不会立即更新组件的状态,而是将状态更新请求添加到更新队列中,等到合适的时机再进行更新,这个过程是异步进行的。

调用 useState 修改状态时,React 会将更新请求添加到更新队列中,并且会在后续的更新过程中进行批量处理,以提高性能。在进行批量处理时,React 会对多个状态更新请求进行合并,然后进行一次更新,这个过程是异步的,因此可能会存在一定的延迟。但是,由于 React 内部对状态更新进行了封装和优化,因此我们通常不需要考虑异步更新状态的具体实现细节,只需要在逻辑上认为 useState 是同步更新状态的就可以了。

15.大屏数据可视化项目屏幕怎么适配?

以下是几种常用的屏幕适配方法:

  1. 百分比布局:使用百分比进行布局,根据父容器的尺寸自适应缩放。例如,可以将父容器的宽度设置为 100%,然后将子元素的宽度和高度设置为百分比值,以适应不同的屏幕尺寸。
  2. rem 布局:使用 rem 单位进行布局,根据根元素字体大小自适应缩放。可以将根元素的字体大小设置为屏幕宽度的一定比例,然后使用 rem 单位进行布局,以适应不同的屏幕尺寸。
  3. vw/vh 布局:使用 vw/vh 单位进行布局,根据视口尺寸自适应缩放。可以使用 vw/vh 单位进行布局,以适应不同的视口尺寸。
  4. 响应式布局:使用 CSS3 的媒体查询功能,根据不同的屏幕尺寸和分辨率,选择不同的布局方案。例如,可以使用媒体查询选择不同的样式表或设置不同的样式属性,以适应不同的屏幕尺寸和分辨率。

需要注意的是,在大屏数据可视化项目中,一般会使用多种布局方式进行组合,以实现更加灵活和自适应的效果。另外,为了提高性能和用户体验,还可以使用前端性能优化的技术,如懒加载、图片压缩、缓存等。

16.ECharts怎么适配?

在 ECharts 中,可以通过以下方式进行适配:

  1. 自适应容器大小:ECharts 提供了 resize 方法,可以在容器大小发生变化时调用该方法,使图表自适应容器大小。可以通过监听窗口大小变化事件或者使用定时器等方式来触发 resize 方法。
  2. 使用响应式布局:ECharts 支持响应式布局,可以通过设置 grid 组件的属性来实现。例如,可以设置 grid 组件的 containLabel 属性为 true,使标签在容器内部居中显示。
  3. 使用多个图表:可以将一个页面分成多个区域,每个区域展示不同的图表,以适应不同的屏幕尺寸和分辨率。例如,可以使用 Bootstrap 等前端框架将页面分成多个列,然后在每个列中展示不同的图表。
  4. 使用响应式图表:ECharts 还提供了多个响应式图表,例如饼图、折线图等,可以根据屏幕尺寸和分辨率自适应缩放。例如,可以使用饼图来展示数据占比,当屏幕尺寸较小时,可以将饼图改为环形图,以便更好地展示数据。
  5. 使用响应式交互功能:ECharts 还提供了多种响应式交互功能,例如缩放、拖拽等,可以根据屏幕尺寸和分辨率自适应缩放和移动。例如,在地图中展示数据时,可以使用缩放和拖拽等功能来查看不同区域的数据。

ECharts 提供了多种适配方法,可以根据不同的需求和场景进行选择和组合,以实现更加灵活和自适应的效果。

17.使用ECharts怎么渲染十万条数据?

在使用 ECharts 渲染大量数据时,通常会遇到性能问题。以下是几种常用的优化方法:

  1. 数据预处理:对于大量数据,可以在前端进行数据预处理,例如对数据进行采样、过滤、聚合等操作,从而减少需要渲染的数据量。
  2. 数据分片:将大量数据分成多个小块,每次渲染一部分数据,以减少一次性渲染大量数据的压力。
  3. 使用 Web Worker:将数据处理和图表渲染放在 Web Worker 中进行,从而不会阻塞主线程,提高渲染效率。
  4. 禁用动画:对于大量数据,通常不需要使用动画效果,可以通过设置 animation 属性为 false 来禁用动画,从而提高渲染效率。
  5. 使用 Canvas 渲染:ECharts 支持使用 Canvas 渲染,可以通过设置 renderer 属性为 'canvas' 来启用 Canvas 渲染,从而提高渲染效率。
  6. 使用大数据量专用的图表类型:ECharts 提供了多个大数据量专用的图表类型,例如散点图、热力图等,可以通过选择适合的图表类型来提高渲染效率。
  7. 延迟渲染:对于需要交互的图表,可以通过延迟渲染的方式来提高渲染效率。例如,在用户进行交互操作时再渲染图表,而不是在页面加载时就渲染图表。

总之,ECharts 渲染大量数据时需要综合考虑多种因素,包括数据量、数据复杂度、交互需求等,选择适合的优化方法,从而实现更高效的渲染效果。

18.js闭包理解优点和缺点?

闭包可以让函数访问外部函数中定义的变量和参数,即使外部函数已经返回,闭包仍然可以访问这些变量和参数。

优点:

  1. 保护变量:闭包可以保护函数内的变量,防止其被外部访问和修改。这种特性使得闭包非常适合用于实现模块化,可以将变量和函数封装在闭包中,只对外暴露需要的接口。
  2. 实现高阶函数:闭包可以用来实现高阶函数,即一个函数返回另一个函数。高阶函数可以简化代码,提高代码的复用性。
  3. 实现函数柯里化:闭包可以用来实现函数柯里化,即将一个接受多个参数的函数转换为接受单个参数的函数序列,这种技术可以使代码更加简洁和易于维护。

3.1函数柯里化是一种将接受多个参数的函数转换为一系列只接受单个参数的函数的技术。通过函数柯里化,我们可以将一个接受 n 个参数的函数转换为 n 个只接受一个参数的函数,这些函数可以依次调用,最终完成原函数的调用。

缺点:

  1. 内存占用:闭包会占用更多的内存,因为它会保留外部函数的变量和参数,导致内存泄漏和性能问题。如果闭包不再被使用,应该手动释放它占用的内存,比如将闭包变量设置为null。
  2. 安全问题:闭包可能会导致安全问题,因为它可以访问外部函数中的变量和参数,如果这些变量和参数包含敏感信息,可能会造成信息泄露和攻击风险。
  3. 可读性问题:使用闭包可能会降低代码的可读性和可维护性,因为它增加了代码的复杂性和难度。在使用闭包时,应该注意代码的规范和标准,保证代码的可读性和可维护性。

19.vue权限验证?

  1. 路由守卫:通过在路由配置中添加meta字段来存储当前页面需要的权限信息,然后在路由跳转时通过beforeEach钩子函数进行权限验证。如果当前用户没有访问该页面的权限,则跳转到登录页面或其他提示页面。
  • 在路由守卫中实现权限验证的步骤如下:
  • 定义路由守卫:在路由配置中定义路由守卫,可以使用 beforeEach 或 beforeResolve 方法来定义路由守卫。
  • 获取用户信息:在路由守卫中获取用户信息,例如从本地存储或服务器获取用户信息。
  • 验证用户权限:根据用户信息验证用户权限,例如判断用户的角色、权限等。
  • 跳转到对应页面:根据用户权限决定是否跳转到对应页面,可以使用 next 方法来实现跳转。

  1. Vuex:通过在Vuex中存储用户信息及权限等相关信息,在组件中通过计算属性或者方法判断当前用户是否具有访问该组件的权限,如果没有则禁止访问。
  • 在 Vuex 中实现权限验证的步骤如下:
  • 定义状态:在 Vuex 的 state 中定义用户信息、权限信息等状态。
  • 定义 actions:在 Vuex 的 actions 中定义获取用户信息、验证用户权限等操作。可以通过调用后端API来获取用户信息,也可以从本地存储中获取。
  • 定义 mutations:在 Vuex 的 mutations 中定义修改用户信息、权限信息等状态的方法。
  • 在组件中调用:在需要验证权限的组件中调用 actions 中的方法来获取用户信息并验证权限。

  1. 后端接口:在后端接口中进行权限验证,通过在请求头中添加token等身份信息判断当前用户是否具有访问该接口的权限,如果没有则返回相应的错误信息。
  2. 自定义指令:通过自定义指令来控制页面元素的显示和隐藏。根据当前用户的权限信息,指令会动态地添加或移除元素的v-show或v-if指令,以实现页面元素的动态控制。
  3. mixin混入:通过在mixin中定义一些公共的权限验证逻辑,然后在组件中使用mixins选项来引入mixin,以实现权限验证的复用。

以上几种Vue权限验证方式都可以在实际项目中使用,具体实现方式根据具体项目需求和开发经验而定。

20.Vue组件之间的通信方式都有哪些?

  1. Props 和 $emit:父组件通过 props 属性向子组件传递数据,子组件通过 $emit 方法触发一个事件,向父组件传递数据。
  2. $parent 和 $children:可以通过 $parent 和 $children 访问父组件和子组件的实例,从而实现组件之间的通信。
  3. $refs:可以使用 $refs 来访问组件实例,从而实现组件之间的通信。
  4. Event Bus:通过一个空的 Vue 实例作为中央事件总线,用它来触发事件和监听事件,从而实现任意组件之间的通信。
  5. Vuex:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,使得不同组件之间可以共享状态,从而实现组件之间的通信。
  6. Provide 和 Inject:父组件通过 provide 属性向子孙组件传递数据,子孙组件通过 inject 属性接收数据,从而实现组件之间的通信。
  7. $attrs 和 $listeners:可以使用 $attrs 和 $listeners 来访问组件的属性和事件,从而实现组件之间的通信。

$bus:可以使用 $bus 来创建一个全局的事件 总线,从而实现组件之间的通信。

  1. Mixin:可以使用 Mixin 将一些通用的逻辑和方法混入到多个组件中,从而实现组件之间的通信。
  2. Render Props:可以使用 Render Props 在组件之间共享逻辑和状态,从而实现组件之间的通信。

21.vue生命周期

Vue的生命周期是指组件在经历创建、更新和销毁过程中所经历的一系列钩子函数。这些钩子函数可以让开发者在特定的阶段执行自定义的逻辑。

Vue的生命周期可以分为8个阶段,分别是:

  1. beforeCreate:在实例被创建之前执行,此时数据观测和初始化都未开始。
  2. created:在实例被创建之后执行,此时已完成数据观测、属性和方法的运算,但尚未开始DOM编译和挂载。
  3. beforeMount:在挂载之前被调用,此时已完成模板编译,但尚未开始挂载。
  4. mounted:在挂载之后被调用,此时已经完成了DOM的挂载,可以进行DOM操作。
  5. beforeUpdate:在更新之前被调用,此时数据已经被更新,但DOM尚未重新渲染。
  6. updated:在更新之后被调用,此时数据和DOM都已经更新完成。
  7. beforeDestroy:在实例销毁之前调用,此时实例仍然可用。
  8. destroyed:在实例销毁之后调用,此时实例已经被销毁,所有的事件监听和子实例都已经被移除。

22.http与https区别

HTTP(Hypertext Transfer Protocol)和HTTPS(Hypertext Transfer Protocol Secure)都是用于客户端和服务器之间进行通信的协议,但是它们之间有几个重要的区别。

  1. 安全性:HTTP是明文传输,数据在传输过程中容易被窃听和篡改,而HTTPS采用了SSL/TLS协议进行加密传输,数据传输过程中被加密,安全性更高。
  2. 端口号:HTTP默认使用80端口,HTTPS默认使用443端口。
  3. 证书:HTTPS需要使用SSL证书,证书中包含了网站的信息和公钥等,用于加密和解密数据。
  4. 速度:由于HTTPS需要进行加密解密过程,所以相比HTTP来说速度会略慢一些。
  5. 缓存:HTTP可以被缓存,而HTTPS不太容易被缓存。

总之,HTTPS比HTTP更加安全,但是会稍微降低一些速度。在需要保护用户隐私和敏感数据的场景下,应该使用HTTPS。

23.vue性能优化方案

  1. 使用异步组件和路由懒加载:将页面中不必要的组件和模块进行异步加载,提高首屏加载速度。
  2. 减少不必要的计算和渲染:避免在模板中使用过于复杂的计算和渲染逻辑,减少不必要的渲染,提高页面渲染速度。
  3. 使用虚拟滚动:对于大量的列表数据,使用虚拟滚动技术,只渲染可视区域的数据,减少不必要的渲染和计算。
  4. 使用keep-alive缓存组件:对于需要频繁切换的组件,可以使用keep-alive缓存组件,避免重复渲染和计算,提高性能。
  5. 使用Vue的内置优化:Vue提供了一些内置的优化选项,如v-if和v-for的key属性,可以避免不必要的重新渲染和计算。
  6. 使用CDN加速:使用CDN可以加速静态资源的加载速度,减少页面加载时间。
  7. 使用Webpack等构建工具进行打包优化:对于项目中的静态资源,可以使用Webpack等构建工具进行打包优化,减少文件大小,提高页面加载速度。
  8. 避免频繁的DOM操作:频繁的DOM操作会导致页面重排和重绘,影响页面性能,应避免频繁的DOM操作。
  9. 路由懒加载:对于大型的单页面应用,使用Vue Router的懒加载功能,按需加载路由组件,减少初始加载的资源体积。

24. Vue响应式原理


Vue的响应式原理是指Vue如何实现数据双向绑定和自动更新视图的机制。核心概念是Vue使用了一种名为"依赖追踪"的技术,通过建立数据与视图之间的关联关系,实现数据的变化能够自动更新到视图,而视图的变化也能够反映到数据上。

具体实现如下:

  1. 数据劫持:Vue使用了ES5的Object.defineProperty方法来劫持数据对象的属性。通过将属性的get和set方法进行重定义,实现在数据访问和赋值时触发对应的操作。
  2. 监听器:Vue在数据劫持的过程中,为每个属性创建了一个监听器(Watcher)对象。监听器负责收集依赖(即视图中使用该属性的地方),当属性发生变化时,通知依赖进行更新。
  3. 依赖收集:在模板编译过程中,Vue会解析模板中的指令、表达式等,建立起依赖关系图。当访问数据时,Watcher会将自身添加到对应数据属性的依赖列表中,以便在属性变化时能够通知到相关的依赖进行更新。
  4. 响应式更新:当数据发生变化时,触发对应属性的set方法。在set方法中,Watcher会通知相关的依赖进行更新,从而触发视图的重新渲染。Vue使用了异步更新队列,将多次数据变化的更新合并成一次,以提高性能。

总结:Vue的响应式原理是通过数据劫持和依赖追踪来实现数据与视图的自动双向绑定。Vue使用Object.defineProperty方法劫持数据对象的属性,创建监听器对象,以收集依赖和触发更新。在模板编译过程中建立依赖关系图,当数据发生变化时,触发对应属性的set方法,通知相关依赖进行更新,进而重新渲染视图。这种响应式机制使得开发者能够更专注于数据处理和业务逻辑,无需手动操作DOM来保持数据和视图的同步,提高了开发效率和代码的可维护性。

data为什么是个函数?

当data选项是一个对象时,该对象会被多个组件实例共享,这样在一个组件实例中修改数据会影响到其他实例,从而导致数据状态混乱和不可预料的行为。

为了避免这种情况,Vue要求data选项必须是一个函数。每个组件实例在创建时会调用该函数,返回一个独立的数据对象。这样每个组件实例都会拥有自己的数据对象,它们之间相互独立且互不干扰。

使用函数返回data的形式有以下几个好处:

  1. 数据隔离:每个组件实例拥有独立的数据对象,避免了数据之间的冲突和混乱。
  2. 可复用性:组件可以复用,每个实例都可以根据自身的需求返回不同的初始数据。
  3. 数据动态更新:每次调用data函数时都可以根据需要动态生成数据,例如从服务器获取最新的数据。

通过将data选项设为函数,Vue能够实现组件实例之间数据的独立性和隔离性,保证每个组件实例都具有独立的数据对象,从而提高了组件的可维护性和可复用性。

Vue3的响应式数据原理是使用Proxy实现的。Proxy是ECMAScript 6中新增的特性,它可以拦截并修改对象的操作。Vue3在创建组件实例时,会使用Proxy对组件的数据进行代理,当数据发生变化时,Proxy会自动触发更新视图的操作。

具体来说,Vue3会将组件的data、computed、methods等属性值进行响应式处理。当数据发生变化时,Proxy会拦截并触发依赖该数据的所有组件进行更新。同时,Vue3还对数组和对象进行了优化,对于数组的修改操作,Vue3会使用封装后的方法进行代理,使得数组的响应式更新更加高效。

25.浏览器渲染机制

浏览器渲染机制是指浏览器将HTML、CSS和JavaScript代码转化为可视化的网页的过程。其主要步骤如下:

  1. 解析HTML文件,构建DOM树。浏览器将HTML文件解析成DOM树,DOM树是由节点构成的树状结构,每个节点代表HTML中的一个元素或标签。
  2. 解析CSS文件,构建CSSOM树。浏览器将CSS文件解析成CSSOM树,CSSOM树也是由节点构成的树状结构,每个节点代表CSS中的一个样式规则。
  3. 将DOM树和CSSOM树结合,生成渲染树。浏览器将DOM树和CSSOM树结合起来,生成渲染树,渲染树只包含需要显示的节点和这些节点的样式信息。
  4. 布局渲染树。浏览器会根据渲染树的结构和样式信息,计算每个节点在屏幕上的位置和大小。
  5. 绘制渲染树。浏览器会遍历渲染树,将每个节点绘制到屏幕上,最终呈现给用户。
  6. 不断重绘和合成。当渲染树中的节点发生变化,浏览器会重绘这些节点,并将它们合成到屏幕上,以保持页面的实时更新。

以上就是浏览器渲染机制的主要步骤,其中每个步骤都会影响页面的性能和加载速度,因此优化这些步骤是提高页面性能的关键。

26.当在浏览器中输入 Google.com 并且按下回车之后发生了什么?

(1)解析URL: 首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。

(2)缓存判断: 浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。

(3)DNS解析: 下一步首先需要获取的是输入的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则使用,如果没有则向本地 DNS 服务器发起请求。本地 DNS 服务器也会先检查是否存在缓存,如果没有就会先向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用户。用户向本地 DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。

(4)获取MAC地址: 当浏览器得到 IP 地址后,数据传输还需要知道目的主机 MAC 地址,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。通过将 IP 地址与本机的子网掩码相与,可以判断是否与请求主机在同一个子网里,如果在同一个子网里,可以使用 APR 协议获取到目的主机的 MAC 地址,如果不在一个子网里,那么请求应该转发给网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机的 MAC 地址应该为网关的地址。

(5)TCP三次握手: 下面是 TCP 建立连接的三次握手的过程,首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接收到请求后向服务器端发送一个 SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个ACK 确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。

(6)HTTPS握手: 如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。这个时候双方都有了三个随机数,按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。

(7)返回数据: 当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接收到响应后,开始对 html 文件进行解析,开始页面的渲染过程。

(8)页面渲染: 浏览器首先会根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示出来了。

(9)TCP四次挥手: 最后一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。

从输入URL到页面加载完成的主要流程如下:
  1. 用户输入URL:用户在浏览器地址栏中输入要访问的网站的URL。
  2. DNS解析:浏览器首先将URL中的域名解析为对应的IP地址。这涉及向DNS服务器发送DNS查询请求,以获取域名对应的IP地址。
  3. 建立TCP连接:浏览器使用HTTP协议通过TCP/IP建立与目标服务器的连接。这需要进行三次握手来确保双方的连接可靠。
  4. 发送HTTP请求:建立TCP连接后,浏览器向目标服务器发送HTTP请求。请求中包含请求的方法(GET、POST等)、URL、请求头信息和可选的请求体。
  5. 服务器处理请求:目标服务器接收到HTTP请求后,根据请求的URL和其他信息来处理请求。这可能涉及路由解析、数据查询、处理业务逻辑等操作。
  6. 服务器返回HTTP响应:服务器根据请求处理的结果生成HTTP响应,包括状态码、响应头信息和响应体。响应体中通常包含请求的资源,如HTML、CSS、JavaScript等文件。
  7. 浏览器接收HTTP响应:浏览器接收到来自服务器的HTTP响应后,开始解析响应。解析过程包括检查状态码、解析响应头信息和获取响应体。
  8. 渲染页面:浏览器根据接收到的响应内容开始解析HTML,并构建DOM树。同时,解析过程中会下载和解析CSS文件、JavaScript文件等,以构建渲染树。
  9. 页面布局和绘制:浏览器根据DOM树和渲染树进行页面布局和绘制。这包括计算元素的位置、大小、样式等,以及将渲染结果绘制到屏幕上。
  10. 加载并执行JavaScript:在页面渲染过程中,如果遇到JavaScript代码,浏览器会下载并执行这些代码。JavaScript的执行可能会修改DOM结构、更新页面内容或执行其他操作。
  11. 页面加载完成:当页面的DOM树、渲染树和JavaScript执行完成后,页面加载过程就算完成了。此时,页面上的内容将完全呈现给用户。
     
TCP三次握手和TCP四次挥手有什么区别?
  1. 三次握手(TCP连接建立):三次握手是为了确保客户端和服务器都能够相互确认对方的可达性和接收能力,并同意建立连接。
    • 第一步:客户端发送一个带有SYN标志的包给服务器,表示请求建立连接。
    • 第二步:服务器接收到请求后,发送一个带有SYN/ACK标志的包给客户端,表示确认请求,并告知客户端准备好接收数据。
    • 第三步:客户端收到服务器的确认后,再发送一个带有ACK标志的包给服务器,表示客户端确认连接建立。
  1. 四次挥手(TCP连接终止):四次挥手是为了保证双方都能正确地关闭连接,避免数据丢失或断开连接的不完整性。
    • 第一步:当客户端需要关闭连接时,发送一个带有FIN标志的包给服务器,表示不再发送数据。
    • 第二步:服务器收到FIN后,发送一个带有ACK标志的包给客户端,表示收到关闭请求。
    • 第三步:服务器关闭与客户端的连接,并发送一个带有FIN标志的包给客户端,表示服务器也不再发送数据。
    • 第四步:客户端收到服务器的关闭请求后,发送一个带有ACK标志的包给服务器,表示确认关闭请求,并关闭连接。

总结:

  • 三次握手是建立连接时的过程,确保双方都能够确认对方的可达性和接收能力,并同意建立连接。
  • 四次挥手是断开连接时的过程,确保双方都能正确地关闭连接,避免数据丢失或断开连接的不完整性。

27.项目优化方案

  1. 代码优化:
    • 优化算法和数据结构,减少不必要的计算和内存消耗。
    • 减少代码冗余,提取重复代码为函数或组件。
    • 使用合适的数据缓存策略,避免重复获取和计算数据。
    • 使用合适的代码压缩工具,减小代码体积,提升加载速度。
  1. 页面加载优化:
    • 减少HTTP请求,合并和压缩静态资源文件。
    • 使用CDN加速静态资源的加载。
    • 延迟加载非关键资源,如图片懒加载。
    • 使用浏览器缓存策略,减少重复请求。
  1. 前端性能优化:
    • 使用合适的图片格式和压缩技术,减小图片大小。
    • 避免过多的DOM操作和重绘,优化页面渲染性能。
    • 使用虚拟列表或分页加载,处理大量数据的展示。
    • 合理使用异步操作,避免阻塞主线程。
  1. 数据请求优化:
    • 合并网络请求,减少请求数量。
    • 使用缓存策略,避免重复请求相同的数据。
    • 对数据进行压缩和分页处理,减小数据传输量。
  1. 移动端优化:
    • 使用合适的移动端框架和组件库,提升页面性能和用户体验。
    • 使用GPU加速动画效果,提高动画流畅度。
    • 避免使用过多的占用CPU的特性,如复杂的阴影效果和动态模糊。

28.hash和history

hash 和 history 是两种前端路由模式,用于在单页面应用中管理页面的导航和URL变化。

  1. Hash 模式:
    • 在 URL 中使用 # 符号来表示路由,例如:http://example.com/#/home。
    • Hash 模式的路由变化不会导致页面的完全刷新,而只是改变 URL 中的 hash 部分。
    • 通过监听 hashchange 事件来响应路由的变化。
    • Hash 模式的优点是兼容性好,支持在不同浏览器和服务器环境下使用。缺点是 URL 中带有 # 符号,不够美观。
  1. History 模式:
    • 使用 HTML5 的 history API 来管理页面的导航,例如:http://example.com/home。
    • History 模式通过修改 URL 的路径部分来表示路由,可以使用更友好的 URL。
    • 使用 pushState 或 replaceState 方法来改变 URL,不会导致页面的刷新。
    • 服务器需要正确配置,以便在刷新页面时正确响应对应的路由。
    • 通过监听 popstate 事件来响应路由的变化。
    • History 模式的优点是 URL 更美观,不带有 # 符号,缺点是需要服务器支持,并且在某些环境下可能出现问题。

在 Vue Router 中,可以通过配置 mode 属性来选择使用 Hash 模式还是 History 模式,默认为 Hash 模式。

29.sort排序,从大到小,从小到大

sort 是 JavaScript 中的一个数组方法,用于对数组进行排序。sort 方法会原地修改数组,不会创建新的数组。

sort 方法可以接受一个可选的比较函数作为参数,用于定义排序的规则。如果没有提供比较函数,sort 方法会将数组元素默认转换为字符串,并按照 Unicode 顺序进行排序。

下面是一些常见的使用方式和示例:

  1. 默认排序:
const numbers = [5, 2, 8, 1, 9];
numbers.sort();
console.log(numbers); // [1, 2, 5, 8, 9]

2.使用比较函数进行排序:

const numbers = [5, 2, 8, 1, 9];
numbers.sort((a, b) => a - b); // 升序排序
console.log(numbers); // [1, 2, 5, 8, 9]

numbers.sort((a, b) => b - a); // 降序排序
console.log(numbers); // [9, 8, 5, 2, 1]

30.MVVM和MVC有什么区别

在 MVVM 中,View 和 ViewModel 是双向绑定的,当 ViewModel 的数据发生变化时,会自动更新 View;当用户与 View 交互时,ViewModel 会处理相应的逻辑和更新数据。

在 MVC 中,View 和 Controller 之间通过事件和命令的方式进行交互,Controller 接收用户的输入并处理逻辑,然后更新数据和通知 View 更新界面。

MVVM 和 MVC 有一些相似之处,但也有一些区别:

  • MVVM 强调数据驱动的双向绑定,通过 ViewModel 将数据和视图绑定在一起,使得数据的变化可以自动反映在视图上。
  • MVC 使用事件和命令的方式进行交互,Controller 负责控制和管理视图的更新和数据的传递。
  • MVVM 更适合前端开发,特别是响应式的数据驱动开发,适用于构建复杂的交互界面。
  • MVC 更通用,适用于各种类型的应用程序,可以用于后端开发和前端开发。

31.首屏优化

  1. 压缩和合并资源:减小 CSS、JavaScript 和图像等文件的大小,通过压缩和合并减少网络请求的数量,从而加快页面加载速度。
  2. 延迟加载非关键资源:将不是首屏必需的资源,如图片、广告等,进行延迟加载,等待页面主要内容加载完成后再加载这些资源,避免阻塞首屏渲染。
  3. 预加载关键资源:使用预加载(prefetching)或预渲染(prerendering)技术,在首屏加载完成后,提前加载下一个页面可能需要的资源,以减少后续页面的加载时间。
  4. 优化关键渲染路径:通过将关键资源放在 HTML 中靠前的位置,优化 CSS 和 JavaScript 的加载顺序,以最小化渲染阻塞,快速呈现首屏内容。
  5. 使用浏览器缓存:合理设置资源的缓存策略,使得重复访问的资源可以从本地缓存加载,减少网络请求。
  6. 懒加载和分片加载:将页面内容进行拆分,按需加载,延迟加载不可见区域的内容,提高首屏的加载速度。
  7. 图片优化:使用适当的图像格式和压缩算法,减小图像文件的大小,同时保持足够的视觉质量。使用响应式图片,在不同设备上加载适合的图像。
  8. 避免阻塞渲染的脚本:将阻塞渲染的 JavaScript 移至页面底部,或使用 async 或 defer 属性,使其在后台加载,不阻塞首屏的渲染。
  9. 服务器端渲染(SSR):对于需要动态生成内容的页面,考虑使用服务器端渲染技术,将部分渲染工作提前在服务器端完成,加快页面的呈现速度。
  10. 使用 CDN 加速:将静态资源部署到全球分布的 CDN(内容分发网络)上,加快资源的加载速度,提供更好的用户体验。

32.keep-alive

<keep-alive> 是 Vue.js 的一个内置组件,用于缓存和复用组件实例,以提高性能和响应速度。它可以包裹动态组件,并在组件切换时保留组件的状态,避免重复创建和销毁组件。

使用 <keep-alive> 组件非常简单,只需将需要缓存的组件包裹在 <keep-alive> 标签内即可。

被 <keep-alive> 缓存的组件会有一些生命周期钩子函数被调用。具体而言,当一个组件被缓存后,它的 activated 钩子函数会被调用,表示组件被激活;而在组件离开缓存时,deactivated 钩子函数会被调用,表示组件被停用。你可以在这些钩子函数中执行相应的操作,如初始化数据、加载资源等。

<keep-alive> 组件是 Vue.js 提供的一种优化手段,用于缓存和复用组件实例,以提高性能和响应速度。它对于频繁切换的组件或需要保持状态的组件尤其有用。通过合理地使用 <keep-alive>,你可以优化Vue.js 应用的性能和用户体验。

33.路由导航守卫

Vue Router 提供了三种类型的导航守卫:

  1. 全局导航守卫:
    • beforeEach(to, from, next):在每个路由切换前执行,可以用来进行全局的前置验证或跳转逻辑。
    • afterEach(to, from):在每个路由切换后执行,可以用来进行全局的后置处理或统计分析。
  1. 路由独享的守卫:
    • beforeEnter(to, from, next):在某个具体路由配置中定义的守卫,只对该路由生效。
  1. 组件内的守卫:
    • beforeRouteEnter(to, from, next):在进入路由对应组件之前执行,可以访问不到组件实例,因此不能直接访问组件的数据和方法。
    • beforeRouteUpdate(to, from, next):在当前路由复用组件时执行,例如在动态路由参数发生变化时。
    • beforeRouteLeave(to, from, next):在离开路由对应组件之前执行,可以用来询问用户是否保存表单数据或做一些其他确认操作。

这些导航守卫可以用来进行诸如身份验证、权限控制、页面跳转等操作。通过在导航守卫中调用 next 方法,你可以决定是否继续导航、导航到其他路由或取消导航。

34.回流和重绘


回流(Reflow)和重绘(Repaint)是浏览器渲染页面时的两个关键概念。

回流指的是浏览器根据 DOM 结构和样式计算元素的位置和大小,并确定页面的布局。当页面的布局发生变化时,浏览器需要重新计算和渲染受影响的部分,这个过程称为回流。回流是比较耗费性能的操作,因为它会触发页面重新布局和重新绘制。

重绘指的是浏览器根据元素的样式进行绘制,将元素呈现在屏幕上。当元素的样式发生变化但不影响其布局时,浏览器只需要重新绘制受影响的部分,这个过程称为重绘。相比回流,重绘的性能开销较小。

回流和重绘的触发条件:

  1. 回流的触发条件:
    • 添加、删除、修改 DOM 元素的结构或内容
    • 修改元素的样式(如宽度、高度、边距等)
    • 修改页面的布局属性(如改变窗口大小、滚动页面等)
  1. 重绘的触发条件:
    • 修改元素的样式(如颜色、背景色、边框等)
    • 元素的可见性变化(如使用 display: none)

优化回流和重绘的方法:

  1. 避免频繁访问和修改布局信息:尽量一次性获取或修改 DOM 元素的样式或布局属性,避免多次操作导致多次回流。
  2. 使用文档片段(Document Fragment)进行多个元素的批量插入:将要插入的多个元素先添加到文档片段中,再将文档片段一次性插入到文档中,减少回流次数。
  3. 使用 display: none 隐藏元素:相比修改元素的可见性,使用 display: none 可以避免重绘,减少性能开销。
  4. 使用 CSS3 动画或转换:利用硬件加速(如使用 GPU)来优化动画效果,减少回流和重绘的开销。
  5. 使用批量修改样式的技术:例如使用 CSS 类名切换样式,而不是直接修改元素的样式属性。
  6. 避免在循环中频繁操作样式或布局:将需要修改的元素缓存起来,在循环结束后一次性进行修改,减少回流次数。

35.set

Set 具有以下特点:

  1. 唯一性:Set 中的值是唯一的,不会重复出现。
  2. 无序性:Set 中的值没有固定的顺序,不会按照插入的顺序存储。

Set 的常用方法包括:

  • add(value): 向 Set 中添加一个值。
  • delete(value): 删除 Set 中的一个值。
  • has(value): 判断 Set 中是否存在指定的值。
  • clear(): 清空 Set 中的所有值。
  • size: 返回 Set 中值的数量。

Set 是一种用于存储唯一值的数据结构,在处理数据时非常有用。它提供了添加、删除、判断存在等基本操作,并且可以迭代值的顺序。在需要处理唯一值的场景下,使用 Set 可以简化代码,并提高性能。

36.判断数据类型的方法

  1. typeof 操作符:typeof 操作符可以返回一个值的基本数据类型。
    1. 需要注意的是,typeof null 返回的是 "object",这是一个历史遗留问题。
  1. instanceof 操作符:instanceof 操作符用于检查对象是否是特定类的实例。
    1. instanceof 可以判断一个对象是否是某个类的实例,但不能用于判断基本数据类型。
  1. Object.prototype.toString.call() 方法:使用 Object.prototype.toString.call() 方法可以返回一个对象的具体类型。
    1. Object.prototype.toString.call() 方法返回的是一个字符串,表示对象的具体类型。
  1. Array.isArray() 方法:Array.isArray() 方法用于判断一个值是否是数组。
    1. Array.isArray() 方法可以准确地判断一个值是否为数组。

37.BFC,即块级格式化上下文

BFC 的应用场景:

  1. 清除浮动:创建一个父级 BFC 元素,可以阻止浮动元素导致的高度塌陷问题。
  2. 防止边距重叠:相邻的两个块级元素如果位于不同的 BFC 区域中,它们的垂直边距不会发生重叠。
  3. 创建自适应的布局:BFC 元素可以根据浮动元素的大小进行布局,适用于实现自适应的多列布局。
  4. 防止浮动元素覆盖:BFC 元素可以阻止浮动元素覆盖其内部的内容。

BFC 是一种用于描述块级元素布局和渲染行为的概念。它具有一些特性和应用场景,通过创建 BFC 可以解决一些常见的布局问题,如清除浮动、防止边距重叠等。

38.重构


重构(Refactoring)是指对现有代码进行优化和改进,以改善代码的可读性、可维护性、性能或扩展性,而不会改变其外部行为。重构是一种迭代的过程,通过一系列小的改动来逐步改进代码的结构和设计,使代码更加健壮、可理解和易于修改。

重构的目的包括:

  1. 提高代码质量:通过优化代码结构和设计,使代码更加清晰、简洁、可读,降低代码的复杂性和维护成本。
  2. 增加可维护性:通过重构,使代码易于理解、修改和扩展,方便后续的维护和改进。
  3. 提升性能:通过重构,可以优化算法、减少不必要的计算和资源消耗,提高代码的执行效率。
  4. 改进代码设计:通过重构,可以遵循更好的设计原则和模式,提高代码的灵活性、可扩展性和可测试性。

常见的重构技术包括:

  1. 提取函数(Extract Function):将一段代码提取成一个独立的函数,提高代码的可读性和复用性。
  2. 合并函数(Inline Function):将一个函数的内容直接替换到调用处,简化代码结构。
  3. 移动函数(Move Function):将一个函数移动到合适的位置,提高代码的组织性和模块化。
  4. 重命名变量和函数(Rename):改善变量和函数的命名,使其更加清晰和表达意图。
  5. 拆分类(Split Class):将一个庞大的类拆分成多个小的类,提高类的职责单一性和可维护性。
  6. 合并类(Merge Class):将多个类合并为一个类,减少不必要的类和依赖关系。
  7. 提取接口(Extract Interface):将类的一部分接口提取成一个独立的接口,提高代码的可扩展性和可替换性。

重构需要慎重进行,确保在每次重构后能够通过测试确保代码的正确性。同时,使用版本控制工具可以保证在重构过程中能够方便地回滚到之前的代码版本。

重构是一个持续的过程,应该结合实际需求和项目情况进行。通过不断地重构,可以改进代码质量,提高开发效率,使代码更加健壮和可维护。

39.谈谈你对原型和原型链的理解

  1. 原型:
    • 在JavaScript中,每个对象(除了 null 和 undefined)都有一个原型(prototype)。
    • 原型是一个对象,它包含了共享的属性和方法。
    • 对象可以通过原型继承属性和方法,即通过原型链来访问和使用原型中的属性和方法。
  1. 原型链:
    • 原型链是一种机制,用于在对象之间实现属性和方法的继承。
    • 当访问对象的属性或方法时,如果对象本身没有定义该属性或方法,JavaScript会自动查找对象的原型,然后在原型中查找,一直追溯到原型链的顶端。
    • 原型链的顶端是 Object.prototype,它是所有对象的最终原型。

具体来说,当我们访问对象的属性或方法时,JavaScript会按照以下顺序进行查找:

  1. 首先检查对象本身是否具有该属性或方法。
  2. 如果对象本身没有该属性或方法,它会沿着原型链向上查找,即在对象的原型上进行查找。
  3. 如果原型上仍然没有找到,就会继续沿着原型链向上查找,直到到达原型链的顶端(Object.prototype)。

当一个对象在访问属性或方法时,如果自身没有定义,它会沿着原型链向上查找,直到找到匹配的属性或方法,或者到达原型链的顶端仍未找到,最终返回 undefined。

40.作用域

  1. 全局作用域(Global Scope):全局作用域是整个程序中的最外层作用域,其中定义的变量可以在程序的任何地方访问。在浏览器环境中,全局作用域通常是指在 <script> 标签中定义的变量。
  2. 函数作用域(Function Scope):函数作用域是在函数内部定义的变量所拥有的作用域范围。函数内部的变量在函数外部是不可访问的,而函数外部的变量在函数内部是可以访问的。
  3. 块级作用域(Block Scope):块级作用域是在一对花括号(如 if 语句、for 循环等)内定义的变量所拥有的作用域范围。在块级作用域中定义的变量只能在该块级作用域内部访问,对于外部作用域是不可见的。在 ES6 中引入了 let 和 const 关键字,可以用来声明块级作用域的变量。
  4. 词法作用域(Lexical Scope):词法作用域是在代码编写阶段就确定的作用域,在代码中定义的位置决定了变量的作用域范围。这种作用域规则允许内部函数访问外部函数的变量,形成了作用域链。

作用域的使用可以帮助我们控制变量的可见性和生命周期,防止变量冲突和数据污染。了解作用域的概念和规则,可以帮助我们更好地组织和管理代码,避免一些潜在的问题和错误。

41.slot插槽类型

  1. 默认插槽(Default Slot):默认插槽是最基本的插槽类型,当父组件中没有使用具名插槽时,默认会将内容分发到子组件的默认插槽中。在子组件中,可以使用 <slot> 标签定义默认插槽的位置,并在需要插入内容的地方显示插槽内容。
  2. 具名插槽(Named Slot):具名插槽允许在父组件中使用多个插槽,并将内容分发到指定的插槽中。在父组件中,可以使用 <slot> 标签的 name 属性定义具名插槽,并在子组件中使用 <template> 标签加上 v-slot 指令来指定插入具名插槽的内容。
  3. 作用域插槽(Scoped Slot):作用域插槽允许子组件向父组件传递数据,同时控制插槽内容的显示逻辑。在子组件中,可以使用带有参数的 <slot> 标签来声明作用域插槽,并通过插槽内的数据参数进行传递。

42.key的作用

下面是对key的几点理解:

  1. 唯一标识:key的值应该是唯一的,用于标识每个节点的身份。当数据发生变化时,Vue会根据key来判断哪些节点是新创建的、被修改的或被删除的,从而进行精确的更新操作。
  2. 重用机制:Vue使用key来判断是否重用已存在的DOM节点。当key值保持不变时,Vue会认为这是同一个节点,并尝试复用它,从而避免不必要的销毁和重新创建,提高渲染效率。
  3. 列表渲染:在列表渲染中,使用key可以确保在新增、删除或重新排序列表项时,Vue能够正确地跟踪每个列表项的状态变化。这样可以避免出现错误的重用或重新排序问题,提供更好的用户体验。
  4. 不建议使用索引作为key:在列表渲染中,通常不建议使用索引作为key。因为索引并不能保证唯一性,并且在数据发生变化时可能导致错误的节点复用或重新排序。

需要注意的是,key只在兄弟节点之间必须唯一,不同层级之间的key可以重复。此外,key仅在需要进行DOM节点的重用或重新排序时才需要指定,对于静态列表或没有变化的节点,可以不必添加key属性。

key在Vue中的作用是用于识别和跟踪动态生成的DOM元素,并帮助Vue进行高效的更新和重用。合理使用key能够提高渲染性能,避免出现错误的节点复用或重新排序问题。

43.vue跟传统开发的区别

  1. 响应式数据绑定:Vue.js 使用了响应式数据绑定机制,通过使用数据绑定语法(如 {{}})将数据和 DOM 元素进行关联。当数据发生变化时,Vue.js 会自动更新相关的 DOM 元素,无需手动操作 DOM。这使得开发者可以更专注于数据的处理和业务逻辑,而不用过多关注视图的更新。
  2. 组件化开发:Vue.js 鼓励使用组件化的开发方式,将页面拆分为多个独立的组件,每个组件负责特定的功能。组件可以包含自己的模板、样式和逻辑,使得代码更加模块化和可维护。组件之间可以进行组合和嵌套,通过 props 和 events 实现组件之间的数据通信。
  3. 虚拟 DOM:Vue.js 使用虚拟 DOM(Virtual DOM)来提高性能和渲染效率。虚拟 DOM 是一个轻量级的 JavaScript 对象树,与真实的 DOM 结构相对应。当数据发生变化时,Vue.js 会先更新虚拟 DOM,然后通过比较虚拟 DOM 的差异,最终只对需要更新的部分进行实际的 DOM 操作,从而减少了 DOM 操作的次数和开销,提高了性能。
  4. 数据驱动和声明式编程:Vue.js 使用数据驱动的思想,开发者只需要关注数据的变化,而不需要手动操作 DOM 或进行复杂的逻辑处理。通过声明式的编程方式,开发者可以更清晰地描述目标状态是什么,而不用关心如何实现。这使得代码更加简洁、可读性更高,并且易于维护和调试。
  5. 生态系统和工具支持:Vue.js 拥有丰富的生态系统和强大的工具支持。它提供了一系列的官方插件和第三方库,涵盖了路由、状态管理、表单验证、UI 组件等方面的功能。同时,Vue CLI 是一个官方提供的脚手架工具,可以快速搭建 Vue 项目并集成常用的开发工具和配置。

与传统开发方式相比,Vue.js 提供了更简洁、高效和可维护的开发方式,使得前端开发变得更加快速和愉悦。它通过引入响应式数据绑定、组件化开发、虚拟 DOM 等技术,为开发者提供了更好的开发体验和更高的性能。

44.数组去重方法

  1. 使用 Set 数据结构:Set 是 ES6 引入的一种新的数据结构,它可以存储唯一的值。利用 Set 的特性,可以快速实现数组去重。
const arr = [1, 2, 3, 1, 2, 3];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3]

2.使用 filter 方法:利用 Array 的 filter 方法,遍历数组并返回满足条件的唯一值。

const arr = [1, 2, 3, 1, 2, 3];
const uniqueArr = arr.filter((value, index, self) => {
  return self.indexOf(value) === index;
});
console.log(uniqueArr); // [1, 2, 3]

3.使用 reduce 方法:通过 reduce 方法遍历数组,将每个元素添加到一个新数组中,但只添加第一次出现的元素。

const arr = [1, 2, 3, 1, 2, 3];
const uniqueArr = arr.reduce((result, current) => {
  if (!result.includes(current)) {
    result.push(current);
  }
  return result;
}, []);
console.log(uniqueArr); // [1, 2, 3]

4.使用 indexOf 方法:遍历数组,利用 indexOf 方法检查元素在数组中的索引,如果索引与当前遍历的索引相等,则表示该元素为第一次出现的元素。

const arr = [1, 2, 3, 1, 2, 3];
const uniqueArr = arr.filter((value, index, self) => {
  return self.indexOf(value) === index;
});
console.log(uniqueArr); // [1, 2, 3]

45.异步更新 $nextTick

当 Vue.js 更新数据时,DOM 并不会立即更新,而是以异步的方式进行更新。这意味着在修改数据后立即访问 DOM 可能无法获取到最新的更新结果。为了解决这个问题,可以使用 $nextTick 方法来确保在 DOM 更新完成后执行相应的操作。

46.封装复用性比较高的组件要考虑什么

封装具有高复用性的组件需要考虑单一职责、可配置性、可扩展性、可组合性、可测试性、文档和示例、样式隔离以及性能考虑等方面。

47.登录 Token

  1. 用户通过用户名和密码等凭证进行登录。
  2. 服务器验证凭证的有效性,如果验证通过,生成一个唯一的登录 Token。
  3. 服务器将登录 Token 发送给客户端(通常是作为响应的一部分),客户端将登录 Token 存储在本地,例如在浏览器的 Local Storage 或 Cookie 中。
  4. 客户端在后续的请求中将登录 Token 作为请求头或请求参数发送给服务器。
  5. 服务器接收到请求后,通过验证登录 Token 来确认用户的身份和权限。
  6. 如果登录 Token 有效,服务器会根据用户的身份和权限处理请求;如果登录 Token 无效或过期,服务器可能会返回错误或要求重新登录。

需要注意的是,为了确保登录 Token 的安全性,应采取一些安全措施,例如使用 HTTPS 加密通信、设置登录 Token 的有效期限、定期更换登录 Token、在客户端进行登录 Token 的存储和处理时进行安全验证等。

登陆流程:

1.用户点击登录时会带着账号和密码调用后端的登陆接口

2.后端会验证数据库中有没有该数据,如果没有,后端会返回错误,前端会在页面提示用户,如果信息存在,给前端返回一个token值,前端拿到token会存储在vuex或者localstrorsge中并跳转页面登录成功

3.前端每次跳转页面时需具备登录状态的页面时都需要判断当前的token是否存在,如果不存在就跳转到登录页面,存在就正常跳转,通常会封装到理由守卫中,在向后端发送其他请求时,需要在请求头中带上token值,项目中通常封装在请求拦截器中,后端判断该请求头中有无token,有就验证token,验证成功后返回数据,验证失败,如过期,返回相应的错误码,前端拿到相关错误信息清除token,回退到登录页面

48.computed 和 watch的区别

computed 是一个计算属性,用于根据响应式数据的变化计算派生出来的值。计算属性是基于它们的依赖进行缓存的,只有当依赖发生变化时,计算属性才会重新计算。计算属性适用于需要进行复杂计算或依赖多个响应式数据的情况。它具有缓存特性,多次访问同一个计算属性会直接返回缓存的结果,不会重复计算。

watch 是一个观察者,用于监听一个或多个响应式数据的变化,然后执行相应的回调函数。通过 watch,可以对数据的变化做出自定义的响应,例如发起异步请求、执行复杂的逻辑操作等。watch 适用于需要在数据变化时执行异步或开销较大的操作的场景。

computed 适用于根据其他响应式数据计算出一个新的值,具有缓存特性;而 watch 适用于监听单个或多个响应式数据的变化,并执行自定义的响应操作,没有缓存特性。根据具体的需求和场景选择使用 computed 或 watch 可以更好地实现响应式数据处理。

computed 和 watch的使用场景?

在Vue中,computed和watch是用于响应数据变化的两个重要特性。

computed属性是一种计算属性,它会根据依赖的响应式数据动态计算出一个新的值,并将其缓存起来,只有依赖的数据发生变化时才会重新计算。computed属性适合用于处理数据的转换、过滤、组合等操作,以及基于数据的衍生计算。

以下是一些适合使用computed的场景:

  1. 数据转换:将原始数据进行格式转换、单位转换、日期格式化等操作。
  2. 数据过滤:根据特定条件过滤列表数据,生成一个新的过滤后的数据。
  3. 数据计算:根据依赖的数据进行简单的计算,如求和、平均值等。
  4. 数据衍生:根据一个或多个数据的组合生成一个衍生数据,如根据用户的姓和名生成全名。
<template>
  <div>
  <p>姓:<input v-model="firstName"></p>
  <p>名:<input v-model="lastName"></p>
  <p>全名:{{ fullName }}</p>
  </div>
  </template>

  <script>
export default {
  data() {
    return {
      firstName: '',
      lastName: ''
    };
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName;
    }
  }
}
  </script>

watch属性用于监听特定的数据变化,并在数据变化时执行相应的操作。与computed不同,watch是一个观察者,它可以监测到数据的每一次变化,无论是简单的数据变化还是复杂的对象或数组的变化。

以下是一些适合使用watch的场景:

  1. 异步操作:当数据发生变化时,需要进行异步操作,如发起网络请求或执行定时器。
  2. 监听对象属性:当对象的属性发生变化时,需要执行特定的操作,如对象属性的增加、删除、更新等。
  3. 复杂计算:当需要进行复杂的计算操作,或需要根据多个数据的变化来触发特定操作时。
  4. 监听数组变化:当需要监听数组的变化,如数组的增加、删除、排序等。
<template>
  <div>
  <p>商品数量:<input v-model="quantity"></p>
  <p>商品总价:{{ totalPrice }}</p>
  </div>
  </template>

  <script>
export default {
  data() {
    return {
      quantity: 0,
      price: 10,
      totalPrice: 0
    };
  },
  watch: {
    quantity: {
      immediate: true,
      handler(newQuantity) {
        this.totalPrice = this.price * newQuantity;
      }
    }
  }
}
  </script>

在上述示例中,使用watch属性监听

49.事件冒泡和事件捕获

事件冒泡: 事件冒泡是指当一个元素触发某个事件时,事件会从该元素开始向上冒泡传播到父元素,直至传播到根节点。换句话说,事件会先触发最内层元素的事件处理函数,然后依次触发父元素的事件处理函数,直到到达根节点或阻止事件冒泡。

事件捕获: 事件捕获是指当一个元素触发某个事件时,事件会从根节点向下捕获传播到最内层元素。换句话说,事件会先触发根节点的事件处理函数,然后依次触发父元素的事件处理函数,直到到达最内层元素或阻止事件捕获。

在 DOM 中,事件传播分为三个阶段:

  1. 捕获阶段(Capture Phase):事件从根节点向下捕获传播,依次触发父元素的事件处理函数,直到到达目标元素。
  2. 目标阶段(Target Phase):事件到达目标元素,触发目标元素上的事件处理函数。
  3. 冒泡阶段(Bubble Phase):事件从目标元素开始向上冒泡传播,依次触发父元素的事件处理函数,直到到达根节点。

50.事件委托

事件委托是利用事件冒泡机制,将事件处理程序绑定在父元素上,而不是绑定在每个子元素上的技术。通过事件委托,我们可以在父元素上统一处理多个子元素的事件,从而减少事件处理程序的数量,提高性能并简化代码。

事件委托的实现步骤如下:

  1. 选取一个合适的父元素作为事件委托的目标,该父元素应包含所有需要监听事件的子元素。
  2. 在父元素上绑定事件处理程序,例如使用 addEventListener 方法。
  3. 在事件处理程序中,通过事件对象的属性(如 event.target)来判断事件的目标元素是哪个子元素。
  4. 根据事件目标元素的条件,执行相应的处理逻辑。

事件委托的优点包括:

  1. 减少事件处理程序的数量,简化代码结构。
  2. 动态添加和删除的子元素无需重新绑定事件处理程序。
  3. 提高性能,减少内存占用和事件注册的开销。
  4. 适用于处理具有相同处理逻辑的子元素。

需要注意以下几点:

  1. 父元素应选择合适的层级,以包含所有需要监听事件的子元素。
  2. 父元素应在页面加载时存在,或者在动态添加时及时绑定事件处理程序。
  3. 事件委托适用于具有相同处理逻辑的子元素,不适用于每个子元素都有不同处理逻辑的情况。
  4. 避免过度嵌套的父元素,以避免性能问题。

51.本地存储

在Web开发中,常用的本地存储技术包括:

  1. Cookie:Cookie 是一种在客户端存储少量数据的机制。它可以存储在浏览器中,并在每次请求时自动发送到服务器。Cookie 的大小受到限制,并且会随着每次请求自动发送,因此适合存储小量的数据和会话相关信息。
  2. Web Storage(localStorage 和 sessionStorage):Web Storage 提供了更大的存储容量,可以在客户端保存更多的数据。它分为两种类型:Web Storage 使用简单的键值对(key-value)形式进行数据存储,可以通过 JavaScript 的 API 进行读取、写入和删除等操作。
    • localStorage:localStorage 提供了持久化的本地存储,数据在浏览器关闭后仍然存在,并且在下次打开网页时可被访问。
    • sessionStorage:sessionStorage 提供了会话级别的本地存储,数据在用户关闭网页或浏览器标签页后会被清除。

52.vue3为什么要用 Proxy 替代defineProperty?

  1. 更好的性能:Proxy API 比 defineProperty API 具有更高的性能。在 Vue 2.x 中,响应式系统使用 defineProperty 监听对象属性的变化,但它需要遍历对象的每个属性,并为每个属性设置 getter 和 setter。而 Proxy API 在底层实现上更接近于原生 JavaScript 对象,能够在访问对象属性时提供更好的性能。
  2. 更好的扩展性和灵活性:Proxy API 提供了丰富的陷阱(trap)和反射操作,允许我们对对象的行为进行自定义。我们可以通过定义陷阱来拦截和处理对象的各种操作,如读取属性、写入属性、删除属性等。这使得我们能够在响应式系统之外做更多的扩展和定制。
  3. 更好的类型检查和错误提示:使用 Proxy API 可以提供更准确的类型检查和错误提示。Vue 3.0 借助 TypeScript 的支持,能够更好地推断和验证对象的类型,并在开发过程中提供更好的类型检查和错误提示,以减少开发中的潜在错误。
  4. 更好的嵌套响应式支持:Vue 3.0 中,使用 Proxy API 可以更好地支持嵌套对象的响应式。在 Vue 2.x 中,嵌套对象需要手动设置为响应式,而在 Vue 3.0 中,通过 Proxy API,嵌套对象会自动被转换为响应式,无需手动设置。

Vue 3.0 使用 Proxy API 替代 defineProperty API 是为了改进响应式系统的实现方式,提供更好的性能、更灵活的特性以及更好的类型检查和错误提示。这使得 Vue 3.0 在开发体验和性能方面有了显著的改进。

53.对proxy的理解

Proxy 提供了多个陷阱方法,包括 get、set、deleteProperty、apply、construct 等,用于拦截不同的操作。我们可以在这些陷阱方法中定义自定义的行为来改变或增强对象的操作。

Proxy 提供了更好的性能和灵活性,相比之前的 defineProperty API,它更接近原生 JavaScript 对象的行为。它还可以与其他 JavaScript 特性如 Reflect API 结合使用,提供更强大的功能。

54.事件循环机制

JavaScript 是单线程的,意味着一次只能执行一个任务。然而,JavaScript 也支持异步操作,如定时器、网络请求、事件处理等,这些异步操作不会阻塞主线程的执行。事件循环机制使得 JavaScript 能够处理这些异步操作,并保持单线程的特性。

事件循环的基本流程如下:

  1. 执行同步任务:首先,JavaScript 引擎会执行当前处于主线程上的同步任务,按照顺序逐个执行。
  2. 处理微任务:在执行同步任务期间,如果产生了微任务(Promise、MutationObserver 等),它们会被添加到微任务队列中。
  3. 执行宏任务:当主线程上的同步任务执行完毕后,JavaScript 引擎会查找宏任务队列中的第一个任务,并执行该任务。常见的宏任务包括定时器回调、事件回调和网络请求等。
  4. 处理微任务:在执行宏任务期间,如果产生了微任务,它们会被添加到微任务队列中。
  5. 重复步骤 3 和步骤 4:循环执行宏任务和微任务的过程,直到任务队列中的所有任务都被执行完毕。

在事件循环中,微任务的执行优先级高于宏任务。也就是说,每次执行完一个宏任务后,会立即处理微任务队列中的所有任务,然后再执行下一个宏任务。

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

父:beforecreate -->父:created --> 父:beforeMount --> 子:beforecreate -->

子:created --> 子:beforeMount --> 子:mounted --> 父:mounted

56.Vuex

Vuex的核心概念包括:

  1. State(状态):Vuex使用一个单一的状态树来存储整个应用程序的状态。它类似于组件中的data,但是可以被多个组件共享。
  2. Mutations(突变):突变是修改状态的唯一方式。它们是同步的操作,用于更改状态的值。通过提交一个突变,可以跟踪状态的变化,并使其成为可追踪的。
  3. Actions(动作):动作是用于处理异步操作的方法。它可以包含多个突变,用于执行一系列的突变操作,或者触发其他动作。通过调度一个动作,可以执行异步操作,然后再提交一个或多个突变来修改状态。
  4. Getters(获取器):获取器允许从状态树中派生出一些衍生状态,类似于组件中的计算属性。它们可以用于根据应用程序的状态计算一些派生数据,供组件使用。
  5. Modules(模块):模块允许将大型的状态树拆分为多个模块,每个模块都有自己的状态、突变、动作和获取器。模块化的状态管理可以更好地组织和管理复杂的应用程序。

57.常用的数组API

  1. push(): 将一个或多个元素添加到数组的末尾,并返回数组的新长度。
  2. pop(): 删除数组的最后一个元素,并返回删除的元素。
  3. shift(): 删除数组的第一个元素,并返回删除的元素。
  4. unshift(): 将一个或多个元素添加到数组的开头,并返回数组的新长度。
  5. concat(): 将两个或多个数组合并为一个新数组。
  6. slice(): 返回一个新数组,其中包含从开始到结束(不包括结束)选择的数组的元素。
  7. splice(): 通过删除、替换或添加元素来修改数组,可以在指定的索引位置插入、删除或替换元素。
  8. forEach(): 对数组中的每个元素执行提供的回调函数。
  9. map(): 对数组中的每个元素执行提供的回调函数,并返回一个新数组,新数组包含每个回调函数的返回值。
  10. filter(): 对数组中的每个元素执行提供的回调函数,并返回一个新数组,新数组只包含符合条件的元素。
  11. find(): 返回数组中满足提供的测试函数的第一个元素的值。
  12. findIndex(): 返回数组中满足提供的测试函数的第一个元素的索引。
  13. reduce(): 对数组中的每个元素执行提供的回调函数,并将其结果汇总为单个值。
  14. some(): 对数组中的每个元素执行提供的测试函数,如果至少有一个元素满足条件,则返回true,否则返回false。
  15. every(): 对数组中的每个元素执行提供的测试函数,如果所有元素都满足条件,则返回true,否则返回false。

这些是常见的数组操作API,可以根据具体需求选择合适的API来处理数组。

  1. indexOf(): 返回数组中第一个匹配给定元素的索引,如果没有找到则返回-1。
  2. lastIndexOf(): 返回数组中最后一个匹配给定元素的索引,如果没有找到则返回-1。
  3. includes(): 判断数组是否包含指定元素,如果包含则返回true,否则返回false。
  4. reverse(): 反转数组中元素的顺序。
  5. sort(): 对数组进行排序,默认按照元素的Unicode编码进行排序,也可以传入自定义的比较函数。
  6. join(): 将数组中的所有元素转换为字符串,并使用指定的分隔符将它们连接起来。
  7. fill(): 用指定的值填充数组的所有元素。
  8. isArray(): 判断给定值是否为数组类型。
  9. flat(): 将多维数组转换为一维数组。
  10. flatMap(): 对数组中的每个元素执行提供的回调函数,并将结果展平为一维数组。

58.谈谈你对Promise的理解

以下是对Promise的理解:

  1. 异步操作的状态管理:Promise具有三种状态:pending(进行中)、fulfilled(已完成)和rejected(已拒绝)。当一个Promise被创建时,它处于pending状态。异步操作的结果可以通过调用resolve函数来将Promise状态转变为fulfilled,或通过调用reject函数将Promise状态转变为rejected。一旦状态转变为fulfilled或rejected,Promise的状态将不再改变。
  2. 链式调用和方法链:Promise提供了链式调用的语法,通过返回一个新的Promise实例,可以在连续的Promise之间串联起来进行操作。这种方法链的形式使得异步操作可以按照顺序执行,并且可以在每个Promise中进行进一步的处理、转换或错误处理。
  3. 错误处理:Promise提供了.catch()方法来捕获Promise链中的任何错误,并提供一种统一的错误处理机制。如果任何一个Promise状态转变为rejected,错误信息将被传递到最近的.catch()块,并且可以在其中进行适当的处理。
  4. 并行和串行执行:Promise可以通过Promise.all()和Promise.race()方法来实现并行或串行执行多个异步操作。Promise.all()用于等待多个Promise全部完成,返回一个包含所有结果的Promise数组;而Promise.race()用于等待多个Promise中的任何一个完成,返回第一个完成的Promise的结果。
  5. 异常冒泡:Promise允许异常在Promise链中进行冒泡,即可以通过在任何一个Promise中抛出错误,然后在后续的.catch()块中捕获和处理错误。这种异常冒泡的机制使得错误处理更加灵活和统一。

59.Promise.all() 执行多个异步操作 其中一个出现错误会出现什么情况


当使用Promise.all()执行多个异步操作时,如果其中一个Promise实例的状态变为rejected(即出现错误),Promise.all()会立即返回一个rejected状态的Promise,并且返回的Promise的错误信息将是第一个被拒绝的Promise的错误信息。

具体情况如下:

  1. 如果所有的Promise都fulfilled:Promise.all()返回一个新的Promise,该Promise的状态为fulfilled,其值是一个包含所有Promise结果的数组。
  2. 如果其中一个Promise被rejected:Promise.all()会立即返回一个新的Promise,该Promise的状态为rejected,其错误信息是第一个被拒绝的Promise的错误信息。

这意味着如果在多个异步操作中,有一个操作出现错误,整个Promise.all()的结果将被拒绝,并且无法获取其他异步操作的结果。这种机制使得在并行执行多个异步操作时,可以快速捕获和处理错误,并且避免等待所有异步操作完成后才处理错误的情况。

需要注意的是,Promise.all()的行为是"一旦有一个Promise被拒绝就立即拒绝",因此如果有些异步操作不关心错误,可以在对应的Promise上使用.catch()方法进行错误处理,以防止整个Promise.all()被拒绝。

60.谈谈你对堆和栈的理解

堆和栈是计算机内存中两种不同的数据存储方式,用于管理程序运行时的内存分配和释放。

  1. 栈(Stack):
    • 栈是一种具有特定的数据结构,采用先进后出(Last-In-First-Out,LIFO)的方式进行数据存储和访问。
    • 栈中的数据以"压栈"(push)和"出栈"(pop)的方式进行操作。
    • 栈的大小在程序编译时就确定,并且内存分配是连续的。
    • 栈主要用于存储局部变量、函数调用和程序执行过程中的上下文信息(函数调用栈帧)等。
  1. 堆(Heap):
    • 堆是一种无序、动态分配和释放内存的数据区域。
    • 堆的内存分配和释放由程序员手动控制,需要显式地进行内存分配(如通过new、malloc等操作)和释放(如通过delete、free等操作)。
    • 堆的大小在程序运行时可以动态改变,并且内存分配是非连续的。
    • 堆主要用于存储动态分配的对象、数据结构(如数组、链表)和大型数据等。

在编程中,栈和堆的选择有不同的用途和影响:

  • 栈的内存管理由编译器自动处理,分配和释放速度快,但栈的容量有限。
  • 堆的内存管理由程序员手动控制,分配和释放速度较慢,但堆的容量较大。


61.WebSocket的使用

以下是使用WebSocket的基本步骤:

  1. 创建WebSocket对象:在客户端代码中,使用JavaScript的WebSocket API创建一个WebSocket对象。通过指定WebSocket服务器的URL,可以与服务器建立连接。
var socket = new WebSocket("ws://example.com/socket");
  1. 监听事件:WebSocket对象提供了一些事件,用于处理连接的打开、消息接收、错误和关闭等情况。可以通过添加相应的事件处理程序来处理这些事件。
socket.onopen = function() {
  // 连接已打开
};

socket.onmessage = function(event) {
  // 接收到服务器发送的消息
  var message = event.data;
  // 处理消息
};

socket.onerror = function(error) {
  // 发生错误
};

socket.onclose = function(event) {
  // 连接已关闭
};
  1. 发送和接收消息:使用WebSocket对象的send()方法向服务器发送消息,服务器可以通过WebSocket对象的onmessage事件监听接收到的消息。
// 发送消息
socket.send("Hello, server!");

// 接收消息(通过onmessage事件处理程序)
socket.onmessage = function(event) {
  var message = event.data;
  // 处理接收到的消息
};
  1. 关闭连接:通过调用WebSocket对象的close()方法可以手动关闭连接。
socket.close();

62.你在axios二次封装做过什么操作。

  1. 设置默认配置:可以在封装过程中设置一些默认配置,如请求的基本 URL、请求头、超时时间等,使得每次发起请求时无需重复设置这些配置。
  2. 请求拦截器:可以在请求被发送之前进行拦截和处理,例如添加认证信息、处理请求参数、设置请求头等。请求拦截器还可以用于全局的错误处理、loading 状态的控制等。
  3. 响应拦截器:可以在接收到响应之后进行拦截和处理,例如处理响应数据、根据状态码进行统一的错误处理、转换数据格式等。
  4. 错误处理:可以根据具体的业务需求对错误进行统一处理,例如根据不同的错误码进行相应的操作,如重新登录、跳转错误页面等。
  5. 统一数据格式处理:可以对请求和响应的数据进行统一的格式化处理,以适应前端的数据需求,例如将接口返回的数据进行转换、添加额外的字段等。
  6. 处理 loading 状态:可以在请求开始和结束时控制全局的 loading 状态,以提供更好的用户体验。
  7. 封装常用请求方法:可以根据业务需求封装常用的请求方法,如 GET、POST、PUT、DELETE 等,简化请求代码的编写。
  8. 处理请求取消:可以在封装过程中处理请求的取消操作,以便在组件销毁或取消请求时能够正确地取消请求,避免造成资源浪费或潜在的错误。
  9. 处理请求重试:可以针对某些特定的请求错误情况,实现请求的自动重试机制,以增加请求的稳定性和容错性。

通过对 Axios 进行二次封装,可以实现对请求和响应的统一处理、简化代码编写、提供更好的错误处理和数据格式处理等功能,从而提高开发效率和代码质量。

63.浏览器缓存怎么做?

  1. HTTP缓存:利用HTTP协议中的缓存机制,通过设置响应头信息控制缓存行为。常见的缓存控制头有:通过合理配置这些缓存控制头,可以让浏览器在满足缓存条件时直接使用本地缓存,减少网络请求。
    • Cache-Control:设置缓存的方式,如max-age设置缓存的最大时间。
    • Expires:设置过期时间,告诉浏览器何时需要重新请求资源。
    • ETag:标识资源的唯一性,用于判断资源是否发生变化。
    • Last-Modified:标记资源的最后修改时间,配合If-Modified-Since进行缓存验证。
  1. 文件版本控制:通过给文件名或URL添加版本号、哈希值等唯一标识,当文件内容发生变化时,修改其版本号或哈希值。这样可以确保浏览器能够正确地识别并获取最新的文件版本,避免缓存过期问题。
  2. Service Worker:使用 Service Worker 技术可以在浏览器中拦截网络请求,缓存资源并在离线时提供缓存的资源。通过 Service Worker,可以实现更高级的缓存策略,如离线缓存、缓存优先策略等。
  3. 缓存策略:根据资源的特性和使用场景,可以选择不同的缓存策略。常见的缓存策略有:
    • 强缓存:通过设置响应头信息,直接使用本地缓存,如 Cache-Control 和 Expires。
    • 协商缓存:通过设置响应头信息,与服务器进行缓存验证,如 ETag 和 Last-Modified。
    • 离线缓存:使用 Service Worker 技术,缓存资源以在离线时提供访问。

综合使用上述方法,可以实现浏览器缓存,减少网络请求,提高页面加载速度和用户体验。需要根据具体的项目需求和资源特性选择合适的缓存策略,并进行适当的调试和测试以确保缓存机制的正确性和性能优化效果。

64.HTML5新特性有哪些?

HTML5引入了许多新特性和改进,以下是HTML5的一些主要新特性:

  1. 语义化标签:HTML5引入了一系列的语义化标签,如<header>、<nav>、<section>、<article>等,用于更准确地描述页面结构和内容,提升了页面的可读性和可访问性。
  2. 媒体支持:HTML5提供了更好的媒体支持,包括<video>和<audio>标签,可以直接在网页中嵌入视频和音频,不再需要依赖第三方插件。
  3. Canvas绘图:HTML5的<canvas>元素允许开发者使用JavaScript进行动态的图形绘制,包括绘制图形、渲染图像和创建动画等,为开发游戏、图表和数据可视化等提供了更灵活的方式。
  4. 地理位置定位:HTML5的地理位置API允许网页获取用户的地理位置信息,通过navigator.geolocation对象可以获取用户的经纬度坐标,为基于地理位置的应用和服务提供支持。
  5. 表单增强:HTML5提供了一些表单的增强功能,如输入类型的扩展(如日期选择、邮箱、电话等)、表单验证、自动填充和表单数据存储等,提升了用户体验和数据交互的能力。
  6. Web存储:HTML5引入了本地存储机制,包括Web Storage(localStorage和sessionStorage)和IndexedDB,允许网页在客户端存储数据,提供了更好的离线和缓存能力。
  7. Web Worker:HTML5的Web Worker允许在后台运行多个JavaScript线程,可以进行复杂的计算和处理,而不阻塞用户界面的响应。
  8. WebSocket:HTML5引入了WebSocket协议,提供了双向通信的能力,实现了实时数据传输,适用于聊天应用、实时通知和多人协作等场景。

这些只是HTML5的一些主要特性,HTML5还包括许多其他改进和功能,使得开发者能够创建更丰富、更交互和更高效的网页应用。


66.CSS3新特性有哪些?

  1. 弹性布局(Flexbox):提供了一种灵活的布局方式,可以方便地实现响应式布局和自适应布局。
  2. 网格布局(Grid):提供了一种二维的布局方式,可以将页面分成多个区域,并且可以控制每个区域的大小和位置。
  3. 自定义字体(@font-face):可以在网页上使用自定义字体,而不需要依赖于用户计算机上已安装的字体。
  4. 渐变效果(Gradient):可以通过CSS3实现直线渐变和径向渐变,可以用来实现背景、边框和文本等多种效果。
  5. 动画效果(Animation):可以通过CSS3实现动画效果,比如旋转、缩放、淡入淡出等多种效果。
  6. 2D和3D转换(Transform):可以通过CSS3实现元素的二维和三维转换,比如旋转、缩放、倾斜、平移等多种效果。
  7. 过渡效果(Transition):可以通过CSS3实现过渡效果,比如颜色、背景、边框等多种效果。
  8. 媒体查询(Media Queries):可以通过CSS3实现响应式布局,根据屏幕大小和设备类型等条件来调整页面布局和样式。
  9. 伸缩盒模型(Box-sizing):可以通过CSS3设置盒模型的大小计算方式,可以方便地控制元素的大小和位置。

67.WebSocket 和服务端建立连接后,服务端修改token会不会断开连接?

WebSocket 建立连接后,服务端更换 Token 不会直接导致连接断开。WebSocket 的连接是一种持久连接,一旦建立,它可以保持活跃状态,并允许双方之间进行实时的双向通信。

更换 Token 可能涉及到一些与身份验证或会话管理相关的操作。在某些情况下,服务端可能需要通知客户端进行重新认证或重新建立连接。这可以通过特定的协议消息或自定义的应用层协议来实现。

具体的实现方式可能会根据你使用的 WebSocket 库、框架或服务端实现而有所不同。一般情况下,服务端可以发送一个特定的消息,提示客户端 Token 已更换,并要求客户端进行相应的处理。客户端接收到这个消息后,可以根据协议规定的方式进行重新认证或重新建立连接,以确保继续保持有效的通信状态。

需要注意的是,具体的实现方式和流程可能会受到你的应用程序设计和需求的影响。建议参考所使用的 WebSocket 库或框架的文档,以了解更多关于 Token 更换时的最佳实践和实现方式。


68.Vue3中常用的组合式API及作用

组合式API通过setup函数提供了一种更直接的方式来编写组件逻辑。

  1. ref:ref函数用于创建一个响应式的数据引用。它将一个普通的JavaScript值转换为一个响应式对象,并返回一个可以访问和修改该值的引用。在模板或setup函数中使用这个引用,当引用的值发生变化时,相关的界面会自动更新。
  2. reactive:reactive函数用于创建一个响应式的数据对象。它将一个普通的JavaScript对象转换为一个响应式对象,并返回一个可以访问和修改该对象的引用。与ref不同,reactive可以处理更复杂的对象,包括嵌套对象和数组。
  3. computed:computed函数用于创建一个计算属性。计算属性是基于响应式数据的衍生值,它会根据依赖的响应式数据自动更新。通过computed函数创建的计算属性可以像普通属性一样访问,但它的值是根据相关的响应式数据动态计算得出的。
  4. watch:watch函数用于监听一个响应式数据的变化,并在数据变化时执行相应的回调函数。你可以使用watch来执行一些副作用操作,例如发送网络请求、处理数据更新等。watch函数还可以传递一个可选的配置对象,用于更详细地控制监听行为。
  5. onMounted、onUpdated和onUnmounted:这些函数分别用于在组件挂载后、更新后和卸载前执行一些操作。你可以在这些生命周期钩子函数中执行一些初始化、清理或其他副作用操作。这些钩子函数是基于组合式API的方式来定义的,与Vue 2.x中的选项式API中的生命周期钩子对应。
  6. toRefs:toRefs函数用于将一个响应式对象转换为一组只读的响应式引用。当你需要将一个响应式对象的属性解构出来,并在模板或其他上下文中使用时,可以使用toRefs来确保属性保持响应式。
  7. provide和inject:这对函数用于在父子组件之间进行依赖注入。通过provide函数在父组件中提供一些值,然后在子组件中使用inject函数来注入这些值。这可以方便地实现跨层级组件之间的通信和共享数据。
  8. onBeforeMount和onBeforeUnmount:这些函数类似于生命周期钩子函数,但在组件挂载或卸载之前触发。你可以在这些函数中执行一些准备工作或清理操作。
  9. onBeforeUpdate和onUpdated:这对函数也类似于生命周期钩子函数,但在组件更新之前和之后触发。你可以在onBeforeUpdate中执行一些更新前的准备工作,而在onUpdated中执行一些更新后的操作。
  10. watchEffect:watchEffect函数用于创建一个响应式的副作用。它会自动追踪其依赖的响应式数据,并在这些数据发生变化时重新运行副作用函数。与watch不同,watchEffect不需要指定具体的数据依赖,它会自动推断出所需的依赖。
  11. onErrorCaptured:这个函数用于捕获组件内部的错误。你可以在组件层级中定义onErrorCaptured函数来捕获子组件中的错误,并进行相应的处理。
  12. isRef和isReactive:这些函数用于判断一个值是否为响应式引用或响应式对象。你可以使用isRef来检查一个值是否为ref创建的引用,使用isReactive来检查一个值是否为reactive创建的响应式对象。

69.v-model使用场景

v-model是Vue.js中的一个指令,用于实现表单元素与数据的双向绑定。通过v-model指令,可以将表单元素的值与Vue实例中的数据进行关联,实现数据的同步更新。下面是一些常见的使用场景:

  1. 表单输入:v-model最常见的用法是在表单输入元素(如<input>、<textarea>和<select>)上。通过将输入元素的值与Vue实例中的数据进行双向绑定,可以实现表单值的动态更新和提交。
<input v-model="message" type="text">
  1. 自定义组件:v-model还可以用于自定义组件中,使其能够像原生表单元素一样进行双向绑定。在自定义组件中,通过在组件上使用model选项定义输入值的属性和触发输入事件的方法,然后通过v-model将组件的值与父组件中的数据进行绑定。
<custom-input v-model="message"></custom-input>
  1. 多个表单元素的组合:当需要在多个表单元素之间进行联动操作时,v-model可以很方便地实现数据的同步更新。例如,当选择一个选项时,另一个输入框的值根据选项进行更新。
<select v-model="selectedOption">
  <option value="option1">Option 1</option>
  <option value="option2">Option 2</option>
</select>
<input v-model="inputValue" type="text" :disabled="selectedOption === 'option1'">

v-model是一个方便且强大的指令,用于实现表单元素与数据之间的双向绑定。它在表单输入、自定义组件以及多个表单元素的联动等场景中都非常有用。

70.React函数式组件和类组件的区别?

  1. 语法:函数式组件是一个JavaScript函数,而类组件是一个ES6类。函数式组件只需要定义一个函数来处理UI渲染,而类组件需要使用class关键字定义一个类,并且扩展自React.Component。
  2. 内部状态(State):在React 16.8之前,函数式组件没有内部状态,只能使用props来接收父组件传递的数据。但是在React 16.8引入的Hooks之后,函数式组件可以使用useState和其他Hooks来管理内部状态,使其与类组件在状态管理方面具有相似的能力。
  3. 生命周期:类组件具有生命周期方法,如componentDidMount、componentDidUpdate、componentWillUnmount等,用于处理组件在不同阶段的操作和副作用。函数式组件在React 16.8之前没有生命周期方法,但是使用useEffect Hook可以模拟生命周期行为。
  4. 可读性和代码量:相对于类组件,函数式组件通常更简洁,代码量较少,逻辑更直观。函数式组件只关注输入和输出,没有类声明和繁琐的生命周期方法。
  5. 性能:由于函数式组件没有实例化和维护额外的实例方法,它们的渲染过程相对于类组件更轻量级和高效,具有更好的性能。
  6. Hooks支持:React的Hooks是在函数式组件中引入的一种功能,可以让函数式组件具有类组件的某些功能,如状态管理和副作用处理。Hooks提供了一种更直观和简洁的方式来管理组件的状态和副作用。


71.为什么要使用React函数式组件?有哪些优点?


使用React函数式组件有以下优点:

  1. 简洁性:函数式组件相对于类组件更加简洁,代码量较少。它们只是一个函数,只需定义组件的渲染逻辑,没有复杂的生命周期方法和类声明。
  2. 易于理解和测试:函数式组件没有内部状态(state)和生命周期方法,因此更易于理解。它们只关注输入和输出,逻辑更加直观。此外,由于没有状态和生命周期的复杂性,函数式组件也更容易进行单元测试,测试代码编写起来更简单。
  3. 更好的性能:函数式组件相对于类组件具有更好的性能。函数式组件没有实例化和维护额外的实例方法,因此渲染过程更加轻量级和高效。
  4. Hooks支持:React 16.8版本引入了Hooks,它们允许在函数式组件中使用状态(useState)、副作用(useEffect)、上下文(useContext)等React功能。Hooks提供了一种更直观和简洁的方式来管理组件的状态和副作用,进一步提高了函数式组件的灵活性和可扩展性。
  5. 可读性和维护性:由于函数式组件的代码相对较少且逻辑更直观,它们具有更高的可读性。在函数式组件中,代码的组织和维护更加简单,因为它们更容易拆分为可重用的小组件。
  6. 与React生态系统的互操作性:函数式组件与React生态系统中的其他工具和库更好地配合使用。例如,它们更适合与状态管理库(如Redux、MobX)和路由库(如React Router)等集成。

72.在React函数式组件中如何处理状态(state)?

  1. useState:useState是React提供的最基本的Hook,用于在函数式组件中声明和更新状态。它接受一个初始值作为参数,并返回一个包含当前状态值和更新状态的函数的数组。可以通过解构赋值来获取状态值和更新函数,并在组件中使用。

import React, { useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
  1. useEffect:useEffect用于处理副作用,比如订阅事件、数据获取、DOM操作等。它接受一个回调函数和一个依赖数组作为参数。回调函数定义了副作用的操作,依赖数组用于指定触发副作用的依赖项。当依赖项发生变化时,副作用函数会被调用。
import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 执行副作用的操作,比如数据获取
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []); // 依赖数组为空,只在组件初始化时执行一次

  return (
    <div>
      <p>Data: {data}</p>
    </div>
  );
}
  1. useContext:useContext用于在函数式组件中访问上下文(Context)。它接受一个上下文对象作为参数,并返回上下文的当前值。可以在函数式组件内部使用useContext来获取上下文的值,并进行相应的处理。
import React, { useContext } from 'react';

const MyContext = React.createContext();

function MyComponent() {
  const value = useContext(MyContext);

  return (
    <div>
      <p>Value: {value}</p>
    </div>
  );
}

73.什么是Hooks?请介绍一下常用的Hooks。

  1. useState:useState是最基本的Hook,用于在函数式组件中声明和更新状态。它返回一个包含当前状态值和更新状态的函数的数组。可以使用解构赋值来获取状态值和更新函数。
import React, { useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}
  1. useEffect:useEffect用于处理副作用,例如订阅事件、数据获取、DOM操作等。它接受一个回调函数和一个依赖数组作为参数。回调函数定义了副作用的操作,依赖数组用于指定触发副作用的依赖项。
import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 执行副作用的操作,例如数据获取
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []); // 依赖数组为空,只在组件初始化时执行一次

  return (
    <div>
      <p>Data: {data}</p>
    </div>
  );
}
  1. useContext:useContext用于在函数式组件中访问上下文(Context)。它接受一个上下文对象作为参数,并返回上下文的当前值。
import React, { useContext } from 'react';

const MyContext = React.createContext();

function MyComponent() {
  const value = useContext(MyContext);

  return (
    <div>
      <p>Value: {value}</p>
    </div>
  );
}
  1. useReducer:
    • useReducer是一个用于状态管理的Hook,它可以替代useState在函数式组件中处理复杂的状态逻辑。它接受一个reducer函数和初始状态作为参数,并返回当前状态和一个dispatch函数用于触发状态更新。
    • reducer函数接受两个参数:当前状态和一个表示更新操作的action对象。它根据action的类型来确定如何更新状态,并返回新的状态。
    • useReducer适用于具有复杂状态转换逻辑或多个相关状态的情况。它提供了一种更结构化和可预测的方式来管理状态。
import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error('Unhandled action');
  }
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const increment = () => {
    dispatch({ type: 'increment' });
  };

  const decrement = () => {
    dispatch({ type: 'decrement' });
  };

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}
  1. useCallback:
    • useCallback用于缓存回调函数,以避免在每次渲染时创建新的回调函数实例。它接受一个回调函数和依赖数组作为参数,并返回一个经过缓存的回调函数。
    • 当依赖数组中的某个值发生变化时,useCallback会返回一个新的回调函数实例。否则,它会返回之前缓存的回调函数,避免不必要的函数创建和组件重新渲染。
    • useCallback适用于将回调函数传递给子组件或作为effect的依赖项时,以确保依赖项发生变化时才创建新的回调函数。
import React, { useCallback } from 'react';

function MyComponent() {
  const handleClick = useCallback(() => {
    // 处理点击事件的逻辑
    console.log('Button clicked');
  }, []);

  return <button onClick={handleClick}>Click Me</button>;
}
  1. useMemo:
    • useMemo用于缓存计算结果,以避免在每次渲染时重新计算。它接受一个计算函数和依赖数组作为参数,并返回经过缓存的计算结果。
    • 当依赖数组中的某个值发生变化时,useMemo会重新计算并返回新的计算结果。否则,它会返回之前缓存的计算结果,避免不必要的重复计算和组件重新渲染。
    • useMemo适用于在渲染期间进行昂贵的计算操作,例如数组排序、数据筛选、字符串拼接等。
import React, { useMemo } from 'react';

function MyComponent() {
  const expensiveResult = useMemo(() => {
    // 执行昂贵的计算操作
    return computeExpensiveResult();
  }, [dep1, dep2]);

  return <div>{expensiveResult}</div>;
}

74.如何在React函数式组件中处理副作用(side effects)?

useEffect接受两个参数:一个副作用函数和一个依赖数组(可选)。

  1. 副作用函数:副作用函数定义了需要执行的副作用操作,例如订阅事件、数据获取、DOM操作等。它会在每次组件渲染之后执行。
  2. 依赖数组(可选):依赖数组用于指定触发副作用的依赖项。当依赖项发生变化时,副作用函数会被重新执行。如果依赖数组为空,则副作用函数只在组件初始化和卸载时执行一次。

下面是一个使用useEffect处理副作用的示例:

import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 在副作用函数中执行数据获取操作
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));

    // 返回清理函数(可选)
    return () => {
      // 在组件卸载前执行清理操作
      // 比如取消订阅、清除定时器等
    };
  }, []); // 依赖数组为空,只在组件初始化时执行一次

  // 组件渲染逻辑
  return (
    <div>
      <p>Data: {data}</p>
    </div>
  );
}

使用useEffect可以处理各种副作用操作,但需要注意以下几点:

  • 副作用函数应该是纯函数,不应该直接修改组件状态,而是通过返回值或清理函数进行间接操作。
  • 在副作用函数中进行异步操作时,应该处理好错误和取消的情况,避免对已卸载的组件进行状态更新。
  • 如果副作用函数依赖某些特定的变量,应该将这些变量添加到依赖数组中,以确保副作用函数在依赖项变化时重新执行。

75.什么是React上下文(Context)?如何在函数式组件中使用上下文?

React上下文是一种跨组件层次共享数据的方法。它允许将数据传递给组件树中嵌套的组件,而不必手动通过props传递。在函数式组件中使用上下文,可以通过创建上下文提供者(Context Provider)和上下文消费者(Context Consumer)来读取和更新上下文数据。使用useContext Hook也可以在函数式组件中直接读取上下文数据。

76.什么是纯函数组件(Pure Function Component)?它们有什么优势?

纯函数组件是指在相同的输入下,总是返回相同的输出,并且没有副作用的函数式组件。它们接收一组属性(props)作为输入,然后根据这些属性生成并返回一个用于渲染UI的React元素。

纯函数组件具有以下优势:

  1. 简洁性:纯函数组件通常比类组件更简洁,因为它们不需要处理生命周期方法、内部状态或this关键字。纯函数组件只关注props作为输入和渲染UI作为输出。
  2. 可读性:由于纯函数组件只依赖于输入的props,并且没有副作用,因此它们的代码更易于理解和阅读。纯函数组件的逻辑更加清晰,易于推理和测试。
  3. 性能优化:纯函数组件对性能优化非常友好。由于纯函数组件没有内部状态,它们在输入不变的情况下可以进行有效的浅比较来决定是否重新渲染。这意味着React可以更轻松地跳过不必要的渲染,提高应用程序的性能。
  4. 可测试性:由于纯函数组件只依赖于输入的props,因此它们非常适合进行单元测试。通过提供不同的props,我们可以轻松地测试纯函数组件的不同渲染情况和输出。
     

77.如何在函数式组件中进行条件渲染(Conditional Rendering)?

  1. 使用if语句: 可以在函数组件中使用普通的JavaScript if语句来进行条件渲染。根据条件决定返回不同的JSX元素或null。例如:
function MyComponent({ isLoggedIn }) {
  if (isLoggedIn) {
    return <p>Welcome, User!</p>;
  } else {
    return <p>Please log in.</p>;
  }
}
  1. 使用三元运算符: 可以使用三元运算符(条件运算符)来根据条件返回不同的JSX元素。例如:
function MyComponent({ isLoggedIn }) {
  return isLoggedIn ? <p>Welcome, User!</p> : <p>Please log in.</p>;
}
  1. 使用逻辑与运算符(&&): 可以使用逻辑与运算符(&&)来进行简单的条件渲染。当条件为真时,返回需要渲染的JSX元素;当条件为假时,返回null。例如:
function MyComponent({ isLoggedIn }) {
  return isLoggedIn && <p>Welcome, User!</p>;
}

78.什么是React的渲染过程?请解释组件的渲染顺序。

React的渲染过程是指React框架如何将组件层次结构转化为实际的UI界面。当应用程序的状态发生变化或组件接收到新的属性时,React会执行重新渲染的过程来更新界面。

下面是React的渲染过程的简要概述:

  1. 初始化阶段(Mounting Phase):
    • 创建组件的实例。
    • 执行组件的构造函数,设置初始状态(state)和属性(props)。
    • 调用组件的生命周期方法(如constructor、componentDidMount)。
  1. 更新阶段(Updating Phase):
    • 根据新的属性和状态计算组件的更新。
    • 调用组件的生命周期方法(如componentDidUpdate)。
    • 更新组件的子组件。
  1. 卸载阶段(Unmounting Phase):
    • 调用组件的生命周期方法(如componentWillUnmount)。
    • 销毁组件实例。

组件的渲染顺序遵循一些规则,确保组件按照正确的顺序进行渲染和更新:

  1. 父组件先于子组件进行渲染。
    • 父组件的render方法会在子组件的render方法之前执行。
  1. 组件的render方法返回的JSX会被转化为虚拟DOM(Virtual DOM)。
    • 虚拟DOM是React内部使用的一种轻量级表示真实DOM的数据结构。
  1. 虚拟DOM会与上一次的虚拟DOM进行比较,找出需要更新的部分。
    • React使用Diff算法来高效地计算出需要更新的DOM节点。
  1. 需要更新的部分会被转化为实际的DOM操作,更新到浏览器中。

React并不会立即执行渲染过程,而是根据需要和调度进行延迟处理。React使用异步的方式来批量处理渲染和更新操作,以提高性能和避免不必要的渲染。

79.如何优化React函数式组件的性能?


优化React函数式组件的性能是提高应用性能和响应速度的重要步骤。下面是一些常用的优化技巧:

  1. 使用React.memo进行组件记忆: 使用React.memo高阶组件可以对函数式组件进行记忆,避免不必要的重新渲染。React.memo会对组件的输入进行浅比较,只有在输入发生变化时才会重新渲染组件。
import React from 'react';

const MyComponent = React.memo(({ prop1, prop2 }) => {
  // 组件渲染逻辑
});

export default MyComponent;
  1. 使用useCallback和useMemo优化函数和值的创建: 使用useCallback和useMemo可以缓存函数和值的创建,避免在每次渲染时重新创建它们。这对于避免不必要的重复计算和回调函数的重新创建很有帮助。
import React, { useCallback, useMemo } from 'react';

function MyComponent({ data }) {
  const expensiveResult = useMemo(() => {
    // 执行昂贵的计算操作
    return computeExpensiveResult(data);
  }, [data]);

  const handleClick = useCallback(() => {
    // 处理点击事件
    doSomething(data);
  }, [data]);

  // 组件渲染逻辑
  return <div onClick={handleClick}>{expensiveResult}</div>;
}
  1. 避免在渲染期间执行昂贵的操作: 渲染期间执行昂贵的操作,如数据获取、计算等,会影响应用的性能。可以使用useEffect Hook来在渲染完成后执行这些操作,以避免阻塞渲染过程。
import React, { useEffect, useState } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    // 在副作用函数中执行昂贵的操作
    fetchData().then(data => setData(data));
  }, []);

  // 组件渲染逻辑
  return <div>{data}</div>;
}
  1. 利用合适的shouldComponentUpdate或React.memo进行渲染控制: 对于复杂的组件层次结构或性能敏感的组件,可以通过手动实现shouldComponentUpdate方法或使用React.memo来进行渲染控制。这可以避免不必要的渲染和更新操作。

80.什么是高阶组件(Higher-Order Component)?它们在函数式组件中有何用途?

高阶组件是一种用于增强或修改组件功能的设计模式。它本质上是一个函数,接收一个组件作为参数,并返回一个新的增强组件。这个增强组件可以具有额外的属性、状态、生命周期方法等,或者修改组件的行为。

在函数式组件中,高阶组件可以用于以下目的:

  1. 代码复用: 高阶组件可以将通用的逻辑、功能或状态提取出来,应用于多个组件中,从而实现代码复用。这样可以避免在多个组件中重复编写相同的逻辑。
  2. 增强组件功能: 高阶组件可以在不修改原组件的情况下,通过封装它们,为组件添加额外的功能或修改其行为。这可以包括添加权限控制、数据获取、事件处理等。
  3. 属性代理和反向继承: 高阶组件可以通过属性代理或反向继承的方式,将额外的属性或状态传递给原组件。这可以用于为组件注入上下文、路由参数、认证状态等。
  4. 组件抽象和包装: 高阶组件可以将组件进行抽象和包装,以使其更具可复用性和通用性。通过将通用逻辑从组件中分离出来,使组件更专注于业务逻辑。

以下是一个简单的例子,演示了一个高阶组件的用途,通过给原组件添加鼠标悬停的功能:

import React from 'react';

// 高阶组件
const withHover = (Component) => {
  return class WithHover extends React.Component {
    state = {
      isHovered: false,
    };

    handleMouseOver = () => {
      this.setState({ isHovered: true });
    };

    handleMouseOut = () => {
      this.setState({ isHovered: false });
    };

    render() {
      const { isHovered } = this.state;

      return (
        <div onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
          <Component {...this.props} isHovered={isHovered} />
        </div>
      );
    }
  };
};

// 原组件
const MyComponent = ({ isHovered }) => {
  const text = isHovered ? 'Hovered!' : 'Not hovered';
  return <div>{text}</div>;
};

// 使用高阶组件包装原组件
const EnhancedComponent = withHover(MyComponent);

在上面的例子中,withHover是一个高阶组件,它接收一个组件作为参数并返回一个新的增强组件WithHover。WithHover组件通过监听鼠标事件来改变isHovered状态,并将isHovered作为属性传递给原组件MyComponent。这样,EnhancedComponent就具有了鼠标悬停的功能,而无需在原组件中编写相关的逻辑。

高阶组件是一种强大的工具,可以在函数式组件中实现可重用和灵活的功能增强。但需要注意,过度使用高阶组件可能会导致组件层次过深、逻辑复杂化等问题,因此需要合理使用,并遵循良好的组件设计原则。

81.如何处理组件之间的通信?请解释父子组件之间和非父子组件之间的通信方式。

在React中,组件之间的通信是实现功能交互和数据传递的重要方面。通常有两种类型的组件通信:父子组件之间的通信和非父子组件之间的通信。下面分别介绍它们的通信方式:

  1. 父子组件之间的通信:
    • 通过属性传递(Props):父组件可以通过props将数据传递给子组件。子组件通过props接收父组件传递的数据,并在自己的组件内部使用。
    • 通过回调函数:父组件可以将一个函数作为props传递给子组件,在子组件中调用该函数来将数据传递回父组件。
    • 通过Context API:父组件可以通过Context API创建一个上下文,将数据共享给所有子孙组件。子组件可以通过使用contextType或useContext来访问上下文中的数据。
  1. 非父子组件之间的通信:
    • 提升状态:可以将共享的状态提升到它们的共同父组件中,并通过props传递给需要通信的组件。
    • 使用全局状态管理工具:例如Redux、MobX等,可以在应用程序级别管理状态,并让不相关的组件通过订阅和分发状态的方式进行通信。
    • 使用事件总线:可以创建一个事件总线实例,允许组件之间发布和订阅事件。组件通过发布事件来发送消息,其他组件通过订阅事件来接收消息。
    • 使用第三方库:一些第三方库(如React PubSub、Emit.js等)提供了简单的发布-订阅或消息传递机制,用于在组件之间进行通信。

82.在函数式组件中如何处理表单(Form)的状态和事件处理?

在函数式组件中处理表单状态和事件处理可以使用useState Hook来管理表单的状态值。通过为表单元素的value属性提供状态值,并为onChange事件处理程序提供更新状态的函数,可以实现表单数据的双向绑定。例如:

const [username, setUsername] = useState('');

const handleUsernameChange = (event) => {
  setUsername(event.target.value);
};

return (
  <input type="text" value={username} onChange={handleUsernameChange} />
);

83.如何使用自定义Hook(Custom Hook)在函数式组件之间共享逻辑?

自定义Hook是一个函数,可以在函数式组件中定义并使用。它可以用于提取和复用组件之间共享的逻辑代码。通过自定义Hook,可以将状态管理、副作用处理等逻辑从组件中提取出来,并在多个组件中共享使用。自定义Hook以"use"开头,例如"useCounter"、"useFetchData"等。

84.什么是React的虚拟DOM(Virtual DOM)?它的作用是什么?


React的虚拟DOM(Virtual DOM)是React用于提高性能的一种技术。它是一个轻量级的、存在于内存中的表示真实DOM的JavaScript对象树。

虚拟DOM的作用是在组件状态发生变化时,通过比较新旧虚拟DOM的差异,最小化对实际DOM的操作,从而提高性能和用户体验。以下是虚拟DOM的工作原理:

  1. 初始化阶段:
    • React组件通过JSX语法构建虚拟DOM树,表示组件的UI结构。
    • 初始渲染时,虚拟DOM树会完全映射到实际的DOM树上。
  1. 更新阶段:
    • 当组件的状态发生变化时,React会生成一个新的虚拟DOM树,表示更新后的UI结构。
    • React会将新旧虚拟DOM树进行比较,找出两者之间的差异(称为协调过程,也叫Diff算法)。
    • 通过对比差异,React能够准确地知道哪些部分需要更新,并将更新的操作优化为最小的改变。
    • 最后,React会将差异应用到实际的DOM树上,只更新必要的部分,以保持DOM的同步。

85.watch和watchEffect区别

在Vue 3中,watch和watchEffect都是用于响应式地监听数据变化并执行相应操作的API,但它们之间存在一些区别。

  1. 使用方式:
    • watch函数需要明确指定要监听的数据源,并提供一个回调函数,当监听的数据发生变化时,回调函数会被触发。可以通过配置选项来进一步控制监听行为,如immediate选项来指定是否立即执行回调。
    • watchEffect函数接受一个回调函数作为参数,这个回调函数内部可以直接使用响应式的数据,并且会自动追踪其中使用到的依赖。当依赖发生变化时,回调函数会被重新执行。
  1. 监听方式:
    • watch允许精确地监听特定的数据源,可以监听一个或多个数据,甚至可以监听深层嵌套的属性。可以使用字符串、函数或数组来指定要监听的数据源。
    • watchEffect会自动追踪其内部回调函数中使用到的所有响应式数据的依赖关系,当其中任何一个依赖发生变化时,回调函数会被重新执行。
  1. 回调触发时机:
    • watch在监听的数据变化时触发回调函数,可以通过配置选项来控制何时触发,如立即执行、延迟执行或在下一轮更新周期中执行。
    • watchEffect会在组件渲染时立即执行一次回调函数,并且会在其内部依赖发生变化时再次执行回调函数。
  1. 返回值:
    • watch函数返回一个用于停止监听的函数。
    • watchEffect函数没有返回值,不能主动停止监听。

watch适合处理需要精确控制监听行为的场景,可以监听多个数据源并在数据变化时执行相应操作。而watchEffect更适合处理简单的场景,只需要追踪某个回调函数内使用的响应式数据,并在依赖变化时自动执行回调函数。

需要注意的是,在Vue 3中,推荐使用watchEffect来替代Vue 2中的watch,因为watchEffect更简洁、更强大,并且具备更好的性能优化。只有在需要更精细的控制监听行为时,才需要使用watch函数。

86.什么是Redux?它的作用是什么?

Redux是一个JavaScript应用程序状态管理库,用于管理应用程序的状态并使状态变化可预测和可追踪。它的作用是帮助开发者在复杂的应用程序中有效地管理状态,通过集中化的状态存储和明确的状态变化规则,实现可预测性、可维护性和组件解耦。Redux的核心原则是单一数据源和状态只读,通过触发action和纯函数的reducers来更新状态。通过Redux,开发者可以获得可预测性、可维护性、可扩展性和时间旅行调试等优势。总之,Redux为应用程序提供了一种可靠、一致和可追踪的状态管理机制,使开发者能够更轻松地构建复杂的应用程序。

87.Redux的核心原则是什么?

  1. 单一数据源(Single Source of Truth):整个应用程序的状态被存储在一个单一的JavaScript对象中,即Redux store。这意味着应用程序的所有状态都集中存储在一个地方,简化了状态的管理和访问。
  2. 状态是只读的(State is Read-Only):状态是只读的,即不能直接修改或变异状态。唯一改变状态的方法是触发一个action。这种限制确保了状态的可追踪性和可预测性。
  3. 纯函数更新状态(Changes are Made with Pure Functions):Redux中的reducer是纯函数,根据先前的状态和接收到的action来计算新的状态。纯函数意味着相同的输入将产生相同的输出,没有副作用,不会改变传入的参数。这种设计保证了状态变化的可预测性和可追踪性。

通过这些核心原则,Redux提供了一种可预测、可维护和可扩展的状态管理解决方案。它帮助开发者以一种统一的方式管理应用程序的状态,简化了状态的变化和传递过程,并提供了可预测性和可追踪性。

88.什么是Action和Reducer?

在Redux中,Action和Reducer是两个关键概念。

  1. Action(动作):示例:
    • Action是一个纯JavaScript对象,用于描述发生的事件或操作。
    • Action必须包含一个type属性,用于指示所执行的操作类型。
    • 除了type属性外,Action还可以包含其他自定义的属性,用于携带与操作相关的数据(例如payload)。
  1. Reducer(归纳器):示例:
    • Reducer是一个纯函数,根据先前的状态和接收到的Action来计算新的状态。
    • Reducer接收先前的状态(previous state)和当前的Action作为参数,返回一个新的状态(new state)。
    • Reducer负责处理特定类型的Action,根据Action的类型和payload来执行相应的状态更新逻辑。
const addTodo = (text) => {
  return {
    type: 'ADD_TODO',
    payload: text
  };
};


const initialState = {
  todos: []
};

const todosReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    case 'DELETE_TODO':
      // 处理删除todo的逻辑
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
    default:
      return state;
  }
};

通过Action和Reducer的配合,Redux实现了对应用程序状态的变化和管理。当组件触发一个Action时,Redux store将Action传递给相应的Reducer进行处理,Reducer根据Action的类型和payload计算出新的状态,并将新的状态更新到Redux store中。这样,整个应用程序的状态就可以被一致地管理和访问,组件可以从Redux store中获取更新后的状态并相应地进行渲染。


89.Redux的工作流程是什么?

Redux的工作流程可以简单概括为以下几个步骤:

  1. 组件触发Action:应用程序中的组件通过调用Action创建函数来触发一个Action。Action是一个纯JavaScript对象,用于描述发生的事件或操作。
  2. Action被发送到Reducer:Redux store接收到触发的Action,并将Action传递给相应的Reducer。Reducer是纯函数,根据Action的类型和payload来计算新的状态。
  3. Reducer更新状态:Reducer根据Action的类型和payload对先前的状态进行计算,并返回一个新的状态。Reducer应该是纯函数,即相同的输入将产生相同的输出,没有副作用。
  4. 状态更新到Redux Store:更新后的状态被存储在Redux store中,替换原有的状态。Redux store是一个包含整个应用程序状态的JavaScript对象。
  5. 组件订阅状态变化:组件可以通过连接到Redux store或使用React Redux Hooks来订阅Redux store的状态变化。当状态发生变化时,相关的订阅函数将被触发。
  6. 组件重新渲染:当Redux store中的状态发生变化时,与该状态相关的订阅的组件将重新渲染。这样,组件将显示更新后的状态。

通过这个工作流程,Redux提供了一种可预测、可追踪和统一的状态管理机制。组件通过触发Action来通知应用程序中的状态变化,Reducer根据Action的类型和payload来计算新的状态,并将更新后的状态存储在Redux store中。组件通过订阅Redux store获取更新后的状态,并相应地进行渲染,以反映最新的状态变化。这种单向的数据流和统一的状态管理有助于提高应用程序的可维护性和可扩展性。

90.什么是Redux中间件?它的作用是什么?

Redux中间件是位于action被发起后到达reducer之前的扩展点。它允许开发者在处理action的过程中进行额外的操作,例如异步处理、日志记录、错误处理、路由导航等。

中间件是一个函数,它位于Redux的action和reducer之间,拦截并处理action。当一个action被触发时,它首先经过中间件,然后再到达reducer进行状态更新。

中间件的作用是提供了一种可插拔的机制,使开发者能够在Redux流程中添加自定义的逻辑和处理步骤。它可以用于处理异步操作,例如发起网络请求并在请求返回后再更新状态。通过中间件,开发者可以将异步操作封装在action中,使代码更加清晰和可维护。

常用的Redux中间件有:

  1. Redux Thunk:用于处理异步操作。它允许action创建函数返回一个函数,而不是一个普通的action对象。这个返回的函数接受dispatch作为参数,可以进行异步操作,并在异步操作完成后触发真正的action。
  2. Redux Saga:使用Generator函数来处理异步操作。通过创建一个Saga来管理异步流程,Saga可以监听action,并在满足特定条件时执行相应的操作。
  3. Redux Logger:用于在控制台中记录Redux的action和状态变化,方便开发过程中的调试和日志记录。

这些中间件提供了一些常见的功能和处理场景,但也可以自定义和扩展中间件来满足特定的需求。通过使用Redux中间件,开发者可以将复杂的逻辑和异步操作集成到Redux流程中,使代码更加灵活、可扩展和可维护。

91.useContext有哪些作用?

useContext 是 React 中的一个 Hook,它用于在函数组件中获取和使用上下文(Context)。使用 useContext 可以方便地访问在父组件中通过 React.createContext 创建的上下文。

useContext 的主要作用如下:

  1. 获取上下文值:通过 useContext 可以获取当前组件树中与指定上下文关联的值。当组件需要访问全局数据或共享状态时,可以使用 useContext 来获取上下文中的值,避免了通过一层层的组件传递 props 的方式。
  2. 避免组件嵌套:使用上下文可以避免组件嵌套过深的问题。当多个组件需要访问同一个数据时,可以将这些组件嵌套在一个提供上下文的组件中,然后使用 useContext 在子组件中直接获取数据,避免了传递多层嵌套的 props。
  3. 简化组件间通信:上下文提供了一种简化组件间通信的方式。通过将共享数据放置在上下文中,不同组件可以直接获取并使用这些数据,而无需显式地通过 props 或事件回调来传递和更新数据。

使用 useContext 的基本流程如下:

  1. 在父组件中创建上下文:使用 React.createContext 创建一个上下文对象,并通过 Provider 组件将共享的数据传递给子组件。
  2. 在子组件中使用上下文:使用 useContext Hook 在函数组件中获取上下文中的值。

以下是一个简单示例:

// 创建上下文
const MyContext = React.createContext();

// 父组件
function ParentComponent() {
  const data = "Hello, World!";

  return (
    <MyContext.Provider value={data}>
      <ChildComponent />
    </MyContext.Provider>
  );
}

// 子组件
function ChildComponent() {
  const value = useContext(MyContext);

  return <p>{value}</p>;
}

在上面的示例中,ParentComponent 创建了一个上下文 MyContext,并通过 Provider 组件将值 "Hello, World!" 传递给子组件 ChildComponent。在 ChildComponent 中使用 useContext 获取上下文中的值,并在 <p> 元素中显示。

总结:useContext 是 React 中的一个 Hook,用于在函数组件中获取和使用上下文。它可以方便地访问上下文中的共享数据,避免了组件嵌套和显式的数据传递。

92.你用webpack做过哪些配置?


在实际项目中,Webpack 的配置可以根据具体需求和项目特点而有所变化。以下是一些在项目中比较常用的 Webpack 配置:

  1. 入口点(Entry Point):指定应用程序的入口文件或入口模块,通常是项目的主 JavaScript 文件。
  2. 输出(Output):配置打包后的文件输出路径和文件名,通常是一个或多个输出文件。
  3. 加载器(Loaders):使用不同的加载器来处理项目中的不同类型文件。常见的加载器包括:
    • Babel Loader:处理 JavaScript 文件,使其兼容旧版浏览器或支持最新的 JavaScript 语法特性。
    • CSS/Sass Loader:处理 CSS 或 Sass 文件,并将其转换为浏览器可识别的样式。
    • File Loader / url-loader:处理静态资源文件(如图片、字体等)并将其复制到输出目录。
  1. 插件(Plugins):通过插件可以扩展和定制 Webpack 的功能。常见的插件包括:
    • HtmlWebpackPlugin:生成 HTML 文件,并自动将打包后的资源文件注入到 HTML 中。
    • MiniCssExtractPlugin:将 CSS 提取为独立的文件,而不是以内联方式注入到 JavaScript 文件中。
    • DefinePlugin:定义全局常量,可以在代码中使用。
    • CopyWebpackPlugin:将静态资源复制到输出目录。
    • CleanWebpackPlugin:在每次构建之前清理输出目录。
  1. 解析(Resolve):配置模块解析规则,包括指定模块的搜索路径、解析文件后缀等。
  2. 代码分割(Code Splitting):将应用程序代码拆分为多个文件,以实现按需加载和提高页面加载性能。可以使用动态 import 语法、配置 optimization.splitChunks 等实现代码分割。
  3. 文件指纹(File Hashing):为生成的文件添加哈希值,以便于缓存管理和版本控制。可以通过配置选项在输出文件名中添加哈希,如使用 chunkhash 或 contenthash。
  4. 开发服务器(DevServer):在开发环境中使用开发服务器,支持自动刷新和热模块替换等功能。可以配置选项如端口号、代理设置、启用热模块替换等。
  5. 优化(Optimization):配置代码优化策略,包括压缩代码、去除冗余、代码分割等。常见的配置选项包括 optimization.minimize、optimization.splitChunks、optimization.runtimeChunk 等。

这些是在项目中常用的 Webpack 配置选项,根据具体项目需求可能会有所不同。可以根据项目的具体情况和需求,选择并配置适合的选项和插件,以满足项目的构建和打包需求。

93.常见的状态码有哪些?

  1. 200 OK:请求成功。服务器成功处理了请求,并返回相应的内容。
  2. 301 Moved Permanently:永久重定向。请求的资源已被永久移动到新位置。
  3. 302 Found / 303 See Other:临时重定向。请求的资源临时移动到其他位置。
  4. 304 Not Modified:未修改。客户端的缓存副本仍然有效,不需要重新传输。
  5. 400 Bad Request:错误的请求。请求中存在语法错误或无法理解的请求。
  6. 401 Unauthorized:未授权。请求需要用户认证,需要提供有效的身份验证凭据。
  7. 403 Forbidden:禁止访问。服务器理解请求,但拒绝执行该请求。
  8. 404 Not Found:未找到。服务器无法找到请求的资源。
  9. 500 Internal Server Error:服务器内部错误。服务器遇到意外错误,无法完成请求。
  10. 502 Bad Gateway:错误的网关。作为代理或网关的服务器从上游服务器收到无效的响应。
  11. 503 Service Unavailable:服务不可用。服务器暂时过载或维护,无法处理请求。

这些状态码是 HTTP 协议定义的标准状态码,浏览器在接收到服务器返回的响应时,会根据状态码来判断请求的处理结果。每个状态码都有特定的含义,用于指示请求是否成功、失败或需要进一步操作。开发人员可以根据不同的状态码来进行相应的处理和调试。

94.怎么检查项目需要做优化?


要检查项目是否需要进行优化,可以考虑以下几个方面:

  1. 性能指标:通过浏览器开发者工具或性能分析工具来检查项目的加载时间、页面渲染时间、资源大小等性能指标。如果发现页面加载缓慢、渲染时间过长或资源文件过大,可能需要进行性能优化。
  2. 网络请求:检查项目中的网络请求情况,包括请求的数量、请求的大小、请求的并行程度等。如果请求过多或请求的大小过大,可能需要优化网络请求,例如合并文件、压缩资源、使用缓存等。
  3. JavaScript 执行:检查 JavaScript 代码的性能情况,包括脚本文件的大小、执行时间、内存占用等。如果 JavaScript 代码量过大或执行时间过长,可能需要进行代码优化,例如删除冗余代码、使用更高效的算法等。
  4. 图片优化:检查项目中的图片使用情况,包括图片的格式、大小、加载方式等。如果图片过大或加载方式不合理,可以考虑进行图片优化,例如压缩图片、使用适当的图片格式、懒加载等。
  5. 缓存策略:检查项目中的缓存策略是否合理,包括浏览器缓存、CDN 缓存等。如果缓存设置不正确或未充分利用缓存机制,可以考虑优化缓存策略,减少重复请求和数据传输。
  6. 响应式设计:检查项目在不同设备上的响应式设计情况,包括移动端和桌面端的适配性。如果项目在某些设备上显示效果不佳或性能下降,可以考虑进行响应式优化,例如使用媒体查询、选择合适的布局方式等。
  7. 代码分割和按需加载:检查项目的代码结构和模块化情况,是否存在大而复杂的代码块。如果项目代码过于庞大或加载整个应用程序的代码影响性能,可以考虑进行代码分割和按需加载,将代码拆分为更小的模块,实现按需加载。
  8. 前端工具分析:使用一些前端工具和插件,如 Lighthouse、Webpack Bundle Analyzer、PageSpeed Insights 等,进行项目的分析和评估,获取详细的优化建议和指导。

通过综合考虑上述方面的情况,可以评估项目的优化需求,并采取相应的优化策略和措施来提升项目的性能和用户体验。

95.谈谈你对typecscript的理解
 

TypeScript是一种由微软开发的开源编程语言,它是JavaScript的超集。TypeScript添加了静态类型检查和一些新的语言功能,以提高JavaScript的开发效率和代码质量。

下面是我对TypeScript的一些理解:

  1. 静态类型检查:TypeScript引入了静态类型系统,允许开发人员在编写代码时指定变量、函数参数和返回值的类型。这样可以在编译阶段就能捕获一些常见的错误,提供更好的代码提示和自动补全,并提高代码的可维护性和可读性。
  2. 类型注解和推断:TypeScript通过类型注解的方式,可以明确地标注变量的类型。同时,它还可以根据上下文自动推断变量的类型,减少了一部分冗余的类型注解。类型注解可以应用于变量、函数参数、函数返回值等,使得代码的意图更加清晰。
  3. ES6+支持:TypeScript支持最新的ECMAScript标准(ES6、ES7等),并且在此基础上添加了一些额外的功能,如类、模块、箭头函数等。这使得开发人员可以在TypeScript中使用JavaScript的最新特性,同时还能获得更好的类型检查。
  4. 类型定义文件:TypeScript通过类型定义文件(.d.ts)来描述JavaScript库、框架或模块的类型信息。这些类型定义文件提供了类型声明,使得TypeScript可以在使用第三方库时进行类型检查和代码补全。这个功能对于大型项目和团队合作非常有用。
  5. 工具生态系统:TypeScript具有强大的工具生态系统,包括编辑器支持(如Visual Studio Code)、构建工具(如Webpack)、调试器和各种开发工具。这些工具提供了丰富的功能,如自动编译、错误检测、重构支持等,提高了开发效率。

总的来说,TypeScript是一种强类型的编程语言,它通过静态类型检查和其他语言特性提供了更好的开发体验和代码质量。它与JavaScript兼容,并且可以无缝地集成到JavaScript项目中,是现代Web应用开发中广泛采用的语言之一。

96.怎么在Vue或Js中实现一个$nextTick?

在Vue.js或JavaScript中实现类似于Vue.js中的 $nextTick 的功能可以通过使用 Promise 或者 setTimeout 来实现。这两种方法都可以将代码推迟到下一个事件循环中执行,以确保在DOM更新之后执行相应的操作。

  1. 使用Promise:
function nextTick() {
  return new Promise(resolve => {
    if (typeof Promise !== 'undefined') {
      Promise.resolve().then(resolve);
    } else {
      setTimeout(resolve, 0);
    }
  });
}

// 使用方式
nextTick().then(() => {
  // 在DOM更新后执行的代码
});

上述代码创建了一个返回Promise的nextTick函数。它首先检查是否原生支持Promise,如果支持,则使用Promise的resolve().then()方法将代码推迟到下一个微任务中执行。否则,使用setTimeout将代码推迟到下一个宏任务中执行。

  1. 使用setTimeout:
function nextTick(callback) {
  setTimeout(callback, 0);
}

// 使用方式
nextTick(() => {
  // 在DOM更新后执行的代码
});

上述代码中的nextTick函数使用setTimeout将回调函数推迟到下一个宏任务中执行。

无论你选择使用Promise还是setTimeout,这些方法都可以在Vue.js或JavaScript中实现一个类似于$nextTick的功能。它们确保在DOM更新之后执行相应的操作,以便你可以获取到最新的DOM状态。

97.常用伪类元素有哪些?

常用的伪类(pseudo-class)元素如下:

  1. :hover - 鼠标悬停时应用的样式。
  2. :active - 当元素被激活或按下时应用的样式。
  3. :focus - 当元素获得焦点时应用的样式。
  4. :visited - 已访问链接的样式。
  5. :link - 未访问链接的样式。
  6. :first-child - 选择作为其父元素的第一个子元素的元素。
  7. :last-child - 选择作为其父元素的最后一个子元素的元素。
  8. :nth-child(n) - 选择作为其父元素的第n个子元素的元素。
  9. :nth-last-child(n) - 选择作为其父元素的倒数第n个子元素的元素。
  10. :first-of-type - 选择作为其父元素的特定类型的第一个子元素的元素。
  11. :last-of-type - 选择作为其父元素的特定类型的最后一个子元素的元素。
  12. :nth-of-type(n) - 选择作为其父元素的特定类型的第n个子元素的元素。
  13. :nth-last-of-type(n) - 选择作为其父元素的特定类型的倒数第n个子元素的元素。
  14. :empty - 选择没有任何子元素的元素。
  15. :checked - 选择已选中的表单元素,如复选框或单选按钮。

这些是常见的伪类元素,它们广泛应用于CSS样式化中,可以根据不同的状态和条件选择元素,并应用相应的样式。你可以根据需要使用这些伪类元素来实现特定的效果和交互行为。

98.link标签和import的区别?

<link>标签和@import在CSS中有不同的作用和使用方式:

<link>标签:

  • <link>标签是HTML中用于在文档中引入外部资源的标签,主要用于引入CSS文件。
  • 它可以通过href属性指定外部CSS文件的URL。
  • <link>标签可以放置在HTML文档的<head>标签内或文档的任何位置。
  • <link>标签支持并行加载,即同时加载多个外部资源,不会阻塞页面的渲染。
  • 使用<link>标签可以在HTML文档中引入其他外部资源,如图标、字体等。

示例:

<link rel="stylesheet" href="styles.css">

@import:

  • @import是CSS中用于引入外部CSS文件的规则,通常放置在CSS文件的顶部。
  • 它在CSS文件中使用,通过@import后跟CSS文件的URL来引入外部CSS文件。
  • @import必须放置在CSS文件的顶部,不允许放在其他位置。
  • @import引入的外部CSS文件会在页面渲染之前加载,并且会阻塞页面的渲染。
  • 在使用@import引入CSS文件时,如果存在多个@import规则,每个引入的文件都会以串行方式加载,加载顺序会影响页面的性能。

示例:

@import url("styles.css");

主要区别:

  • <link>是HTML标签,用于在HTML文档中引入外部资源,主要用于引入CSS文件。
  • @import是CSS规则,用于在CSS文件中引入其他外部CSS文件。
  • <link>标签支持并行加载,不会阻塞页面的渲染,而@import会阻塞页面的渲染。
  • <link>标签可以放置在HTML文档的任何位置,而@import必须放置在CSS文件的顶部。

一般来说,推荐使用<link>标签来引入外部CSS文件,因为它更灵活、效果更好,并且支持并行加载。而@import在一些特殊的情况下可能会有一些特定的用途,例如在使用CSS预处理器时。

99.在Vue生命周期每个阶段能做什么操作?

在Vue.js的每个生命周期阶段,你可以执行不同的操作。以下是每个阶段的一些常见操作示例:

  1. beforeCreate:
    • 设置组件的初始数据。
    • 执行一些初始化操作,如创建全局事件总线、配置全局插件等。
  1. created:
    • 发起网络请求,获取组件初始化所需的数据。
    • 订阅事件,以便在后续阶段响应事件。
    • 对初始数据进行进一步处理,如计算属性、监听属性等。
  1. beforeMount:
    • 对数据进行最终的修改或处理。
    • 执行一些准备工作,如计算DOM元素的尺寸、初始化第三方库等。
  1. mounted:
    • 执行DOM操作,如获取DOM元素、绑定事件监听器。
    • 初始化第三方库,如图表库、地图库等。
    • 发起与DOM相关的操作,如自定义动画、调整布局等。
  1. beforeUpdate:
    • 对数据进行检查或预处理。
    • 在数据更新前执行一些操作,如取消订阅、清除定时器等。
  1. updated:
    • 对更新后的DOM进行操作,如更新样式、执行动画等。
    • 与第三方库进行交互,以响应数据的变化。
  1. beforeDestroy:
    • 清理定时器、取消订阅等资源。
    • 解除对全局事件的监听。
    • 执行一些销毁前的清理工作。
  1. destroyed:
    • 执行最终的清理工作,如释放内存、取消绑定事件等。

此外,Vue.js还提供了一些其他的生命周期钩子函数,如errorCaptured和renderTracked,用于处理错误和渲染跟踪等特定情况下的操作。

100.Vue样式隔离怎么实现?


在Vue中,你可以通过以下方法自己实现样式隔离:

  1. CSS Modules:使用CSS Modules可以实现样式的模块化和隔离。将每个组件的样式文件命名为*.module.css或*.module.scss,然后在组件中引入样式并通过类名来应用。
<template>
  <div class="container">
  <p class="text">Hello, World!</p>
  </div>
  </template>

  <style scoped module>
  .container {
  background-color: red;
}

.text {
  color: blue;
}
</style>

使用scoped属性和module属性可以同时实现样式的作用域和模块化。在组件中使用$style对象来引用CSS Modules中定义的类名:使用CSS Modules时,类名会被自动转换成独一无二的值,确保组件样式的唯一性,避免了全局类名的冲突。

  1. 动态生成唯一的类名:你可以在组件的<style>标签中通过计算属性或方法生成唯一的类名,并在模板中使用这些类名。这样可以确保组件样式的唯一性。
<template>
  <div :class="containerClass">
  <p :class="textClass">Hello, World!</p>
  </div>
  </template>

  <style>
export default {
  computed: {
    containerClass() {
      return `container-${this._uid}`;
    },
    textClass() {
      return `text-${this._uid}`;
    }
  }
}
  </style>

在上述示例中,通过使用组件实例的_uid属性来生成唯一的类名。这样可以确保每个组件的样式类名不会相互冲突。

  1. CSS-in-JS:你可以使用CSS-in-JS库来在Vue组件中直接编写样式,并将其作为JavaScript对象注入到组件中。这种方式使得样式与组件的作用域紧密结合,实现了样式的隔离。
<template>
  <div :class="containerClass">
  <p :class="textClass">Hello, World!</p>
  </div>
  </template>

  <script>
  import { css } from 'emotion';

export default {
  computed: {
    containerClass() {
      return css`
        background-color: red;
      `;
    },
    textClass() {
      return css`
        color: blue;
      `;
    }
  }
}
  </script>

在上述示例中,使用emotion库将样式定义为CSS-in-JS的形式,并将其作为计算属性中的函数返回。这样可以实现样式的隔离和动态生成。

通过以上方法之一,你可以自己在Vue中实现样式的隔离,确保每个组件的样式不会相互干扰。根据项目的需求和个人偏好,选择适合的方式进行样式隔离。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值