2025面试大全(28)

197 阅读48分钟

1. display:none与visibility:hidden 有什么区别?

display:nonevisibility:hidden都是CSS属性,用于控制元素的显示与隐藏,但它们之间有一些关键的区别:

  1. 渲染行为
    • display:none:元素不会在页面中占据任何空间,如同它根本不存在一样。浏览器不会渲染这个元素,也不会为其保留位置。
    • visibility:hidden:元素虽然不可见,但仍然占据它原本的位置。浏览器会渲染这个元素,只是不显示出来。
  2. 子元素继承
    • display:none:当父元素设置display:none时,其所有子元素也会被隐藏,并且不会出现在文档中。
    • visibility:hidden:子元素会继承visibility属性,但如果子元素显式设置visibility:visible,则可以覆盖父元素的hidden设置,使自身可见。
  3. 动画和过渡
    • display:none:由于元素不在文档流中,因此无法对其应用动画或过渡效果。
    • visibility:hidden:可以对元素应用动画或过渡效果,因为元素仍然在文档流中。
  4. 性能影响
    • display:none:在显示和隐藏频繁切换时,由于浏览器需要重新渲染页面,可能会对性能产生较大影响。
    • visibility:hidden:由于元素位置保持不变,切换显示状态时对性能的影响较小。
  5. 事件触发
    • display:none:元素不会触发任何事件,因为它们不在文档流中。
    • visibility:hidden:元素仍然可以触发事件,如点击事件等。 根据具体需求,可以选择使用display:nonevisibility:hidden。如果需要完全从页面中移除元素,使用display:none;如果只是暂时隐藏元素但希望保留其位置,使用visibility:hidden

30. 使用Promise实现每隔1秒输出1,2,3

要使用Promise实现每隔1秒输出1, 2, 3,我们可以创建一个函数,该函数返回一个Promise,该Promise在指定的时间后解决。然后,我们可以链式调用这个函数三次,每次输出一个数字。 以下是一个简单的实现:

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
function logNumber(number) {
  return delay(1000).then(() => console.log(number));
}
logNumber(1)
  .then(() => logNumber(2))
  .then(() => logNumber(3));

在这个例子中,delay函数接受一个毫秒数ms,然后返回一个Promise,该Promise在ms毫秒后解决。logNumber函数接受一个数字,使用delay函数等待1秒,然后输出该数字。 我们首先调用logNumber(1),它会在1秒后输出1。然后,我们链式调用.then()来继续执行logNumber(2),它会在另一个1秒后输出2。最后,我们再次链式调用.then()来执行logNumber(3),它会在再一个1秒后输出3。 这样,我们就实现了每隔1秒输出1, 2, 3的要求。

3. webpack treeShaking机制的原理是什么?

Webpack 的 Tree Shaking 机制是一种优化技术,用于删除 JavaScript 代码中的未使用部分,以减少打包后的文件大小。其原理主要基于以下几个方面:

  1. 静态结构分析
    • Webpack 通过静态分析模块的导入和导出语句,来确定哪些代码是实际使用的,哪些不是。
    • 这要求代码必须是静态的,即导入和导出语句不能依赖于运行时的条件。因此,Tree Shaking 更适用于 ES6 模块(importexport),而不是 CommonJS 模块(requiremodule.exports)。
  2. ES6 模块语法
    • ES6 模块具有静态的导入和导出语法,这使得 Webpack 可以在编译时确定模块的依赖关系。
    • 与 CommonJS 模块不同,ES6 模块的导入和导出是最终确定的,不会在运行时改变。
  3. 副作用分析
    • Webpack 会分析模块是否有副作用。副作用是指模块执行时除了导出值之外的影响,如修改全局变量、执行 IO 操作等。
    • 如果一个模块没有副作用,并且没有任何其他模块导入它的导出,那么这个模块可以被安全地删除。
  4. 压缩阶段优化
    • 在 Webpack 的压缩阶段(通常使用 UglifyJS 或 TerserPlugin),未使用的代码会被删除。
    • 这包括未使用的变量、函数和导入的模块。
  5. 使用工具支持
    • Webpack 需要与其他工具配合使用以实现 Tree Shaking,如 Babel 需要配置以保留 ES6 模块语法,而不是转换为 CommonJS。
  6. 代码分割
    • Webpack 的代码分割功能可以与 Tree Shaking 配合使用,以进一步优化打包结果,只加载需要的代码。 要启用 Tree Shaking,通常需要确保:
  • 使用 ES6 模块语法。
  • 在 Webpack 配置中启用压缩插件,如 TerserPlugin。
  • 确保没有副作用或者正确标记有副作用的模块(可以使用 package.json 中的 sideEffects 字段)。 通过这些机制,Webpack 的 Tree Shaking 能够有效地删除未使用的代码,从而减小打包文件的大小,提高应用的性能。

4. 无重复字符的最长子串

要实现一个无重复字符的最长子串,我们可以使用滑动窗口的技术。以下是一个JavaScript函数,它接受一个字符串作为输入,并返回该字符串中的无重复字符的最长子串的长度:

function lengthOfLongestSubstring(s) {
  let maxLength = 0;
  let start = 0;
  const charIndexMap = {};
  for (let end = 0; end < s.length; end++) {
    const currentChar = s[end];
    if (charIndexMap[currentChar] >= start) {
      start = charIndexMap[currentChar] + 1;
    }
    charIndexMap[currentChar] = end;
    maxLength = Math.max(maxLength, end - start + 1);
  }
  return maxLength;
}
// 示例
const inputString = "abcabcbb";
const result = lengthOfLongestSubstring(inputString);
console.log(result); // 输出: 3,因为"abc"是无重复字符的最长子串

这个函数的工作原理如下:

  1. 初始化maxLength为0,表示最长子串的长度。
  2. 初始化start为0,表示滑动窗口的起始位置。
  3. 创建一个空对象charIndexMap,用于存储字符及其在字符串中的索引。 遍历字符串中的每个字符:
  • 如果当前字符已经在charIndexMap中,并且它的索引大于或等于start,那么我们需要移动start到重复字符的下一个位置,以避免重复。
  • 更新当前字符在charIndexMap中的索引。
  • 计算当前无重复字符子串的长度,并更新maxLength if necessary。 最后,函数返回maxLength,即无重复字符的最长子串的长度。 这个算法的时间复杂度是O(n),其中n是字符串的长度,因为每个字符只被遍历一次。空间复杂度也是O(min(m, n)),其中m是字符集的大小,因为charIndexMap存储的字符数量最多不会超过字符集的大小。

5. 两数相加

在JavaScript中,两数相加非常简单,可以直接使用加号+操作符。以下是一个基本的示例:

function addTwoNumbers(a, b) {
  return a + b;
}
// 示例
const num1 = 5;
const num2 = 10;
const sum = addTwoNumbers(num1, num2);
console.log(sum); // 输出: 15

在这个示例中,我们定义了一个函数addTwoNumbers,它接受两个参数ab,并返回它们的和。然后,我们创建了两个数字num1num2,并调用addTwoNumbers函数来计算它们的和,最后将结果打印到控制台。 如果你想要处理的是字符串形式的数字,你需要先将它们转换为数字,然后再相加:

function addTwoNumbersAsString(a, b) {
  const numA = parseFloat(a);
  const numB = parseFloat(b);
  return numA + numB;
}
// 示例
const strNum1 = "5";
const strNum2 = "10";
const sumFromString = addTwoNumbersAsString(strNum1, strNum2);
console.log(sumFromString); // 输出: 15

在这个例子中,我们使用parseFloat函数将字符串转换为浮点数,然后再进行相加。如果你确定输入的字符串总是表示整数,你也可以使用parseInt函数来代替parseFloat。 请注意,如果输入的字符串不能转换为有效的数字,parseFloat会返回NaN(Not-a-Number),这会导致加法结果也是NaN。因此,在实际应用中,你可能需要添加错误处理逻辑来处理这种情况。

6. 两数之和

"两数之和"通常指的是两个数字相加的结果。在不同的上下文中,这个表达可能有所不同,但基本概念是相同的。以下是几种不同情境下计算两数之和的方法:

算术运算

在基本的算术运算中,两数之和就是将两个数字相加:

2 + 3 = 5

编程语言

在不同的编程语言中,计算两数之和的方式类似,都是使用加号+操作符。以下是几个示例:

Python
def add_two_numbers(a, b):
    return a + b
num1 = 2
num2 = 3
sum = add_two_numbers(num1, num2)
print(sum)  # 输出: 5
JavaScript
function addTwoNumbers(a, b) {
    return a + b;
}
let num1 = 2;
let num2 = 3;
let sum = addTwoNumbers(num1, num2);
console.log(sum); // 输出: 5
Java
public class Sum {
    public static int addTwoNumbers(int a, int b) {
        return a + b;
    }
    public static void main(String[] args) {
        int num1 = 2;
        int num2 = 3;
        int sum = addTwoNumbers(num1, num2);
        System.out.println(sum); // 输出: 5
    }
}

数学问题

在数学问题中,"两数之和"可能是一个问题的一部分,例如: 问题:找出两个数,它们的和为特定值,比如10。 解决方案:这个问题有无数解,例如:

  • 1 和 9
  • 2 和 8
  • 3 和 7
  • ...

数据结构

在数据结构中,例如数组或列表,"两数之和"可能指的是找出数组中两个数,它们的和等于一个给定的值。这是一个常见的问题,称为"两数之和问题"。 示例:给定一个数组[2, 7, 11, 15]和一个目标值9,找出数组中两个数,使它们的和等于目标值。 解决方案:在这个例子中,答案是[2, 7],因为2 + 7 = 9。 这个问题可以通过哈希表在O(n)时间复杂度内解决:

def two_sum(nums, target):
    num_to_index = {}
    for i, num in enumerate(nums):
        complement = target - num
        if complement in num_to_index:
            return [num_to_index[complement], i]
        num_to_index[num] = i
    return []
# 示例
nums = [2, 7, 11, 15]
target = 9
print(two_sum(nums, target))  # 输出: [0, 1],表示索引0和索引1的元素之和为9

在这个函数中,我们遍历数组,对于每个元素,我们计算它与目标值的差(即补数),然后检查这个补数是否已经在哈希表中。如果在,我们就找到了一对和为目标的数。如果不在,我们将当前数和它的索引添加到哈希表中。 根据你的具体需求,你可以选择适合的方法来计算两数之和。

7. 你知道哪些css模块化的方案?

CSS模块化是现代前端开发中的一个重要概念,它有助于提高样式的可维护性、可重用性和可扩展性。以下是一些常见的CSS模块化方案:

  1. CSS Sprites
    • 将多个小图像合并到一张大图中,通过背景定位显示需要的部分。
    • 减少HTTP请求,提高加载速度。
  2. BEM(Block Element Modifier)
    • 一种命名约定,通过特定的命名规则来区分块(Block)、元素(Element)和修饰符(Modifier)。
    • 例如:block__element--modifier
    • 强调了组件化的思想,有助于维护和复用。
  3. OOCSS(Object-Oriented CSS)
    • 面向对象的CSS,强调分离结构和皮肤、容器和内容。
    • 提倡使用类名来表示功能,而不是具体的内容。
  4. SMACSS(Scalable and Modular Architecture for CSS)
    • 可扩展和模块化的CSS架构。
    • 将CSS分为基础、布局、模块、状态和主题五类。
    • 提供了一套组织CSS文件的规则。
  5. CSS Modules
    • 将CSS类名局部化,避免全局冲突。
    • 通常与构建工具(如Webpack)一起使用,自动生成唯一的类名。
    • 在JavaScript文件中导入和使用CSS模块。
  6. Styled-Components(用于React):
    • 一种CSS-in-JS库,允许在JavaScript中编写CSS。
    • 样式被封装在组件中,不会泄露到全局。
    • 支持自动完成、主题和服务器端渲染。
  7. Sass/SCSS
    • CSS预处理器,提供了变量、嵌套、混合(Mixins)、继承等高级功能。
    • 有助于组织大型项目的CSS代码。
  8. Less
    • 另一种CSS预处理器,与Sass类似,提供了变量、嵌套、混合等功能。
    • 广泛应用于Vue.js等框架。
  9. Stylus
    • 一种富有表现力的、动态的、健壮的CSS预处理器。
    • 提供了类似于Sass和Less的功能,但语法更简洁。
  10. Tailwind CSS
    • 一个实用类优先(Utility-First)的CSS框架。
    • 提供了大量可以直接应用于HTML元素的类名,以实现快速布局和样式设计。
  11. BootstrapFoundation 等UI框架:
    • 提供了一套预定义的CSS类和组件。
    • 可以快速搭建响应式网页,但也需要考虑定制化和样式冲突的问题。
  12. PostCSS
    • 一个使用JavaScript插件转换CSS的工具。
    • 可以用来实现自动添加浏览器前缀、CSS变量、嵌套等特性。
    • 可以与其他预处理器或模块化方案结合使用。 选择哪种CSS模块化方案取决于项目的需求、团队习惯和偏好。一些方案(如BEM、OOCSS、SMACSS)更注重命名和组织规则,而其他方案(如CSS Modules、Styled-Components、Sass)则提供了更多的编程功能和集成选项。

8. 页面统计数据中,常用的 PV、UV 指标分别是什么?

**PV(Page View)UV(Unique Visitor)**是网页统计数据中常用的两个指标,它们分别表示:

  1. PV(Page View)
    • 含义:页面浏览量或页面访问量。
    • 解释:指在一定时间内,用户打开或刷新某个网页的次数。每次打开或刷新页面,PV值都会增加。
    • 用途:用于衡量网页的受欢迎程度和用户访问频率。高PV值通常意味着网页内容吸引人,用户访问频繁。
  2. UV(Unique Visitor)
    • 含义:独立访客数。
    • 解释:指在一定时间内,访问网页的唯一用户数量。无论用户访问多少次,只要是在同一设备上,都只算作一个UV。
    • 用途:用于衡量网页的真实用户数量,反映网站的受众规模。UV值有助于了解网站的独立用户数,避免重复计数。 区别
  • 计数方式:PV是按页面访问次数计数,而UV是按独立用户数量计数。
  • 重复访问:PV会记录用户的每次访问,包括重复访问;UV则只记录唯一用户的首次访问。
  • 反映内容:PV反映的是页面的访问热度,UV反映的是网站的受众规模。 补充
  • IP:有时还会提到IP指标,它表示独立IP地址的数量。与UV类似,但IP更侧重于从网络地址的角度统计独立访问者。
  • Session:会话数,指用户在一段时间内与网站的互动过程。一个用户可以产生多个会话。 这些指标共同用于网页统计分析,帮助网站管理员和市场营销人员了解网站性能、用户行为和营销效果。

