前端面试经典

220 阅读44分钟

1. http1.0 和http2.0的区别

HTTP/1.0和HTTP/2.0是两个不同版本的HTTP协议,它们在性能、传输方式和功能方面存在一些区别。下面是HTTP/1.0和HTTP/2.0之间的主要区别:

  1. 性能和多路复用(Multiplexing): HTTP/1.0采用了串行的请求-响应模型,每个请求都需要等待前一个请求的响应完成后才能发送下一个请求。而HTTP/2.0引入了多路复用,可以同时发送多个请求和接收多个响应,不再需要等待前一个请求的响应,提高了性能和效率。

  2. 头部压缩(Header Compression): HTTP/1.0每次请求和响应都会携带完整的头部信息,导致了冗余的数据传输。HTTP/2.0使用了头部压缩机制,通过对头部字段进行压缩和编码,减少了传输的数据量,提高了性能。

  3. 流(Stream)和优先级(Priority): HTTP/2.0引入了流的概念,可以将请求和响应分割为多个独立的流进行传输,每个流都有一个唯一的标识符。同时,HTTP/2.0支持对流设置优先级,可以指定哪些流的数据优先传输,提高了资源的利用效率。

  4. 服务器推送(Server Push): HTTP/2.0支持服务器主动推送资源到客户端,无需客户端明确请求。服务器可以根据客户端的请求,主动发送与请求相关的其他资源,减少了客户端发起额外请求的次数,提高了性能。

  5. 安全性: HTTP/2.0对数据传输引入了TLS(Transport Layer Security)的要求,即HTTPS。使用HTTPS加密传输数据,提高了数据的安全性。

需要注意的是,虽然HTTP/2.0在性能方面有明显的优势,但在某些环境和特定的网络条件下,HTTP/1.0仍然可以提供良好的性能。另外,浏览器和服务器是否支持HTTP/2.0也是一个考虑因素。

综上所述,HTTP/2.0相较于HTTP/1.0具有更好的性能、多路复用、头部压缩、流与优先级控制以及服务器推送等特性,有助于提升网站的性能和用户体验。

2. babel-plugin-import 带来的问题, 怎么解决, 优点?

babel-plugin-import是Babel插件之一,用于按需加载(按需引入)JavaScript库或组件。它的作用是根据需要动态引入代码,以减小打包后的文件体积,提高应用的加载速度。

使用babel-plugin-import可能会遇到以下问题:

  1. 配置复杂性: 在使用babel-plugin-import时,需要进行相关的配置,包括指定库的按需引入规则、配置路径别名等。配置可能会比较复杂,尤其是在引入多个库或组件时。

  2. 调试困难: 由于babel-plugin-import会对源代码进行转换,最终的代码可能与原始代码不完全一致。这可能会导致在调试时出现困难,因为你所看到的代码与实际运行的代码存在差异。

解决这些问题的方法如下:

  1. 仔细配置: 在使用babel-plugin-import时,需要仔细配置相关的按需引入规则和路径别名。确保配置的准确性和一致性,并考虑到项目的特殊需求。

  2. 测试和调试: 在应用babel-plugin-import之前,应该进行充分的测试和调试,以确保转换后的代码的正确性和可调试性。在调试过程中,可以使用源码映射(source maps)来跟踪转换前后的代码,以方便定位问题。

babel-plugin-import的优点包括:

  1. 减小文件体积: 按需加载只引入需要的代码,可以减小打包后的文件体积,从而提高页面加载速度。

  2. 优化性能: 减小文件体积可以减少网络传输的时间,加快页面加载速度,提升用户体验。

  3. 代码模块化: 按需引入可以使代码更加模块化,按需加载外部库或组件,提高代码的可维护性和可扩展性。

  4. 灵活性: 使用babel-plugin-import可以根据具体需求按需引入不同的库或组件,灵活适应项目的需求。

总的来说,babel-plugin-import是一个有用的插件,可以帮助优化前端项目的性能和可维护性。在使用过程中,需要仔细配置和进行测试,以确保代码的正确性和可调试性。

3. webpack的loader和plugin区别, webpack5新增了那些方法, 带来的好处

在Webpack中,Loader和Plugin是两个不同的概念,它们在构建过程中扮演着不同的角色。

  1. Loader(加载器): Loader用于对模块的源代码进行转换和处理。它们是Webpack的核心部分,用于处理各种类型的文件,例如将ES6代码转换为ES5、将Sass转换为CSS、将图片转换为Base64等。Loader通过在配置文件中进行配置,并且可以串联使用,按照顺序对模块进行转换。

  2. Plugin(插件): Plugin用于扩展Webpack的功能,它可以在整个构建过程中执行自定义的操作和功能扩展。Plugin通过在Webpack配置中实例化,并且可以监听Webpack的生命周期事件,如在构建过程中进行文件的复制、代码压缩、资源优化等。Plugin可以完成更复杂的任务,比如生成HTML文件、提取CSS到单独文件、进行代码分割等。

Webpack 5引入了一些新的方法和功能,带来了以下好处:

  1. 模块联邦(Module Federation): Webpack 5引入了模块联邦的概念,允许将不同的Webpack构建共享模块,实现多个应用之间的模块共享和通信。这样可以提高应用程序的性能和可扩展性。

  2. 持久缓存(Persistent Caching): Webpack 5改进了缓存机制,通过标记和存储已解析模块的依赖关系,提高了构建的速度。当代码发生变化时,Webpack可以通过缓存来避免重新构建没有变化的模块,提升了开发体验和构建性能。

  3. 支持WebAssembly和TypeScript的增强: Webpack 5对WebAssembly和TypeScript的支持进行了改进,提供了更好的性能和构建体验。

  4. 优化输出包大小: Webpack 5引入了一些优化功能,如Tree Shaking和代码分割等,可以更好地优化输出的包大小,减少不必要的代码。

  5. 改进的错误处理和调试体验: Webpack 5改进了错误处理和调试功能,提供了更好的错误信息和源码映射,便于开发者定位和解决问题。