9. 64匹马,8个赛道,找出最快的4匹马,要比赛多少轮?

为了从64匹马中找出最快的4匹马,并且有8个赛道可用,我们可以通过以下步骤进行:

第一轮

将64匹马分成8组,每组8匹,进行比赛。这样将得到每组的排名,但还无法确定具体的前4名。

第二轮

将第一轮中每组的第1名(共8匹马)进行比赛。这轮比赛可以确定最快的马。

第三轮

为了确定接下来的3匹最快马,我们需要进一步比赛。第三轮的比赛安排如下:

  • 第二轮的第2、3、4名(3匹马)
  • 第一轮中,第二轮第1名所在组的第2、3名(2匹马)
  • 第一轮中,第二轮第2名所在组的第2名(1匹马)
  • 第一轮中,第二轮第3名所在组的第2名(1匹马) 这样,第三轮共有8匹马参加比赛。

第四轮

根据第三轮的结果,我们可以确定前4名的剩余位置。第四轮的比赛安排如下:

  • 第三轮的第2、3、4名(3匹马)
  • 前几轮中表现较好的马,但尚未确定前4名的(5匹马) 这样,第四轮也有8匹马参加比赛。

总结

通过以上四轮比赛,我们可以确定最快的4匹马。因此,总共需要4轮比赛

注意

这个方案是基于每轮比赛后能够准确排名的假设。实际情况中,可能需要额外的比赛来处理平局或其他意外情况。但在这个理想化的情况下,4轮比赛足以找出最快的4匹马。

10. TCP 和 UDP的区别是什么?

TCP和UDP的区别

TCP(传输控制协议)和UDP(用户数据报协议)是互联网上用于数据传输的两种主要协议,它们在多个方面存在显著区别:

1. 连接性

  • TCP:面向连接的协议。在数据传输之前,需要建立连接,传输完成后需要断开连接。
  • UDP:无连接的协议。发送数据之前不需要建立连接,直接发送数据包。

2. 可靠性

  • TCP:提供可靠的服务。通过确认机制、数据重传、顺序控制等手段保证数据的完整性和顺序。
  • UDP:不保证可靠性。数据包可能会丢失、重复或乱序,但不进行重传。

3. 数据流控制

  • TCP:具有流量控制和拥塞控制机制,可以根据网络状况调整数据传输速率。
  • UDP:没有流量控制和拥塞控制,数据传输速率不受网络状况的影响。

4. 传输速度

  • TCP:由于需要建立连接和进行各种控制,传输速度相对较慢。
  • UDP:传输速度快,因为不需要建立连接和进行复杂的控制。

5. 数据边界

  • TCP:数据以流的形式传输,没有边界,适用于大量数据的连续传输。
  • UDP:数据以数据报的形式传输,每个数据报都有明确的边界,适用于少量数据的传输。

6. 应用场景

  • TCP:适用于对数据可靠性要求高的应用,如网页浏览、电子邮件、文件传输等。
  • UDP:适用于对实时性要求高的应用,如视频会议、在线游戏、VoIP(网络电话)等。

7. 头部开销

  • TCP:头部较大,包含序列号、确认号、窗口大小等控制信息。
  • UDP:头部较小,只包含源端口、目标端口、长度和校验和。

8. 错误处理

  • TCP:通过确认和重传机制处理错误。
  • UDP:通常不处理错误,由应用程序负责错误检测和处理。

总结

TCP和UDP各有优缺点,适用于不同的应用场景。TCP提供可靠的服务,但传输速度较慢;UDP传输速度快,但不保证可靠性。选择哪种协议取决于应用程序的具体需求。

11. ajax、axios、fetch有什么区别?

AJAX、Axios和Fetch都是用于在Web应用程序中实现异步HTTP请求的技术,但它们之间存在一些关键的区别:

1. AJAX(Asynchronous JavaScript and XML)

定义

  • AJAX不是一种新的编程语言,而是一种利用现有标准的新方法,可以实现异步Web请求。 特点
  • 基于XMLHttpRequest对象。
  • 允许在不重新加载页面的情况下与服务器交换数据。
  • 可以更新部分网页内容。 用法
  • 需要编写更多的代码来处理请求的发送和响应的接收。
  • 跨域请求需要额外的配置。 兼容性
  • 广泛支持所有现代浏览器和许多旧浏览器。

2. Axios

定义

  • Axios是一个基于Promise的HTTP客户端,用于Node.js和浏览器中发送请求。 特点
  • 简化了请求的发送和响应的处理。
  • 支持请求和响应的拦截器。
  • 自动转换JSON数据。
  • 可以取消请求。 用法
  • 提供了更现代、更易用的API。
  • 支持Promise语法,使得链式调用更加方便。 兼容性
  • 不支持非常古老的浏览器。

3. Fetch

定义

  • Fetch是现代浏览器提供的一个原生API,用于发送HTTP请求。 特点
  • 基于Promise。
  • 提供了一个更简洁、更符合现代Web开发的API。
  • 默认不发送cookie,需要显式设置。 用法
  • 使用起来比XMLHttpRequest更简单。
  • 支持异步请求,并且可以链式调用。 兼容性
  • 不支持旧浏览器,但可以通过polyfill实现兼容。

主要区别总结

  • 技术基础:AJAX基于XMLHttpRequest,Axios是一个独立的库,而Fetch是现代浏览器的原生API。
  • 易用性:Axios和Fetch提供了更现代、更易用的API,相比之下,AJAX需要编写更多的代码。
  • Promise支持:Axios和Fetch都基于Promise,而AJAX不直接支持Promise。
  • 拦截器:Axios支持请求和响应的拦截器,而AJAX和Fetch不支持。
  • 兼容性:AJAX具有最广泛的兼容性,Fetch和Axios不支持非常古老的浏览器。 **选择哪种技术取决于具体的项目需求和浏览器兼容性要求。**在现代Web开发中,Fetch和Axios由于其易用性和现代特性,通常比AJAX更受欢迎。

12. for...in和for...of有什么区别?

for...infor...of 都是 JavaScript 中用于遍历的循环语句,但它们的设计目的和用法有所不同:

for...in

用途

  • 用于遍历对象中的可枚举属性(包括自有属性和继承的属性)。 特点
  • 循环中的变量代表的是对象的键(属性名)。
  • 会遍历对象的所有可枚举属性,包括原型链上的属性。
  • 不建议用于遍历数组,因为会遍历数组对象的属性,而不仅仅是数组的元素。 示例
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
  console.log(key); // 输出: "a", "b", "c"
  console.log(obj[key]); // 输出: 1, 2, 3
}

for...of

用途

  • 用于遍历可迭代对象(如数组、字符串、Map、Set等)的元素。 特点
  • 循环中的变量直接代表的是迭代对象中的值(元素)。
  • 只遍历可迭代对象自身的元素,不会遍历对象的属性。
  • 适用于数组和其他可迭代对象,提供了一种更简洁、更直接的遍历方式。 示例
const array = [1, 2, 3];
for (const value of array) {
  console.log(value); // 输出: 1, 2, 3
}

主要区别总结:

  • 遍历目标for...in 遍历对象的属性键,for...of 遍历可迭代对象的元素值。
  • 适用场景for...in 适用于对象,for...of 适用于数组、字符串、Map、Set等可迭代对象。
  • 遍历内容for...in 会遍历对象的所有可枚举属性,包括原型链上的属性;for...of 只遍历对象自身的元素。
  • 性能考虑:对于数组遍历,for...of 通常比 for...in 更高效,因为 for...in 会遍历数组的额外属性。 选择哪种循环取决于你需要遍历的数据类型和目的。 如果需要遍历对象的属性,使用 for...in;如果需要遍历数组或其他可迭代对象的元素,使用 for...of

13. 什么是 DOM 和 BOM?

DOM和BOM是Web开发中两个非常重要的概念,它们分别代表文档对象模型和浏览器对象模型。

DOM(Document Object Model)

定义

  • DOM是文档对象模型的缩写,它是一个与平台和语言无关的应用程序编程接口(API),用于处理HTML和XML文档。
  • DOM将文档表示为一个由节点组成的树形结构,其中每个节点代表文档中的一个部分,如元素、属性或文本。 主要功能
  • 允许程序和脚本动态地访问和更新文档的内容、结构和样式。
  • 提供了操作文档的方法,如添加、删除、修改元素和属性。 示例
// 修改HTML元素的内容
document.getElementById("myElement").innerHTML = "新内容";
// 添加新元素
const newElement = document.createElement("div");
document.body.appendChild(newElement);

BOM(Browser Object Model)

定义

  • BOM是浏览器对象模型的缩写,它提供了与浏览器窗口进行交互的API。
  • BOM的核心对象是window,它代表了浏览器的窗口,并且是所有其他BOM对象的顶层对象。 主要功能
  • 控制浏览器窗口的大小和位置。
  • 访问浏览器的导航历史(如前进、后退)。
  • 提供与浏览器相关的功能,如弹窗、定时器等。 BOM的主要对象
  • window:代表浏览器窗口。
  • navigator:提供关于浏览器的信息。
  • screen:提供关于用户屏幕的信息。
  • history:提供浏览器历史记录的管理。
  • location:提供当前URL的信息和导航功能。 示例
// 打开一个新的浏览器窗口
window.open("https://www.example.com");
// 设置定时器
setTimeout(function() {
  alert("时间到了!");
}, 3000);
// 获取屏幕宽度
const screenWidth = screen.width;

DOM与BOM的区别:

  • 目的不同:DOM旨在操作文档内容,而BOM旨在提供与浏览器窗口交互的功能。
  • 范围不同:DOM是HTML和XML文档的模型,而BOM是浏览器窗口的模型。
  • 对象不同:DOM的核心对象是document,而BOM的核心对象是window在实际开发中,DOM和BOM经常一起使用,以实现丰富的交互式Web应用。 例如,可以使用DOM来改变网页内容,同时使用BOM来控制浏览器行为,如在新窗口中打开链接或获取用户的屏幕尺寸信息。

14. 什么是类数组对象?

类数组对象(Array-like Object)是指那些在JavaScript中具有类似于数组特性的对象,但它们并不是通过数组构造函数Array创建的,也不具备数组的所有方法。类数组对象通常具有以下特征:

  1. 索引访问:对象可以通过索引(如object[0])来访问其元素。
  2. 长度属性:对象具有一个length属性,表示对象中元素的数量。
  3. 不继承自Array.prototype:类数组对象并不继承自Array.prototype,因此它们没有数组的方法,如pushpopforEach等。 常见的类数组对象包括:
  • 函数的参数对象:在函数内部,arguments对象是一个类数组对象,它包含了函数调用时传入的所有参数。
  • DOM元素集合:如document.getElementsByTagNamedocument.querySelectorAll返回的HTML元素集合。
  • 字符串:字符串也可以通过索引访问每个字符,并且具有length属性,但它们不是真正的数组。 示例
function example() {
  // arguments 是一个类数组对象
  for (let i = 0; i < arguments.length; i++) {
    console.log(arguments[i]);
  }
}
example(1, 2, 3); // 输出 1, 2, 3
// 类数组对象 - DOM元素集合
const divs = document.getElementsByTagName('div');
for (let i = 0; i < divs.length; i++) {
  console.log(divs[i].innerHTML);
}
// 类数组对象 - 字符串
const str = "Hello";
for (let i = 0; i < str.length; i++) {
  console.log(str[i]);
}

类数组对象与数组的转换: 有时,我们需要将类数组对象转换为真正的数组,以便使用数组的方法。这可以通过以下几种方法实现:

  • Array.from():ES6引入的方法,可以轻松将类数组对象转换为数组。
  • 扩展运算符:使用扩展运算符...可以将类数组对象转换为数组。
  • Array.prototype.slice.call():使用数组的slice方法,通过函数调用方式将其应用于类数组对象。 转换示例
// 使用 Array.from()
const arrayFromArgs = Array.from(arguments);
// 使用扩展运算符
const arrayFromDivs = [...document.getElementsByTagName('div')];
// 使用 Array.prototype.slice.call()
const arrayFromStr = Array.prototype.slice.call("Hello");

了解类数组对象及其与数组的区别和转换方法,对于深入理解JavaScript中的数据结构和操作非常有帮助。

15. JavaScript脚本延迟加载的方式有哪些?