总的来说,Webpack的Loader和Plugin在构建过程中扮演不同的角色。Loader用于对模块进行转换和处理,而Plugin用于扩展Webpack的功能和提供额外的操作。Webpack 5引入了一些新的方法和功能,提供了更好的性能、构建体验和优化输出包大小的好处。

4. babel 的runtime和helpers

在Babel中,有两个相关的概念:Babel Runtime(运行时)和Babel Helpers(帮助函数)。它们都是用于支持转译后的代码在目标环境中运行的工具。

  1. Babel Runtime(运行时): Babel Runtime是一个公共的运行时环境,它包含了一组用于支持转译后的代码运行所需的公共函数和工具。它通常作为一个独立的包(如@babel/runtime)进行安装并在目标环境中使用。Babel Runtime的作用是提供一致的运行时环境,以支持转译后的代码在不同环境中的运行。

Babel Runtime的优点包括:

  • 避免在每个转译的文件中重复引入相同的辅助函数,减小输出文件的体积。
  • 提供了一致的运行时环境,确保转译后的代码在不同环境中的可靠运行。
  1. Babel Helpers(帮助函数): Babel Helpers是一组用于帮助转译后的代码运行所需的辅助函数。这些函数包括一些在转译过程中需要使用的工具函数,例如_classCallCheck_inherits_defineProperty等。这些辅助函数在转译后的代码中会被引入,以保证代码的正确性和兼容性。

Babel Helpers的优点包括:

  • 提供了一组可靠和标准化的辅助函数,确保转译后的代码的正确性和可靠性。
  • 避免在每个转译的文件中重复定义相同的辅助函数,减小输出文件的体积。

总的来说,Babel Runtime提供了一致的运行时环境,用于支持转译后的代码在不同环境中的运行。而Babel Helpers则是一组辅助函数,用于转译后的代码的正确性和兼容性。它们都起到了简化和优化转译过程的作用。

5. 浏览器的GC机制(垃圾回收机制)

浏览器的垃圾回收(Garbage Collection,GC)机制是一种自动内存管理的机制,它负责在运行时跟踪和回收不再使用的内存,以减少内存泄漏和优化内存使用。

浏览器的GC机制主要有以下几个组成部分:

  1. 标记-清除(Mark and Sweep): 这是最常见的垃圾回收算法。当变量不再被引用时,垃圾收集器会将其标记为可回收的,并在后续的垃圾收集周期中清除这些标记的变量,释放其占用的内存。

  2. 引用计数(Reference Counting): 这是一种简单的垃圾回收算法,在对象被引用或释放时进行计数。当对象的引用计数变为零时,垃圾收集器将回收该对象的内存。然而,引用计数算法无法处理循环引用的情况,因为循环引用会导致对象的引用计数始终不为零,从而造成内存泄漏。

  3. 标记-压缩(Mark and Compact): 这是一种结合了标记和清除的算法。它首先标记不再使用的对象,然后将存活的对象紧凑地移动到内存的一端,以便释放连续的内存空间。这种算法可以减少内存碎片化,提高内存的利用率。

  4. 增量式垃圾回收(Incremental Garbage Collection): 这种机制将垃圾回收过程分成多个小步骤,与应用程序的执行交替进行。通过将垃圾回收任务分解成更小的部分,可以在更长的时间内保持应用程序的响应性,减少长时间的垃圾回收暂停。

每个浏览器厂商可能会实现不同的垃圾回收机制,并针对不同的应用场景进行优化。优化的GC机制可以减少内存占用和提高应用程序的性能。同时,开发者也可以通过避免全局变量、及时释放不再使用的对象等方法,优化自己的代码,减少内存泄漏的风险。

6. 基础类型判定方法, 优缺点, 区别

在JavaScript中,可以使用多种方法来判定基础类型(原始类型)的值,如字符串、数字、布尔值等。以下是几种常见的基础类型判定方法、它们的优缺点和区别:

  1. typeof 运算符: typeof 运算符返回一个表示操作数类型的字符串。例如,typeof 'hello' 返回 'string'typeof 42 返回 'number'。它的优点是简单、快速,适用于大多数基础类型的判定。然而,typeof 运算符对于一些特殊情况,如 null 和数组,返回的结果可能不符合预期。

  2. instanceof 运算符: instanceof 运算符用于检查对象是否属于某个特定的类型。例如,'hello' instanceof String 返回 false42 instanceof Number 返回 false。它的优点是可以准确判定对象是否为某个构造函数的实例,但对于基础类型的判定则不适用。

  3. constructor 属性: 所有对象都具有 constructor 属性,它引用对象的构造函数。例如,'hello'.constructor === String 返回 true(42).constructor === Number 返回 true。它的优点是可以判定对象的构造函数,但对于基础类型的判定则不适用。

  4. Object.prototype.toString 方法: Object.prototype.toString 方法返回一个表示对象类型的字符串。例如,Object.prototype.toString.call('hello') 返回 '[object String]'Object.prototype.toString.call(42) 返回 '[object Number]'。它的优点是可以准确判定基础类型的值,且对于所有值都适用。它的缺点是使用起来相对繁琐,需要通过 call 方法来调用。

这些方法的主要区别在于适用的场景和精确度。typeof 运算符适用于大多数基础类型的判定,但在某些特殊情况下可能返回不准确的结果。instanceof 运算符适用于判定对象是否为某个构造函数的实例,但对于基础类型的判定则不适用。constructor 属性适用于判定对象的构造函数,但对于基础类型的判定则不适用。Object.prototype.toString 方法可以准确判定基础类型的值,但使用起来相对繁琐。

在实际开发中,根据具体的需求和场景选择合适的判定方法是很重要的。通常情况下,typeof 运算符是最常用和简便的判定方法,而 Object.prototype.toString 方法可以作为一个通用的方法来准确判定基础类型的值。

7. TCP三次握手与四次挥手

TCP(Transmission Control Protocol)是一种可靠的传输协议,它使用三次握手建立连接和四次挥手关闭连接。