JavaScript脚本的延迟加载是一种优化技术,可以加快页面的加载速度,提高用户体验。以下是一些常见的JavaScript脚本延迟加载的方式:

  1. 使用<script>标签的defer属性
    <script src="example.js" defer></script>
    
    defer属性告诉浏览器延迟执行该脚本,直到整个页面解析完毕。defer脚本按照在文档中出现的顺序执行。
  2. 使用<script>标签的async属性
    <script src="example.js" async></script>
    
    async属性也是告诉浏览器异步加载脚本,但与defer不同,async脚本在加载完成后会立即执行,不保证执行顺序。
  3. <script>标签放在文档的底部
    <body>
      <!-- 页面内容 -->
      <script src="example.js"></script>
    </body>
    
    将脚本放在</body>标签之前,可以确保在脚本执行之前,页面的主要内容已经加载完毕。
  4. 使用JavaScript动态创建<script>标签
    function loadScript(url, callback) {
      var script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = url;
      script.onload = function() {
        if (typeof callback === 'function') {
          callback();
        }
      };
      document.body.appendChild(script);
    }
    
    这种方式可以手动控制脚本加载的时机,并且在脚本加载完成后执行回调函数。
  5. 使用IntersectionObserver API
    let observer = new IntersectionObserver((entries, observer) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          loadScript('example.js');
          observer.unobserve(entry.target);
        }
      });
    }, { /* options */ });
    observer.observe(document.querySelector('#trigger-element'));
    
    IntersectionObserver可以用来检测某个元素是否进入视口,从而实现按需加载脚本。
  6. 使用requestIdleCallback API
    requestIdleCallback(() => {
      loadScript('example.js');
    });
    
    requestIdleCallback会在浏览器空闲时执行回调,适合用于加载非关键脚本。
  7. 使用Event触发加载
    document.addEventListener('DOMContentLoaded', () => {
      loadScript('example.js');
    });
    
    可以在特定事件(如DOMContentLoadedloadscroll等)触发时加载脚本。
  8. 使用webpack等模块打包工具的动态导入
    import('path/to/module').then((module) => {
      // 使用模块
    });
    
    在使用模块打包工具时,可以使用动态导入来实现代码分割和延迟加载。 选择哪种延迟加载方式取决于具体的需求和场景。例如,如果脚本之间没有依赖关系,且不关心执行顺序,可以使用async;如果需要保证脚本按顺序执行,可以使用defer;如果需要更细粒度的控制,可以采用动态创建<script>标签或使用IntersectionObserver等方式。

16. 箭头函数的 this 指向哪⾥?

箭头函数(Arrow Functions)是ES6(ECMAScript 2015)引入的一种新的函数表达式形式。箭头函数的一个显著特点是其this值的绑定方式与普通函数不同。 箭头函数的this指向定义时所在的上下文,而不是调用时所在的上下文。具体来说:

  1. 箭头函数不绑定自己的this:箭头函数不会创建自己的this上下文,而是在其被创建时就捕获了所在上下文的this值。
  2. this值由外围最近一层非箭头函数决定:箭头函数的this值继承自外围函数(即包含箭头函数的最内层非箭头函数)的this值。 示例:
function OuterFunction() {
  this.value = 'outer';
  const innerFunction = () => {
    console.log(this.value); // 'outer'
  };
  innerFunction();
}
OuterFunction();

在这个例子中,innerFunction是一个箭头函数,它没有自己的this,而是继承了OuterFunctionthis值,因此console.log(this.value)输出的是'outer'注意事项

  • 由于箭头函数不绑定自己的this,因此不能用作构造函数(不能使用new关键字)。
  • 箭头函数不能使用arguments对象,但可以访问外围函数的arguments
  • 箭头函数不能用作生成器函数(不能使用yield关键字)。 箭头函数的这种this绑定行为在很多场景下都非常方便,尤其是需要在回调函数中保持this上下文时。然而,也需要注意其与普通函数在this行为上的差异,以避免潜在的混淆或错误。

17. 如果new一个箭头函数会怎么样?

如果尝试使用 new 关键字来调用一个箭头函数,JavaScript 会抛出一个错误。这是因为箭头函数没有 [[Construct]] 方法,即它们没有构造函数的行为。箭头函数的设计初衷就是不需要自己的 thisargumentssupernew.target 绑定,因此它们不能被用作构造函数。 当你尝试使用 new 关键字时,以下情况会发生:

const ArrowFunc = () => {};
try {
  const instance = new ArrowFunc(); // 这会抛出错误
} catch (e) {
  console.error(e); // 输出错误信息
}

在这个例子中,尝试 new ArrowFunc() 会抛出一个类型错误(TypeError),错误信息类似于:

ArrowFunc is not a constructor

这个错误表明箭头函数 ArrowFunc 不能被用作构造函数。 总结

  • 箭头函数没有自己的 this 绑定,因此不能作为构造函数使用。
  • 尝试使用 new 关键字调用箭头函数会抛出 TypeError
  • 如果需要创建可构造的对象,应使用传统的函数表达式或函数声明。

18. object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别是什么?

Object.assign 和扩展运算符(spread operator)都是进行浅拷贝的操作。它们在拷贝对象时,只会复制对象的顶层属性,而不会递归地复制对象中的嵌套对象。这意味着如果源对象中包含嵌套的对象或数组,拷贝后的对象中对应的属性将引用相同的嵌套对象或数组,而不是它们的副本。 浅拷贝的特点

  • 拷贝的是对象的引用,而不是对象本身。
  • 如果源对象中的属性是基本数据类型,则拷贝的是值的副本。
  • 如果源对象中的属性是对象或数组,则拷贝的是引用,修改拷贝后的对象中的嵌套对象或数组会影响到源对象。 Object.assign 的使用
const source = { a: 1, b: { c: 2 } };
const target = Object.assign({}, source);

扩展运算符的使用

const source = { a: 1, b: { c: 2 } };
const target = { ...source };

两者的区别

  1. 语法Object.assign 需要一个目标对象作为第一个参数,后面可以跟一个或多个源对象。扩展运算符则是直接在对象字面量中使用,可以更直观地表示拷贝操作。
  2. 可读性:扩展运算符通常提供更好的可读性,尤其是在合并多个对象时。
  3. 灵活性:扩展运算符可以更灵活地用于数组和其他场景,而 Object.assign 主要用于对象。
  4. 兼容性Object.assign 是 ES6 引入的,而扩展运算符是 ES2018 引入的。虽然现代浏览器都支持这两个特性,但在一些旧的环境中,Object.assign 可能需要polyfill。 深拷贝: 如果你需要进行深拷贝,即完全复制对象及其所有嵌套对象,你需要使用其他方法,如:
  • 使用 JSON.parse(JSON.stringify(object))(注意这不能复制函数、Symbol属性和循环引用)。
  • 使用第三方库,如 Lodash 的 _.cloneDeep 方法。
  • 手写递归复制函数。 总结
  • Object.assign 和扩展运算符都是浅拷贝。
  • 它们之间的主要区别在于语法和可读性。
  • 如果需要深拷贝,应选择其他方法。

19. typeof NaN 的结果是什么?

在JavaScript中,typeof NaN 的结果是 "number"NaN 是 "Not-a-Number" 的缩写,它是一个特殊的数值,表示非数字。尽管它的名字暗示它不是数字,但在JavaScript的类型系统中,NaN 仍然被归类为数字类型。因此,使用 typeof 操作符检查 NaN 时,返回的结果是 "number"。 这是一个常见的JavaScript特性,有时可能会让新手感到困惑。下面是一个简单的示例代码:

console.log(typeof NaN); // 输出: "number"

这个行为是符合IEEE 754浮点数标准的,该标准是大多数现代编程语言中数字实现的基石。在这个标准中,NaN 是一种特殊的数值,用于表示未定义或无法表示的数值结果,例如0除以0或者将非数值字符串转换为数字。

20. null 和 undefined 有什么区别?