三次握手:

  1. 第一次握手(SYN): 客户端向服务器发送一个带有 SYN 标志的连接请求报文段,表示客户端请求建立连接。此时客户端进入 SYN-SENT 状态。

  2. 第二次握手(SYN+ACK): 服务器收到客户端的连接请求后,会向客户端发送一个带有 SYN/ACK 标志的报文段作为响应,表示同意建立连接。同时,服务器也为这次连接分配缓冲区等资源。此时服务器进入 SYN-RECEIVED 状态。

  3. 第三次握手(ACK): 客户端收到服务器的响应后,向服务器发送一个带有 ACK 标志的报文段,表示确认连接。此时客户端和服务器都进入 ESTABLISHED 状态,完成连接的建立。

四次挥手:

  1. 第一次挥手(FIN): 当客户端决定关闭连接时,它发送一个带有 FIN(Finish)标志的报文段给服务器,表示不再发送数据。客户端进入 FIN-WAIT-1 状态。

  2. 第二次挥手(ACK): 服务器收到客户端的关闭请求后,发送一个带有 ACK 标志的报文段作为确认,表示收到了关闭请求。服务器进入 CLOSE-WAIT 状态,客户端进入 FIN-WAIT-2 状态。

  3. 第三次挥手(FIN): 当服务器决定关闭连接时,它发送一个带有 FIN 标志的报文段给客户端,表示不再发送数据。服务器进入 LAST-ACK 状态。

  4. 第四次挥手(ACK): 客户端收到服务器的关闭请求后,发送一个带有 ACK 标志的报文段作为确认,表示接受关闭请求。服务器进入 TIME-WAIT 状态,等待一段时间后关闭连接,客户端进入 CLOSED 状态。

这样,经过四次挥手后,连接关闭,双方都进入 CLOSED 状态。

通过三次握手建立连接和四次挥手关闭连接,TCP 提供了可靠的数据传输机制,并确保了双方的连接状态同步和正确关闭连接的过程。

8. 函数闭包

函数闭包(Function Closure)是指在一个函数内部定义的函数,该内部函数可以访问其外部函数的变量、参数和其他内部函数,即使外部函数已经执行完毕,这些变量仍然可以被内部函数访问和操作。

闭包的特性主要包括以下几点:

  1. 内部函数访问外部函数的变量:内部函数可以访问其外部函数作用域中的变量,包括外部函数的参数、局部变量和其他内部函数定义的变量。

  2. 外部函数执行完毕后仍然可以访问变量:即使外部函数执行完毕,闭包仍然可以访问和操作外部函数中定义的变量。这是因为闭包维持了对外部函数作用域的引用,使得外部函数的变量不会被垃圾回收机制回收。

  3. 外部函数的变量可以被多个闭包共享:如果外部函数返回多个内部函数,这些内部函数都可以访问并共享外部函数的变量。每个闭包都有自己独立的作用域,但它们共享外部函数的作用域。

闭包的应用场景包括但不限于:

  • 封装私有变量:通过闭包可以创建私有变量和私有函数,只有内部函数可以访问它们,外部无法直接访问,实现了信息隐藏和封装的效果。
  • 创建函数工厂:通过外部函数返回内部函数,可以创建具有不同状态或配置的函数实例,用于生成特定功能的函数。
  • 延迟执行:通过闭包可以实现延迟执行的效果,将函数的执行推迟到某个特定的时机。

需要注意的是,闭包在使用不当的情况下可能会导致内存泄漏,因为闭包会保留对外部函数作用域的引用,导致相关的变量无法被垃圾回收。因此,在使用闭包时,需要注意避免产生不必要的内存泄漏,及时释放不再需要的资源。

9. 原型和原型链

原型(Prototype)是 JavaScript 中的一个概念,每个对象都有一个指向它的原型对象的内部链接。原型对象可以包含共享的属性和方法,可以被其他对象通过原型链继承和访问。

原型链(Prototype Chain)是一种通过对象的原型链接到其他对象形成的链式结构。当访问一个对象的属性或方法时,如果该对象本身不存在该属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端(通常是 Object.prototype)。

下面是一个简单的原型链关系图示例:

    +-------------------+
    |   Object.prototype  |
    +-------------------+
            ^
            |
            |
    +-------------------+
    |     Person.prototype  |
    +-------------------+
            ^
            |
            |
    +------------------+
    |        person         |
    +------------------+    

在上面的示例中:

  • Object.prototype 是 JavaScript 中所有对象的根原型对象。
  • Person.prototype 是自定义的原型对象,包含了一些共享的属性和方法。
  • person 是通过 Person 构造函数创建的对象,它通过原型链连接到 Person.prototype,同时也继承了 Object.prototype 上的属性和方法。

通过原型链,person 对象可以访问 Person.prototype 上定义的属性和方法,如果在 Person.prototype 上没有找到,会继续沿着原型链向上查找,直到找到或到达原型链的顶端。

请注意,原型链中的每个对象都有一个 __proto__ 属性,它指向该对象的原型对象。在现代 JavaScript 中,可以使用 Object.getPrototypeOf(obj) 方法来获取对象的原型。

10. 继承的方式

  1. 原型链继承:
    • 原理:通过将一个对象的实例赋值给另一个对象的原型来实现继承关系。
    • 优点:
      • 简单易懂,容易理解和实现。
      • 可以继承父对象的属性和方法。
    • 缺点:
      • 无法传递参数给父对象的构造函数。
      • 所有子对象共享同一个原型对象,对原型的修改会影响所有继承自该原型的对象。
    • 示例代码:
function Parent() {
  this.name = 'Parent';
}

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

function Child() {
  this.name = 'Child';
}

Child.prototype = new Parent();

var child = new Child();
child.sayHello(); // 输出:Hello, I am Child
  1. 构造函数继承(借用构造函数):
    • 原理:在子对象的构造函数中使用 callapply 方法调用父对象的构造函数。
    • 优点:
      • 可以传递参数给父对象的构造函数。
      • 每个子对象拥有独立的属性和方法。
    • 缺点:
      • 无法继承父对象原型上的方法。
      • 无法实现函数复用。
    • 示例代码:
function Parent(name) {
  this.name = name;
}

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

function Child(name) {
  Parent.call(this, name);
}

var child = new Child('Child');
child.sayHello(); // 输出:TypeError: child.sayHello is not a function
  1. 组合继承(原型链继承 + 构造函数继承):
    • 原理:同时使用原型链继承和构造函数继承的方式。
    • 优点:
      • 既可以继承父对象的属性和方法,又可以拥有独立的属性和方法。
      • 父对象的原型上的方法也能被继承。
    • 缺点:调用了两次父对象的构造函数,存在重复的属性和方法。
    • 示例代码:
function Parent(name) {
  this.name = name;
}

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

function Child(name) {
  Parent.call(this, name);
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var child = new Child('Child');
child.sayHello(); // 输出:Hello, I am Child
  1. 原型式继承:
    • 原理:使用一个中间对象作为桥梁,通过原型链继承的方式创建新对象。
    • 优点:简单易用,可以快速创建对象并继承原型上的属性和方法。
    • 缺点:对象之间存在引用共享的问题,修改一个对象的属性会影响其他对象。
    • 示例代码:
function createObject(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}

var parent = {
  name: 'Parent',
  sayHello: function() {
    console.log('Hello, I am ' + this.name);
  }
};

var child = createObject(parent);
child.name = 'Child';
child.sayHello(); // 输出:Hello, I am Child
  1. 寄生式继承:
    • 原理:在原型式继承的基础上增加了对新对象的扩展和修改。
    • 优点:可以在继承的基础上进行对象的定制化修改。
    • 缺点:对象之间存在引用共享的问题,修改一个对象的属性会影响其他对象。
    • 示例代码:
function createObject(obj) {
  var clone = Object.create(obj);
  clone.sayHello = function() {
    console.log('Hello, I am ' + this.name);
  };
  return clone;
}

var parent = {
  name: 'Parent'
};

var child = createObject(parent);
child.name = 'Child';
child.sayHello(); // 输出:Hello, I am Child
  1. 寄生组合式继承(最常用):

    • 原理:通过借用构造函数继承属性,通过原型链继承方法,使用寄生式继承修复构造函数继承中的问题。
    • 优点:避免了属性共享和相互影响的问题;既能继承父类原型上的属性和方法,又能向父类传递参数。
    • 缺点:相对复杂一些。
    • 示例代码:
function inheritPrototype(subType, superType) {
  // 创建父类原型的副本
  const prototype = Object.create(superType.prototype);
  // 将副本的构造函数指向子类
  prototype.constructor = subType;
  // 将副本设置为子类的原型
  subType.prototype = prototype;
}

// 父类
function SuperType(name) {
  this.name = name;
}

SuperType.prototype.sayHello = function() {
  console.log("Hello, " + this.name);
};

// 子类
function SubType(name, age) {
  // 借用构造函数继承父类的属性
  SuperType.call(this, name);
  this.age = age;
}

// 使用寄生组合式继承
inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function() {
  console.log("I'm " + this.age + " years old.");
};

// 测试
const instance = new SubType("Alice", 25);
instance.sayHello(); // 输出: Hello, Alice
instance.sayAge(); // 输出: I'm 25 years old.

11. 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

页面从输入 URL 到加载显示完成的过程中,经历了以下主要步骤:

  1. URL 解析:浏览器解析输入的 URL,提取出协议、主机、端口、路径等信息。

  2. DNS 解析:浏览器根据主机信息发起 DNS 查询,将主机名解析为对应的 IP 地址。

  3. TCP 连接:浏览器与服务器建立 TCP 连接。这个过程涉及 TCP 的三次握手,确保双方能够可靠地进行数据传输。

  4. 发起请求:浏览器发送 HTTP 请求到服务器,包括请求方法、请求头、请求体等信息。

  5. 服务器处理请求:服务器接收到请求后,根据请求的资源路径和方法进行处理,可能涉及后端代码的执行、数据库查询等操作。

  6. 响应生成:服务器根据请求的处理结果生成 HTTP 响应,包括响应状态码、响应头、响应体等信息。

  7. 响应传输:服务器将生成的响应通过 TCP 连接发送回浏览器。

  8. 浏览器接收响应:浏览器接收到响应后,根据响应的状态码判断请求是否成功,然后开始解析响应。

  9. HTML 解析:浏览器解析 HTML 文档,构建 DOM 树,解析过程中可能会遇到外部资源(如样式表、脚本等),需要进行进一步的请求和加载。

  10. 样式和布局计算:浏览器解析 CSS 样式表,计算元素的样式和布局信息,确定每个元素在页面中的位置和大小。

  11. 渲染页面:浏览器根据 DOM 树和样式信息,将页面内容渲染到屏幕上。

  12. 脚本执行:如果页面中有 JavaScript 脚本,浏览器会执行脚本,可能会修改 DOM 树、触发样式和布局的重新计算等。

  13. 加载外部资源:浏览器继续加载页面中引用的外部资源,如图片、字体、媒体文件等。

  14. 页面加载完成:当所有资源都加载完成并渲染完毕,页面加载显示完成。

12. 事件循环机制

事件循环机制是 JavaScript 引擎中用于管理和执行任务的一种机制。它确保 JavaScript 代码和事件的处理是异步的,并能够保证任务的执行顺序和时机。

事件循环机制主要包括以下几个部分:

  1. 任务队列(Task Queue):任务队列用于存储待执行的任务。任务可以是宏任务(Macrotasks)或微任务(Microtasks)。

  2. 宏任务(Macrotasks):宏任务包括定时器回调函数(setTimeout、setInterval)、事件回调函数、Ajax 请求回调等。

  3. 微任务(Microtasks):微任务包括 Promise 的回调函数(then、catch、finally)、MutationObserver 的回调、process.nextTick 等。

  4. 微任务队列(Microtask Queue):微任务队列用于存储待执行的微任务。微任务的优先级高于宏任务。

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

  1. 执行当前执行上下文中的同步任务。

  2. 检查微任务队列,如果有微任务,则依次执行队列中的微任务,直到微任务队列为空。

  3. 更新页面渲染。

  4. 从宏任务队列中选择一个宏任务执行。执行完毕后,回到第2步。

  5. 重复执行第2步至第4步,直到所有任务都执行完毕。

需要注意的是,事件循环机制的执行是单线程的,即一次只能执行一个任务,任务的执行顺序由任务队列中的任务顺序决定。同时,微任务队列优先级高于宏任务队列,所以微任务会在宏任务之前执行。

这种事件循环机制的设计使得 JavaScript 能够处理异步任务,保证了代码的执行和事件的响应不会阻塞主线程,从而保证了页面的流畅性和响应性。

13. Promise

Promise 是 JavaScript 中用于处理异步操作的一种机制。它可以用于管理和处理异步操作的状态和结果,以便更加简洁和可读地编写异步代码。

Promise 的特点如下:

  1. 异步操作:Promise 主要用于处理异步操作,如网络请求、文件读取等。它可以将异步操作封装成一个 Promise 对象,然后通过 Promise 的方法链式调用来处理操作的状态和结果。

  2. 状态:Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当一个 Promise 对象被创建时,它的初始状态是 pending。异步操作执行完成后,可以将 Promise 置为 fulfilled(成功)或 rejected(失败)状态。

  3. then 方法:Promise 提供了 then 方法,用于注册操作成功时的回调函数,以及处理操作失败的情况。then 方法可以链式调用,使得代码更加清晰和易于维护。

  4. 错误处理:Promise 通过 catch 方法来处理操作过程中出现的错误。catch 方法可以捕获 Promise 链中的任何一个被 rejected 的 Promise,方便统一处理错误。

使用 Promise 的基本流程如下:

  1. 创建 Promise 对象:通过 new 关键字创建一个 Promise 对象,并传入执行异步操作的函数。

  2. 处理操作结果:使用 then 方法注册操作成功时的回调函数,并使用 catch 方法处理操作过程中的错误。

  3. 异步操作完成:在异步操作完成后,根据操作的结果调用 resolve 或 reject 方法,将 Promise 对象的状态置为 fulfilled(成功)或 rejected(失败)。

Promise 的优点包括:

  1. 异步代码更加清晰:Promise 的链式调用可以使异步代码的逻辑更加清晰和易于阅读,避免了回调地狱。

  2. 错误处理更方便:Promise 提供了统一的错误处理机制,使用 catch 方法可以捕获 Promise 链中的任何一个被 rejected 的 Promise,并进行统一的错误处理。

  3. 支持并发和串行操作:Promise 可以很方便地实现多个异步操作的并发和串行执行。

  4. 更好的代码组织和复用:Promise 的链式调用使得代码的组织和复用更加容易,可以将各个步骤的操作封装成独立的函数,方便重复使用。

14. 为什么0.1+0.2不等于0.3 ?

在 JavaScript 中,由于使用的是二进制浮点数表示法(IEEE 754标准),导致某些十进制小数无法精确表示为二进制浮点数。这就是为什么在某些情况下,0.1 + 0.2 的结果不等于 0.3。

在二进制浮点数中,小数部分以分数的形式表示,使用二进制表示的分数可能会无限循环,而浮点数只有有限的位数来存储。因此,将十进制的小数转换为二进制浮点数时,可能会产生舍入误差。

具体到 0.1 + 0.2 的例子,0.1 和 0.2 都无法精确表示为二进制浮点数。当进行加法运算时,会涉及到将这两个近似值相加,而在二进制浮点数中进行相加时可能会产生舍入误差。因此,最终的结果可能是一个非精确的近似值,而不是精确的 0.3。

要避免这种问题,可以使用一些处理浮点数精度的方法,例如:

  1. 使用整数运算:将需要进行精确计算的小数转换为整数进行计算,然后再转换回小数。

  2. 使用toFixed()方法:可以使用 JavaScript 的 toFixed() 方法将结果四舍五入为指定小数位数的字符串表示,例如 (0.1 + 0.2).toFixed(1)。

  3. 使用专门的库:有一些第三方库(如 Decimal.js、Big.js 等)提供了更精确的浮点数运算功能,可以用于处理高精度的计算需求。

15. 浏览器缓存

浏览器缓存是指浏览器在访问网页时将一些数据存储在本地,以便在后续访问相同资源时可以直接使用本地缓存,提高网页加载速度和减少网络流量消耗。浏览器缓存分为两种类型:强缓存和协商缓存。

  1. 强缓存:浏览器在请求资源时,首先会检查该资源的缓存标识(如响应头中的 Cache-Control 和 Expires),如果满足缓存条件,则直接使用本地缓存,不发送请求到服务器。常见的缓存策略有:

    • Cache-Control:可以通过设置 max-age 参数来指定资源的有效期,例如 Cache-Control: max-age=3600 表示该资源在请求后的 3600 秒内有效。
    • Expires:是一个具体的过期时间,通过设置一个日期时间值来指定资源的有效期。
  2. 协商缓存:如果资源的缓存标识表明该资源已过期或需要验证,浏览器会发送请求到服务器,服务器通过判断资源的最新状态来决定是否返回新的资源或告知浏览器继续使用本地缓存。常见的缓存策略有:

    • Last-Modified 和 If-Modified-Since:服务器在响应头中返回 Last-Modified 字段表示资源的最后修改时间,浏览器在后续请求中通过 If-Modified-Since 字段将上次资源的 Last-Modified 值发送给服务器,服务器根据这个值来判断资源是否有更新。
    • ETag 和 If-None-Match:服务器在响应头中返回 ETag 字段表示资源的唯一标识符,浏览器在后续请求中通过 If-None-Match 字段将上次资源的 ETag 值发送给服务器,服务器根据这个值来判断资源是否有更新。

浏览器缓存可以提高网页的加载速度和用户体验,减轻服务器的负载和网络流量消耗。但在开发过程中,可能需要注意缓存对页面更新的影响,需要及时更新缓存或使用缓存策略来控制缓存行为,以确保用户获取到最新的资源。

16. 浏览器重排和重绘

浏览器渲染过程中的两个重要概念是重排(reflow)和重绘(repaint),它们都涉及到页面布局和元素样式的变化。

  1. 重排(reflow):

    • 重排是指浏览器计算并确定页面中所有元素的位置和大小,并根据最新的布局信息重新绘制页面的过程。
    • 重排通常发生在以下情况下:
      • 添加、删除、修改 DOM 元素。
      • 修改元素的样式,例如改变尺寸、位置、字体大小等。
      • 浏览器窗口大小变化。
      • 用户事件触发,如滚动、改变窗口大小等。
    • 重排的过程比较耗费性能,会导致页面重新布局和重绘,影响页面的响应速度和流畅度。
  2. 重绘(repaint):

    • 重绘是指在重排之后,浏览器根据最新的元素样式信息重新绘制页面的过程,但并不涉及元素的位置和大小变化。
    • 重绘通常发生在以下情况下:
      • 修改元素的颜色、背景色、文本颜色等只影响元素外观而不影响布局的样式属性。
    • 重绘的代价相对较小,不涉及布局计算,仅仅是重新绘制元素的外观。

由于重排和重绘都会涉及到页面的重新计算和绘制,因此在性能优化方面可以采取以下策略:

  • 减少重排和重绘的次数:合并多次操作,尽量一次性进行修改,使用 CSS3 的 transform 和 opacity 属性等能够减少重排和重绘的属性。
  • 使用文档片段(Document Fragment)进行批量操作:将多个 DOM 操作放在文档片段中进行,最后一次性插入文档,减少重排次数。
  • 使用 CSS3 动画代替 JavaScript 操作:CSS3 动画通常利用硬件加速,性能更高效。
  • 使用虚拟 DOM 技术(如 React、Vue 等框架):通过虚拟 DOM 的优化算法,减少不必要的 DOM 操作和重排。

17. 前端如何进行seo优化

前端开发可以采取以下措施来进行 SEO(搜索引擎优化)优化:

  1. 合理的标题、描述和关键词: 确保每个页面都有独特且描述准确的<title>标签和<meta name="description">标签。这有助于搜索引擎理解页面内容,同时也为用户在搜索结果中显示更具吸引力的信息。
  2. 语义化的 HTML 结构: 使用语义化的 HTML 标签,如<header><footer><nav><section><article>等,以帮助搜索引擎更好地理解页面结构和内容。
  3. 良好的页面结构: 确保页面具有清晰的层次结构和导航,这有助于搜索引擎爬虫更容易地抓取和索引页面内容。
  4. 优化 URL 结构: 使用简洁且描述性的 URL,避免过长或包含不必要的参数。这有助于搜索引擎理解页面内容,并提高用户体验。
  5. 使用 header 标签: 使用<h1><h6>等 header 标签来组织页面内容,确保每个页面只有一个<h1>标签。这有助于搜索引擎理解页面的主题和重要性。
  6. 图片优化: 为所有图片添加alt属性,以便搜索引擎了解图片内容。同时,优化图片大小和格式,以提高页面加载速度。
  7. 内部链接和锚文本: 合理地使用内部链接和有意义的锚文本,以帮助搜索引擎更好地理解站点结构和内容之间的关联。
  8. 响应式设计: 确保网站适应不同设备和屏幕尺寸,提供良好的用户体验。搜索引擎倾向于对移动友好的网站给予更高的排名。
  9. 网站速度优化: 提高网站加载速度,减少页面渲染时间。这包括压缩资源、合并文件、优化图片、使用浏览器缓存等。搜索引擎会考虑网站速度作为排名的因素之一。
  10. 遵循 W3C 标准: 确保代码符合 W3C 标准,减少 HTML、CSS 和 JavaScript 的错误。这有助于搜索引擎更容易地抓取和解析页面内容。
  11. 生成 XML Sitemap: 为网站创建一个 XML Sitemap,并提交给搜索引擎。这有助于搜索引擎更有效地抓取和索引网站内容。
  12. 使用结构化数据: 使用结构化数据(如 Schema.org、JSON-LD、Microdata 等)来标注页面内容,有助于搜索引擎更准确地理解页面信息,并可能在搜索结果中显示为富文本摘要,提高点击率。
  13. 使用 robots.txt 控制爬虫访问: 合理设置 robots.txt 文件,指定搜索引擎爬虫可以访问和抓取的页面,避免爬虫抓取不相关或低质量的页面。
  14. 优化站内搜索: 提供高效、准确的站内搜索功能,有助于提高用户体验和用户停留时间,间接影响搜索排名。
  15. 社交媒体整合: 将网站内容与社交媒体平台整合,提高内容的曝光度和分享率,增加外部链接,有助于提高搜索排名。
  16. 网站安全: 使用 HTTPS 加密,保护用户数据和隐私。搜索引擎会将安全性作为排名因素之一。

18. Websocket与Ajax的区别?

WebSocket和Ajax是两种在Web开发中用于实现数据交互的不同技术,它们有以下区别:

  1. 通信方式:

    • Ajax(Asynchronous JavaScript and XML)是通过HTTP协议进行通信的。它使用异步的HTTP请求来向服务器发送数据并获取响应。
    • WebSocket是一种全双工通信协议,它在客户端和服务器之间建立持久的连接,可以实现双向的实时通信。
  2. 实时性和效率:

    • Ajax是基于请求和响应的方式,每次数据交互都需要发起HTTP请求和等待服务器响应,实时性较差。
    • WebSocket通过建立长连接,实现了实时双向通信,数据的传输效率高,可以更快地实现实时更新和推送。
  3. 数据量和频率:

    • Ajax适用于小量数据的交互,一般用于发送和接收少量数据。
    • WebSocket适用于大量数据的交互,可以实现高频率的数据传输,适合实时性要求较高的应用场景。
  4. 服务器资源消耗:

    • Ajax每次请求都需要建立和断开连接,对服务器资源消耗较少。
    • WebSocket建立一次连接后可以保持长时间的通信,服务器需要维护连接状态和处理多个客户端的数据交互,对服务器资源消耗较高。
  5. 跨域请求:

    • Ajax受同源策略限制,存在跨域请求的限制,需要通过代理或跨域资源共享(CORS)来解决。
    • WebSocket可以跨域进行通信,不受同源策略限制。

19. HTTP/3相比HTTP/2解决了哪些问题?

HTTP/3 是互联网工程任务组(IETF)制定的新一代 HTTP(超文本传输协议)标准,它基于 QUIC 协议(快速 UDP 互联网连接),旨在解决 HTTP/2 存在的一些问题,提高网络性能和安全性。相较于 HTTP/2,HTTP/3 主要解决了以下问题:

  1. 首部阻塞(Head-of-line blocking):在 HTTP/2 中,多个请求和响应通过同一个 TCP 连接进行多路复用。由于 TCP 是面向连接的、可靠的传输协议,要求数据按顺序到达,当其中一个数据包丢失或延迟时,整个连接的其他数据包必须等待,直到丢失的数据包被重新传输并到达。这种现象被称为首部阻塞。HTTP/3 采用基于 UDP 的 QUIC 协议,它允许单独处理每个数据流,从而消除了首部阻塞问题。
  2. 连接建立延迟:HTTP/2 在建立 TCP 连接之后,还需要通过 TLS 握手建立安全连接。这意味着需要多次往返才能完成连接建立。而 HTTP/3 使用 QUIC 协议,它将传输层(TCP)和安全层(TLS)整合在一起,从而减少了连接建立所需的往返次数。对于已经与服务器建立过连接的客户端,QUIC 甚至可以实现 0-RTT(零往返时间)连接建立,大幅降低了延迟。
  3. 更好的网络路径迁移:当客户端或服务器的 IP 地址发生变化时(如移动设备在 Wi-Fi 和移动网络之间切换),TCP 连接可能中断,需要重新建立。HTTP/3 的 QUIC 协议提供了内置的连接迁移特性,允许在 IP 地址变化时保持连接状态,减少中断和延迟。
  4. 拥塞控制和恢复:由于 HTTP/3 使用 QUIC 协议,它可以在单独的数据流上实现更细粒度的拥塞控制。这意味着当一个数据流受到网络拥塞影响时,其他数据流可以继续正常传输,不受影响。同时,QUIC 采用了更先进的丢包恢复机制,使得数据传输更加高效和可靠。

尽管 HTTP/3 有很多优势,但它目前仍在逐步推广和普及。浏览器和服务器需要支持 QUIC 协议,以便充分利用 HTTP/3 带来的改进。

20. react生命周期

在 React 中,组件的生命周期可以分为三个阶段:挂载阶段(Mounting)、更新阶段(Updating)和卸载阶段(Unmounting)。下面是每个阶段中常用的生命周期方法:

  1. 挂载阶段(Mounting):

    • constructor:组件的构造函数,在组件被创建时调用,用于初始化状态和绑定方法。
    • static getDerivedStateFromProps:在组件实例化和接收新的 props 时调用,用于根据新的 props 更新状态。
    • render:根据状态和 props 渲染组件的 UI。
    • componentDidMount:在组件挂载到 DOM 后调用,可进行异步操作、订阅事件等。
  2. 更新阶段(Updating):

    • static getDerivedStateFromProps:在接收到新的 props 时调用,用于根据新的 props 更新状态。
    • shouldComponentUpdate:在组件更新前调用,决定是否重新渲染组件,默认返回 true,可以通过比较前后 props 和状态来进行性能优化。
    • render:重新渲染组件的 UI。
    • componentDidUpdate:在组件更新后调用,可进行 DOM 操作、网络请求等。
  3. 卸载阶段(Unmounting):

    • componentWillUnmount:在组件即将被卸载和销毁前调用,可进行清理操作、取消订阅等。

此外,React 还提供了一些其他的生命周期方法用于处理特定的情况,例如错误处理、获取快照等。在 React 16.3 之后,一些生命周期方法已被标记为过时(deprecated),推荐使用新的生命周期方法或使用函数式组件和 Hooks 来代替。

需要注意的是,React 17 之后,一些生命周期方法(如 componentWillMount、componentWillUpdate、componentWillReceiveProps)已被移除,可以使用其他替代方法来完成相同的操作。

对Webpack做过哪些优化?

Webpack 是一个非常强大的模块打包器,它可以帮助开发者处理代码依赖、打包、压缩等任务。在使用 Webpack 的过程中,我们可以通过一些方法优化项目,提高性能和用户体验。以下是一些常见的 Webpack 优化方法:

  1. Tree shaking:通过此技术,Webpack 可以消除未使用的代码,从而减少最终包的大小。为了实现这一点,确保在 package.json 文件中设置 "sideEffects" 选项。
  2. 代码分割(Code Splitting):将代码分割成不同的 chunks,从而实现按需加载和并行加载。这可以减少首次加载时间和浏览器解析时间。可以使用 SplitChunksPlugin 和动态 import() 实现代码分割。
  3. 懒加载(Lazy Loading):懒加载是一种按需加载策略,只有在实际需要时才加载某些代码。这可以显著减少首屏加载时间。
  4. 使用缓存:通过设置 cache-loader、HardSourceWebpackPlugin 或其他缓存插件,可以利用缓存加快构建速度。
  5. 压缩代码:使用插件如 TerserPlugin(用于 JavaScript)和 MiniCssExtractPlugin(用于 CSS)对代码进行压缩,减少代码体积,提高加载速度。
  6. 使用 DLL:通过使用 DllPlugin 和 DllReferencePlugin,可以将第三方库与应用程序代码分离,从而减少构建时间。
  7. 配置 resolve.alias:通过配置 resolve.alias,可以缩短查找模块的路径,从而提高构建速度。
  8. 使用 Web Workers 或 Service Workers:通过将一些任务放在后台线程中处理,可以提高应用程序的性能。
  9. 提取 CSS:通过使用 MiniCssExtractPlugin,可以将 CSS 从 JS 中分离出来,提高加载性能。
  10. 使用 Loaders 和 Babel:通过配置不同的 loaders 和 Babel 插件,可以在构建过程中优化代码,例如转换 ES6 语法、移除 console.log 等。
  11. 配置性能提示:通过配置 performance.hints 和 performance.assetFilter,可以监控和优化构建产物的大小。
  12. 使用 Webpack Bundle Analyzer:通过这个插件,可以分析和可视化 Webpack 输出的文件,从而帮助发现潜在的优化点。

Wepback的生命周期有哪些?

Webpack 的生命周期主要由以下几个阶段组成:

  1. 初始化(Initialization):在这个阶段,Webpack 会读取配置文件(如:webpack.config.js)和命令行参数,然后创建一个新的 Compiler 实例。这个实例包含了整个构建过程中的配置、插件、Loaders 等相关信息。
  2. 解析(Resolution):Webpack 根据入口文件(entry)开始逐层解析依赖关系。对于每个解析到的模块文件,Webpack 都会检查是否需要使用对应的 Loaders 进行转换和处理。此阶段的主要目的是创建一个依赖图(Dependency Graph),其中包含了项目中所有模块及其相互依赖关系。
  3. 编译(Compilation):在这个阶段,Webpack 开始根据依赖图逐个编译模块。对于每个模块,Webpack 会首先执行预编译任务(如使用 Babel 转换 ES6 语法),然后调用相应的 Loaders 处理模块内容。编译完成后,Webpack 会生成一个中间表示(Intermediate Representation,简称 IR),这是一个包含所有模块及其处理结果的对象。
  4. 输出(Emit):Webpack 将 IR 转换为最终的输出文件(如:bundle.js)。在这个阶段,Webpack 会执行优化任务(如代码压缩、文件名哈希化),并将处理后的文件写入磁盘。输出完成后,Webpack 会触发相应的钩子(如:onEmit、afterEmit),以便插件可以执行自定义操作。
  5. 完成(Done):构建流程完成后,Webpack 会触发一系列完成钩子(如:onDone、afterDone),以便插件可以执行清理和统计任务。此时,Webpack 会输出构建结果,包括处理后的文件、错误、警告等信息。

Webpack有哪些常见的Loader?

  1. babel-loader:用于将 ES6/ES7 语法转换为浏览器兼容的 ES5 语法。
  2. css-loader:解析 CSS 文件中的 @import 和 url(),将 CSS 转换为 JavaScript 模块。
  3. style-loader:将 CSS 作为样式标签插入到 HTML 文档中。
  4. less-loader:将 Less 代码转换为 CSS 代码。
  5. sass-loader:将 Sass/SCSS 代码转换为 CSS 代码。
  6. postcss-loader:使用 PostCSS 对 CSS 进行处理,如自动添加浏览器前缀、压缩 CSS 等。
  7. file-loader:处理文件引用,将文件复制到输出目录,并返回文件的 URL。
  8. url-loader:将文件以 base64 编码的形式内联到代码中,可以减少 HTTP 请求次数。
  9. image-webpack-loader:压缩和优化图像文件。
  10. ts-loader:将 TypeScript 转换为 JavaScript。

Webpack有哪些常见的Plugin?

  1. HtmlWebpackPlugin:生成一个 HTML 文件,并自动引入所有生成的脚本和样式。
  2. MiniCssExtractPlugin:将 CSS 提取为单独的文件,而不是将其内联到 JavaScript 中。
  3. CleanWebpackPlugin:在每次构建前清理输出目录。
  4. DefinePlugin:允许在编译时创建全局常量,用于在开发和生产环境中区分不同的行为。
  5. TerserPlugin:压缩和优化 JavaScript 代码。
  6. OptimizeCSSAssetsPlugin:压缩和优化 CSS 文件。
  7. HotModuleReplacementPlugin:实现模块热替换,用于开发环境。
  8. CopyWebpackPlugin:将静态资源复制到输出目录。
  9. SplitChunksPlugin:实现代码分割,提高加载性能。
  10. CompressionWebpackPlugin:使用 Gzip 或 Brotli 压缩生成的资源文件。

Webpack中Loader和Plugin的区别?

  1. Loader 用于转换和处理模块。它是一个函数,接收源文件作为输入,并输出处理后的结果。Loader 的作用是对源代码进行处理,例如编译、压缩、转换等。常见的 Loader 任务包括:将 Sass/SCSS 转换为 CSS、将 ES6 代码转换为浏览器兼容的代码等。
  2. Plugin 是用于扩展 Webpack 功能的插件。它可以在构建流程中的不同阶段执行不同的任务,如清理输出目录、生成 HTML 文件等。Plugin 的作用范围更广泛,它可以访问 Webpack 的整个编译过程,从而实现各种复杂的功能。

Loader 主要负责对模块的转换和处理,而 Plugin 负责完成更广泛的任务,包括编译过程中的各种钩子和自定义功能。

Wepback5有哪些新特性?

  1. 持久缓存:Webpack 5 引入了文件系统缓存,可以在多次构建之间持久存储中间结果。这可以显著提高重复构建的速度。
  2. 模块联邦(Module Federation):这是一个允许多个独立构建共享代码的新特性。模块联邦可以在不牺牲性能的情况下实现微前端架构。
  3. 更好的 Tree Shaking:Webpack 5 改进了 Tree Shaking,可以更有效地移除无用代码。
  4. 默认支持 Content Hash:现在默认为输出文件名添加内容哈希,以实现长期缓存优化。
  5. 更小的运行时代码:Webpack 5 的运行时代码更小,有助于减小最终生成的包的大小。
  6. 改进的代码分割策略:Webpack 5 对 SplitChunksPlugin 的默认配置进行了优化,以更好地支持公共代码的提取。
  7. 移除了一些过时的特性:Webpack 5 移除了一些不推荐使用的特性,如 Node.js 的 polyfill。