在JavaScript中,nullundefined都表示缺少值,但它们之间有一些关键的区别:

  1. 定义
    • null:通常用于表示一个已知的存在但当前没有值的对象。它是一个表示“无”的对象。
    • undefined:用于表示变量已声明但尚未赋值的情况。它是一个表示“未定义”的原始类型。
  2. 类型
    • nullNull类型的唯一值。
    • undefinedUndefined类型的唯一值。
  3. 使用场景
    • null通常用于表示一个对象应该有值,但目前为空。例如,一个表示用户信息的对象可能被设置为null来表示没有用户信息。
    • undefined通常用于表示变量已声明但尚未赋值,或者对象属性不存在。例如,访问一个未定义的变量或对象的一个不存在的属性会得到undefined
  4. 相等性
    • null == undefinedtrue,因为它们都表示缺少值。
    • null === undefinedfalse,因为它们是不同的类型。
  5. 转换为数字
    • null转换为数字时为0
    • undefined转换为数字时为NaN
  6. 布尔值转换
    • both null and undefined are considered falsy values in a boolean context, meaning they are treated as false when converted to a boolean. 下面是一些示例代码:
let a = null;
let b;
console.log(typeof a); // "object"
console.log(typeof b); // "undefined"
console.log(a == b); // true
console.log(a === b); // false
console.log(Number(a)); // 0
console.log(Number(b)); // NaN
if (!a) {
  console.log('a is falsy'); // 这会被执行
}
if (!b) {
  console.log('b is falsy'); // 这也会被执行
}

在实际开发中,根据具体的情况选择使用nullundefined。通常,如果变量应该有一个对象值但现在为空,使用null;如果变量尚未赋值或属性不存在,使用undefined

21. 数据类型检测的方式有哪些?

在JavaScript中,检测数据类型的方法有多种,以下是一些常用的方式:

  1. typeof运算符
    • typeof可以用来检测基本数据类型(undefinedstringnumberbooleansymbol)和函数。
    • 对于对象类型(nullarrayobject),typeof会返回"object"
    • 对于nulltypeof会错误地返回"object",这是一个历史遗留问题。
    console.log(typeof 123); // "number"
    console.log(typeof 'abc'); // "string"
    console.log(typeof true); // "boolean"
    console.log(typeof undefined); // "undefined"
    console.log(typeof Symbol()); // "symbol"
    console.log(typeof function(){}); // "function"
    console.log(typeof {}); // "object"
    console.log(typeof []); // "object"
    console.log(typeof null); // "object"
    
  2. instanceof运算符
    • instanceof用于检测构造函数的prototype属性是否出现在对象的原型链中,适用于检测对象的具体类型。
    • 它不能用于基本数据类型。
    console.log([] instanceof Array); // true
    console.log({} instanceof Object); // true
    console.log(new Date() instanceof Date); // true
    console.log(/abc/ instanceof RegExp); // true
    
  3. Object.prototype.toString.call()方法
    • 这是检测数据类型最准确的方法,可以区分所有基本数据类型和对象类型。
    • 它返回一个表示对象类型的字符串,格式为"[object Type]"
    console.log(Object.prototype.toString.call(123)); // "[object Number]"
    console.log(Object.prototype.toString.call('abc')); // "[object String]"
    console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
    console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
    console.log(Object.prototype.toString.call(Symbol())); // "[object Symbol]"
    console.log(Object.prototype.toString.call({})); // "[object Object]"
    console.log(Object.prototype.toString.call([])); // "[object Array]"
    console.log(Object.prototype.toString.call(null)); // "[object Null]"
    console.log(Object.prototype.toString.call(new Date())); // "[object Date]"
    console.log(Object.prototype.toString.call(/abc/)); // "[object RegExp]"
    
  4. constructor属性
    • 每个对象都有constructor属性,指向创建该对象的构造函数。
    • 可以通过constructor属性来检测对象的类型。
    • 注意,如果改变对象的prototype,可能会影响constructor属性的指向。
    console.log((123).constructor === Number); // true
    console.log(('abc').constructor === String); // true
    console.log((true).constructor === Boolean); // true
    console.log(({}).constructor === Object); // true
    console.log(([]).constructor === Array); // true
    
  5. Array.isArray()方法
    • 用于检测一个值是否是数组。
    • 这是专门为数组设计的检测方法。
    console.log(Array.isArray([])); // true
    console.log(Array.isArray({})); // false
    

选择哪种方法取决于具体的需求和场景。对于基本数据类型,typeof通常足够使用;对于对象类型,instanceofObject.prototype.toString.call()constructor属性都是可行的选择。对于数组的检测,Array.isArray()是最直接的方法。

22. Object.is() 与比较操作符 “===”、“==” 的区别?

Object.is() 方法、严格相等操作符 === 和相等操作符 == 在JavaScript中用于比较两个值,但它们在比较时有不同的行为和规则:

Object.is()

  • Object.is() 是ES6引入的新方法,用于比较两个值是否为同一个值。
  • 它的行为与 === 非常相似,但是有一些区别:
    • Object.is() 认为 NaNNaN 是相等的,而 === 认为 NaN 不等于 NaN
    • Object.is() 认为 +0-0 是不等的,而 === 认为 +0-0 是相等的。
console.log(Object.is('foo', 'foo')); // true
console.log(Object.is({}, {})); // false
console.log(Object.is(NaN, NaN)); // true
console.log(NaN === NaN); // false
console.log(Object.is(+0, -0)); // false
console.log(+0 === -0); // true

严格相等操作符 ===

  • === 用于比较两个值是否严格相等,即类型和值都必须相同。
  • 如果比较的两个值类型不同,则直接返回 false
  • NaN 不等于任何值,包括它自己。
  • +0-0 被认为是相等的。
console.log(3 === 3); // true
console.log(3 === '3'); // false
console.log(NaN === NaN); // false
console.log(+0 === -0); // true

相等操作符 ==

  • == 用于比较两个值是否相等,如果类型不同,会先进行类型转换(类型 coercion)然后比较。
  • 类型转换可能会带来一些意想不到的结果,因此通常建议使用 === 以避免这些潜在的问题。
  • NaN 不等于任何值。
  • +0-0 被认为是相等的。
console.log(3 == '3'); // true
console.log(true == 1); // true
console.log(null == undefined); // true
console.log(NaN == NaN); // false
console.log(+0 == -0); // true

总结

  • Object.is()=== 类似,但处理 NaN-0+0 时有所不同。
  • === 是严格比较,不进行类型转换,通常被认为是比较值的最佳实践。
  • == 会进行类型转换,可能导致一些意外的比较结果,通常不推荐使用。 在实际开发中,根据具体的需求和场景选择合适的比较方式。如果需要比较两个值是否完全相同,没有类型转换,且希望 NaN 等于 NaN,则可以使用 Object.is()。如果只需要严格比较值和类型,使用 ===。如果确实需要类型转换的比较,再考虑使用 ==

23. isNaN 和 Number.isNaN 函数有什么区别?

isNaNNumber.isNaN 都是JavaScript中用于检测一个值是否为NaN(Not-a-Number)的函数,但它们在行为上有所区别:

isNaN

  • isNaN 是全局函数,可以接受任何类型的参数。
  • isNaN 会首先尝试将传入的参数转换为数字,然后判断转换后的结果是否为NaN
  • 由于isNaN会进行类型转换,因此它可能会对非数字类型的值返回true,这可能导致一些意外的结果。
console.log(isNaN(NaN)); // true
console.log(isNaN('Hello')); // true,因为'Hello'无法转换为数字
console.log(isNaN('')); // false,因为''转换为数字是0
console.log(isNaN(true)); // false,因为true转换为数字是1

Number.isNaN

  • Number.isNaN 是ES6引入的新的Number对象的方法。
  • Number.isNaN 不会进行类型转换,它只会检查传入的参数是否为NaN
  • 如果传入的参数不是数字类型,Number.isNaN 会直接返回false
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN('Hello')); // false,因为'Hello'不是数字类型
console.log(Number.isNaN('')); // false,因为''不是数字类型
console.log(Number.isNaN(true)); // false,因为true不是数字类型

总结

  • isNaN 会进行类型转换,可能会对非数字类型的值返回true
  • Number.isNaN 不会进行类型转换,只有当参数确实是NaN时才返回true
  • Number.isNaN 是更严格、更符合直觉的检测NaN的方法。 在实际使用中,如果需要检测一个值是否为NaN,建议使用Number.isNaN,因为它不会受到类型转换的影响,可以更准确地判断一个值是否为NaN。如果需要兼容旧环境(ES6之前),则可能需要使用isNaN,但要注意它的类型转换行为。

24. 使用ts实现一个判断入参是否是数组类型的方法?

在TypeScript中,你可以使用多种方法来判断一个参数是否是数组类型。以下是几种常见的方法:

方法1:使用Array.isArray

function isArray(value: any): value is Array<any> {
  return Array.isArray(value);
}
// 使用示例
const arr = [1, 2, 3];
console.log(isArray(arr)); // true
const notArr = {};
console.log(isArray(notArr)); // false

方法2:使用类型保护

TypeScript提供了类型保护机制,可以让你更安全地判断类型。

function isArray<T>(value: any): value is Array<T> {
  return Array.isArray(value);
}
// 使用示例
const arr = [1, 2, 3];
if (isArray(arr)) {
  console.log(arr.length); // 类型为Array<number>,可以访问length属性
}
const notArr = {};
if (!isArray(notArr)) {
  console.log(notArr); // 类型为非数组类型
}

方法3:使用instanceof操作符

function isArray(value: any): value is Array<any> {
  return value instanceof Array;
}
// 使用示例
const arr = [1, 2, 3];
console.log(isArray(arr)); // true
const notArr = {};
console.log(isArray(notArr)); // false

方法4:使用Object.prototype.toString.call

function isArray(value: any): value is Array<any> {
  return Object.prototype.toString.call(value) === '[object Array]';
}
// 使用示例
const arr = [1, 2, 3];
console.log(isArray(arr)); // true
const notArr = {};
console.log(isArray(notArr)); // false

总结

  • Array.isArray 是ES5中引入的方法,它是判断数组类型的最直接和推荐的方式。
  • 类型保护是TypeScript特有的机制,它可以提供更严格的类型检查。
  • instanceof 操作符可以用来检查一个对象是否是某个类的实例,但它在跨框架或多个全局作用域的情况下可能不适用。
  • Object.prototype.toString.call 是一种兼容性较好的方法,可以在各种环境中准确判断数组类型。 在TypeScript中,使用Array.isArray或类型保护通常是首选,因为它们提供了更好的类型安全和代码可读性。

25. react中懒加载的实现原理是什么?

在React中,懒加载(Lazy Loading)是一种优化技术,用于提高应用程序的启动速度和性能。它通过延迟加载非关键资源,直到需要时才加载它们,从而减少了初始加载时间。React实现了懒加载的原理主要基于动态导入(Dynamic Imports)和代码分割(Code Splitting)。

实现原理:

  1. 动态导入(Dynamic Imports)
    • 动态导入是一种ES2020提案的功能,允许你异步地加载模块。在React中,你可以使用import()函数来动态地导入组件。
    • import()返回一个Promise,该Promise在模块加载完成时解析。
  2. 代码分割(Code Splitting)
    • 代码分割是一种将代码分割成多个小块的技术,这些小块可以在需要时单独加载。React利用Webpack、Rollup等打包工具提供的代码分割功能来实现懒加载。
    • 当你使用import()动态导入模块时,打包工具会自动将该模块分割成一个单独的chunk,并在运行时按需加载。
  3. React.lazy
    • React提供了React.lazy函数,它使得懒加载组件变得简单。React.lazy接受一个动态导入组件的函数,并返回一个React组件。
    • 当该组件被渲染时,它会自动加载包含该组件的chunk。
  4. Suspense组件
    • Suspense组件用于包裹懒加载的组件,它可以“捕获”加载状态,并显示一个回退内容(如加载指示器),直到懒加载的组件加载完成。
    • Suspense组件的fallback属性接受一个React元素,作为加载状态时的显示内容。

示例代码:

import React, { Suspense, lazy } from 'react';
// 使用React.lazy动态导入组件
const LazyComponent = lazy(() => import('./LazyComponent'));
function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

在上述示例中:

  • lazy(() => import('./LazyComponent'))会动态导入LazyComponent组件。
  • Suspense组件包裹LazyComponent,并在加载过程中显示“Loading...”文本。

注意事项:

  • 懒加载适用于分割大型组件或页面,以减少初始加载时间。
  • 确保懒加载的组件在使用前已经加载完成,以避免渲染错误。
  • 使用懒加载时,要注意网络延迟和加载失败的情况,并提供相应的用户体验优化(如错误处理、加载指示器等)。 通过结合动态导入、代码分割、React.lazySuspense组件,React实现了高效且用户友好的懒加载机制。

26. 说说你对以下几个页面生命周期事件的理解:DOMContentLoaded,load,beforeunload,unload

页面生命周期事件是浏览器在加载和卸载网页时触发的一系列事件,它们提供了在不同阶段进行操作和优化的机会。以下是几个常见的页面生命周期事件及其解释:

  1. DOMContentLoaded
    • 触发时机:当初始HTML文档被完全加载和解析完成后触发,不需要等待样式表、图片和子框架的加载完成。
    • 用途:这个事件通常用于执行依赖于DOM的脚本,因为此时所有的DOM元素都已经可用,但其他资源如图片可能还未加载完成。
    • 示例:可以在这个事件中绑定事件处理器、初始化插件或执行其他DOM操作。
  2. load
    • 触发时机:当整个页面包括所有依赖的资源如样式表、图片和子框架都完全加载完成后触发。
    • 用途:这个事件表示页面已经完全加载,可以用于执行需要在所有资源都加载完毕后才能进行的操作。
    • 示例:可以在这个事件中执行页面加载完成后的分析、跟踪或显示加载完成的消息。
  3. beforeunload
    • 触发时机:当用户即将离开当前页面(例如关闭浏览器标签或导航到另一个页面)时触发。
    • 用途:这个事件可以用来提醒用户他们有未保存的更改,或者进行一些清理操作。通过返回一个字符串,可以提示用户是否真的要离开页面。
    • 示例:可以在这个事件中显示一个确认对话框,询问用户是否真的要离开。
  4. unload
    • 触发时机:当用户已经离开当前页面时触发。
    • 用途:这个事件用于进行最后的清理操作,例如关闭弹窗、发送统计数据或清理本地存储。
    • 示例:可以在这个事件中发送页面卸载的跟踪数据或清除页面状态。

注意事项:

  • 性能考虑DOMContentLoadedload事件的处理器中不应执行过多的操作,以免阻塞页面的渲染或影响用户体验。
  • 兼容性:这些事件在所有现代浏览器中都得到支持,但在某些旧版浏览器中可能存在差异。
  • 使用限制:由于浏览器的安全限制,beforeunloadunload事件中的操作受到限制,例如不能阻止导航或执行异步操作。

示例代码:

document.addEventListener('DOMContentLoaded', function() {
  console.log('DOM is fully loaded');
});
window.addEventListener('load', function() {
  console.log('Page is fully loaded');
});
window.addEventListener('beforeunload', function(event) {
  event.preventDefault();
  event.returnValue = '';
  console.log('Are you sure you want to leave?');
});
window.addEventListener('unload', function() {
  console.log('Page is unloading');
});

在这些示例中,我们为每个事件添加了事件监听器,并在控制台中输出了相应的消息,以演示它们在不同阶段的触发。

27. 一台设备的dpr,是否是可变的?

是的,一台设备的DPR(设备像素比)是可变的。DPR是指物理像素与逻辑像素(或称为设备独立像素)之间的比例。在某些情况下,DPR可能会发生变化。 以下是一些可能导致DPR变化的情况:

  1. 用户缩放:当用户在浏览器中缩放页面时,DPR可能会发生变化。例如,如果用户将页面放大到200%,DPR可能会从1变为2。
  2. 系统设置:在某些操作系统中,用户可以更改系统的DPI(每英寸点数)设置,这可能会影响DPR。
  3. 设备特性:某些设备具有可变刷新率或动态分辨率调整功能,这些特性可能会影响DPR。
  4. 浏览器设置:某些浏览器允许用户设置默认的缩放级别,这也会影响DPR。
  5. 虚拟现实(VR)或增强现实(AR)设备:在这些设备上,DPR可能会根据应用的需求或用户的交互而动态变化。
  6. 多显示器设置:在多显示器设置中,不同显示器的DPR可能不同,当窗口在显示器之间移动时,DPR可能会发生变化。
  7. 软件更新:操作系统或浏览器的更新有时会引入新的显示特性或优化,这可能间接影响DPR。 尽管DPR是可变的,但在大多数情况下,对于特定的设备和浏览器组合,DPR在一段时间内是相对稳定的。开发者通常可以通过JavaScript中的window.devicePixelRatio属性来获取当前的DPR值,并根据需要进行相应的适配或优化。

28. React中的路由懒加载是什么?原理是什么?

React中的路由懒加载是一种优化技术,它允许将应用程序的不同部分(通常是不同的路由组件)分割成多个代码块,并在需要时才加载这些代码块。这样做可以减少初始加载时间,提高应用程序的性能和用户体验。 原理: 路由懒加载的原理基于JavaScript的动态导入(Dynamic Imports)功能。在React中,通常使用React.lazy函数和Suspense组件来实现路由懒加载。

  1. React.lazy:这是一个高阶组件,它允许你动态地导入一个组件。你只需要将组件的导入语句替换为React.lazy()调用,并传入一个动态导入组件的函数。
    const LazyComponent = React.lazy(() => import('./LazyComponent'));
    
  2. Suspense组件Suspense组件用于包裹懒加载的组件,它可以“等待”懒加载的组件加载完成。在加载过程中,你可以显示一个加载指示器(如旋转的加载图标)。
    import React, { Suspense, lazy } from 'react';
    const LazyComponent = lazy(() => import('./LazyComponent'));
    function App() {
      return (
        <div>
          <Suspense fallback={<div>Loading...</div>}>
            <LazyComponent />
          </Suspense>
        </div>
      );
    }
    

在上述代码中,当LazyComponent被渲染时,React会自动启动导入过程。在组件加载完成之前,Suspense组件会显示fallback属性中指定的内容(例如“Loading...”)。一旦组件加载完成,LazyComponent就会渲染到页面上。 工作流程

  1. 用户导航到某个路由。
  2. React路由器检测到该路由对应的组件是懒加载的。
  3. React启动动态导入过程,请求相应的代码块。
  4. 在代码块加载期间,Suspense组件显示加载指示器。
  5. 代码块加载完成后,React渲染对应的组件。
  6. 用户看到渲染后的组件。 通过这种方式,初始加载时只需要加载必要的代码,其他代码可以在需要时按需加载,从而减少了初始负载,提高了应用的启动速度。

29. 介绍一下 tree shaking 及其工作原理

Tree Shaking 及其工作原理

Tree Shaking是一种优化技术,用于在编译阶段移除JavaScript代码中未使用的部分。这项技术主要应用于打包工具(如Webpack、Rollup等)中,以减少最终打包文件的体积,提高应用程序的加载速度和运行效率。

工作原理

Tree Shaking的工作原理基于ES6模块系统(ES6 Modules)的静态结构特性。以下是Tree Shaking的主要步骤:

  1. 静态分析
    • ES6模块系统允许打包工具在编译时进行静态分析,即在不执行代码的情况下分析模块的依赖关系。
    • 打包工具会识别出每个模块的importexport语句,从而构建出一个完整的模块依赖图。
  2. 标记未使用代码
    • 在构建模块依赖图后,打包工具会遍历这张图,标记出所有未使用的代码。
    • 未使用的代码包括:未导出的变量、函数、类,以及虽然导出但未被其他模块导入的变量、函数、类。
  3. 删除未使用代码
    • 一旦标记完成,打包工具会在最终的打包文件中删除这些未使用的代码。
    • 这个过程就像“摇树”(Shaking)一样,把树上未使用的“叶子”(代码)摇落。
  4. 生成优化后的代码
    • 删除未使用代码后,打包工具会生成优化后的代码,这些代码只包含实际使用的部分。
    • 最终的打包文件因此变得更小,加载更快。
实现条件

为了使Tree Shaking有效工作,需要满足以下条件:

  • 使用ES6模块语法:Tree Shaking依赖于ES6模块的静态结构,因此使用importexport语句是必要的。
  • side-effect-free:模块应该是“无副作用”的,即模块的执行不应该有除了导出内容之外的其他影响。可以使用package.json中的"sideEffects": false来声明模块无副作用。
  • 正确的打包工具配置:确保使用的打包工具(如Webpack)配置正确,以启用Tree Shaking功能。
示例

以下是一个简单的示例,展示Tree Shaking如何工作:

// math.js
export function add(a, b) {
  return a + b;
}
export function subtract(a, b) {
  return a - b;
}
// app.js
import { add } from './math.js';
console.log(add(1, 2)); // 使用了add函数
// subtract函数未使用,将被Tree Shaking删除

在打包过程中,由于subtract函数未被使用,Tree Shaking会将它从最终的打包文件中删除。

总结

Tree Shaking是一种强大的优化技术,通过移除未使用的代码来减少应用程序的体积。它依赖于ES6模块的静态结构特性,并在打包工具中进行静态分析和优化。正确使用Tree Shaking可以显著提高应用程序的性能和加载速度。