我的面试题总结(持续更新)

163 阅读24分钟

ES6

Promise

什么是Promise

异步编程的一种解决方案,比传统的解决方案(回调函数形成回调地狱)更加合理和更加强大

  • 链式操作减低了编码难度
  • 代码可读性明显增强

React

JSX是什么,它和JS有什么区别

JSX是react的语法糖,它允许在html中写JS,它不能被浏览器直接识别,需要通过webpack、babel之类的编译工具转换为JS执行 为什么在文件中没有使用react,也要在文件顶部import React from “react”

只要使用了jsx,就需要引用react,因为jsx本质就是React.createElement

JSX与JS的区别:

  1. JS可以被打包工具直接编译,不需要额外转换,jsx需要通过babel编译,它是React.createElement的语法糖,使用jsx等价于React.createElement
  2. jsx是js的语法扩展,允许在html中写JS;JS是原生写法,需要通过script标签引入

React合成事件

在React中,合成事件(Synthetic Event)是React为了解决跨浏览器兼容性问题而引入的一种事件系统。它提供了一种统一的事件接口,挂载在组件的 DOM 元素上,使得开发人员可以更方便地处理各种浏览器中的事件。React将原生浏览器事件封装为合成事件,并提供了一套跨浏览器的事件处理机制。

React的合成事件具有以下特点:

  1. 跨浏览器兼容性:合成事件提供了一致的事件处理方式,使得开发人员不必担心不同浏览器之间的事件差异。
  2. 事件委托:React使用事件委托的方式来处理事件,将所有事件处理程序注册到顶层容器上,而不是每个单独的元素上,这可以提高事件处理的性能和效率。
  3. 事件池:React采用了事件池的技术来管理合成事件,以减少事件对象的创建和回收,从而提高性能并减少内存占用。

可以通过访问e.nativeEvent来获取底层的原生事件

需要注意的是,由于合成事件是React提供的一种抽象层,它并不是原生的浏览器事件,因此在一些特殊情况下,可能需要使用原生事件来处理特定的需求。

React中的高阶组件HOC

高阶组件:可以被看作是React对装饰模式的一种实现。它是一个函数,接受一个组件作为参数,并返回一个新的组件。通过高阶组件,我们可以在不修改原有组件的逻辑的情况下,对组件的属性和方法进行拓展操作,从而实现代码的复用和功能增强。

Hooks:Hooks提供了一系列钩子函数,如useState、useEffect等。通过组合这些钩子函数,我们可以将逻辑划分为可复用的小块,并在函数组件中组合这些块来构建具有复杂功能的组件。

总结来说,高阶组件是一个函数,通过对组件进行装饰来实现代码复用和功能增强。而React的Hooks通过组合模式提供了一种现代的方式来实现高阶组件的功能,通过组合钩子函数来管理组件逻辑,使得代码更加简洁和直观。

拓展

继承模式

  • 高耦合性,子类父类紧密的绑定,继承层次结构的改变可能会影响到整个继承链的行为。
  • 多层次继承复杂性:当继承层次结构变得很深或很复杂时,维护和理解代码可能变得困难。子类可能继承了多个父类的属性和方法,导致代码的可读性和可维护性下降。

组合模式

  • 灵活性:组合允许你将多个不同的、独立的部分组合在一起,形成更复杂的组件或对象。这种方式更加灵活,可以根据需要进行组合和重组。
  • 低耦合性:通过组合,组件或对象之间的耦合性较低。每个组件或对象只关注自身的功能和责任,而不需要关注其他组件或对象的实现细节。这使得系统更加灵活,并且更容易进行扩展和修改。

个人碎碎念·,仅代表个人理解:

类组件给人的感觉就是大而全,牵一发而动全身,随着项目越来越大,代码也就愈加复杂
函数式组件,模块化,每一块只关注于自身的职责,我们需要什么就拿来什么,更清晰简洁

react和vue目前基本抛弃了类组件而转身拥抱函数组件

React.memo 和 useMemo

React.memo: 是一个高阶组件,用于对函数组件进行记忆(memoization)。它的作用是在组件的输入(props)没有发生变化时,阻止组件重新渲染,从而提高性能。
useMemo: 是一个用于性能优化的hook,用于在组件渲染过程中缓存计算结果。它接收一个函数和一个依赖数组作为参数,并返回缓存的计算结果。

React 受控组件和非受控组件

受控组件:在HTML的表单元素中,它们通常自己维护一套state,并随着用户的输入自己进行UI上的更新,这种行为是不被我们程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件

非受控组件:非受控组件,简单来讲,就是不受我们控制的组件一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态,当需要时,可以使用ref 查询 DOM并查找其当前值,如下:

绝大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React组件负责处理;当然如果选择非受控组件的话,表单数据就由DOM本身处理。

React hook

React hook 是什么?解决了什么问题?

React hooks

简单总结:

  1. useState - 用于在函数组件中管理和更新局部状态,比如表单输入、模态框的显示状态、简单的计数器等。
  2. useEffect - 用于执行数据获取、订阅、手动DOM操作或者其他需要在组件渲染后执行的操作。常见的场景包括数据获取、订阅更新、设置事件监听等。
  3. useContext - 用于在组件树中传递数据,特别是在多层嵌套组件中共享全局数据或应用程序状态。
  4. useReducer - 适用于管理复杂的组件级别的状态,特别是当状态之间有复杂的依赖关系时,可以使用类似于Redux的reducer函数进行状态管理。
  5. useCallback - 在性能优化方面有用,特别是当向子组件传递回调函数时,可以使用useCallback来避免不必要的重新渲染。
  6. useMemo - 用于对昂贵的计算进行优化,特别是在渲染过程中需要执行复杂计算或处理大量数据时。
  7. useRef - 用于处理DOM操作、动画、第三方库集成或者需要在函数组件渲染周期之间保持持久性数据的场景。
  8. useImperativeHandle - 通常与useRef一起使用,配合forwardRef用于向父组件暴露特定功能或方法,使得父组件可以直接操作子组件的实例。
import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  // 暴露给父组件的方法
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    getValue: () => {
      return inputRef.current.value;
    }
  }));

  return <input ref={inputRef} />;
});

// 在父组件中直接控制子组件的方法
const ParentComponent = () => {
  const inputRef = useRef(null);

  const handleFocusClick = () => {
    inputRef.current.focus();
  };

  const handleGetValueClick = () => {
    const value = inputRef.current.getValue();
    console.log(value);
  };

  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={handleFocusClick}>Focus Input</button>
      <button onClick={handleGetValueClick}>Get Input Value</button>
    </div>
  );
};

export default ParentComponent;


具体示例:「React 进阶」 React 全部 Hooks 使用大全 (包含 React v18 版本 )

React Fiber

  • React Fiber 是 React 中的一种新的协调机制,从 React 16 开始引入(在这以前是通过对组件树的递归遍历来进行更新)。
  • React Fiber 是一种用于实现虚拟DOM更新和组件渲染的算法和架构。
  • React Fiber 的目标是实现更高效的渲染,能够在需要时中断、终止和恢复渲染过程,以提高用户界面的响应性和性能。
  • Fiber 架构的设计目标之一是提供更细粒度的控制和调度能力。Fiber 架构通过将任务切片为小的工作单元,并使用任务优先级、时间切片等技术来调度和控制任务的执行顺序,从而实现更高级别的控制。

这里我们可以看一下Generator
Generator 是 ECMAScript 6 引入的一种函数类型,它可以通过 function* 关键字定义。

  • Generator 函数可以被暂停和继续执行,通过 yield 关键字可以产生一个值,并在下次调用时恢复执行。
  • async await 语法糖就是通过 promisegenerator 实现的

官方为什么不使用 Generatorgithub.com/facebook/re…

  1. Generator 不能在栈中间让出。比如你想在嵌套的函数调用中间让出, 首先你需要将这些函数都包装成Generator,另外这种栈中间的让出处理起来也比较麻烦,难以理解。除了语法开销,现有的生成器实现开销比较大,所以不如不用。
  2. Generator 是有状态的, 很难在中间恢复这些状态。
  3. Generator 函数具有传染性(contagious)的特性

为什么不能在条件语句中写 hook

这与React Hooks的底层设计的数据结构相关,先抛出结论:react维护了一个hooks链表保证hooks的顺序

React通过一个链表来保存所有的hooks,每次render都是按照顺序执行。同时,在DEV模式下,又通过一个数组hookTypesDev依次保存所有hook的名字。 如果你在条件语句中使用钩子,可能会导致钩子在不同渲染中的顺序发生变化,从而引发意外行为或错误。

setState是异步吗

在组件生命周期或React合成事件中,setState是异步
在setTimeout或者原生dom事件中,setState是同步

为什么react大部分情况setState是异步的呢?
假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。
React18以后,使用了createRoot api后,所有setState都是默认异步批量执行的

虚拟Diff

整个DOM-diff的过程:

  1. 用JS对象模拟DOM(虚拟DOM)
  2. 把此虚拟DOM转成真实DOM并插入页面中(render)
  3. 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)
  4. 把差异对象应用到真正的DOM树上(patch)

虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象表现。 状态变更时,记录新树和旧树的差异 最后把差异更新到真正的dom

不能用下标作为key值,当删除首节点时,剩余的key值都发生变化,性能消耗过大

react和vue Diff算法的区别

redux工作流程

image.png

  1. ⾸先,View(通过⽤户)发出 Action,发出⽅式就是使用 dispatch ⽅法
  2. 然后,Store 调⽤ Reducer 并且传⼊两个参数(当前的 State 和收到的 Action),Reducer 处理后返回新的 State
  3. State ⼀旦有变化,则 Store 会调⽤监听函数来通知 View 进行更新

TypeScript

TS 是什么?他相对于JavaScript有什么优势?

它是 JavaScript 的一个超集,通过添加可选的静态类型系统和其他特性来扩展 JavaScript。TypeScript 代码最终会被编译成普通的 JavaScript 代码,可以运行在任何支持 JavaScript 的平台上。

优势:

  • 静态类型检查: 可以在开发过程中捕获错误并提供更好的可读性。
  • 强大的开发工具支持: 提供代码补全、重构和静态错误检查等功能。(我们常用的vscode就对ts有良好的支持)
  • 更好的可维护性和可扩展性: 具有严格的类型系统,能更好地理解和维护代码,使用 ts 可以更容易地重构代码、添加新功能
  • 渐进式应用: 可以逐步将现有js代码库迁移到ts(逐渐体会到好处,无需直接全部重构)

综上所述,ts的这些特性特别适合开发大型应用程序和复杂项目。

TS 中有哪些内置类型?

  1. 基本类型:
  • number:表示数字类型。
  • string:表示字符串类型。
  • boolean:表示布尔类型。
  • symbol:表示唯一的、不变的值。
  • null:表示空值或空对象引用。
  • undefined:表示未定义的值。
  • void:表示没有任何类型,常用于函数没有返回值的情况。
  1. 复杂类型:
  • Array:表示数组类型,可以使用泛型来指定数组中元素的类型。
  • Tuple:表示元组类型,用于表示一个已知长度和固定类型的数组。
  • Object:表示对象类型,可以用于描述任意 JavaScript 对象的类型。
  • Function:表示函数类型,用于描述函数的参数和返回值类型。
  • Promise:表示 Promise 类型,用于处理异步操作并获取最终结果的值。
  1. 其他类型:
  • any:表示任意类型,可以是任何类型的值。(会关闭 ts 的类型检查)
  • unknown:表示未知类型,类似于 any 类型,但更加安全。(对 unknown 类型的值进行操作之前,必须进行类型检查或类型断言)
  • never:表示永远不存在的值的类型,常用于表示抛出异常或无法到达的终点。
function error(message: string): never {
 throw new Error(message);
}

TS 中 Declare关键字有什么作用?

declare 关键字的主要作用是告诉 TypeScript 编译器如何处理已经存在的外部代码或类型,以便使 TypeScript 项目能够与现有的 JavaScript 代码库更好地集成,并提供更好的类型检查和推断能力。

全局变量声明

描述外部模块或库

有的js库没有ts声明文件,假设就有这样一个库 aaa,我们使用时候会提示错误,我们想使用的时候就可以使用Declare关键字

declare let aaa

TypeScript 会将这个库的类型视为 any 类型,就无法提供针对这个库的类型检查和推断
要想ts提供类型检查的话我们可以在.d.ts编写这个库的类型声明

ps(当你在一个 .d.ts 文件中声明一个类型时,它会被自动视为全局范围的声明,因此不需要显式使用 declare 关键字)

TS 中的枚举(Enum)是什么?

枚举(Enum)是一种用于定义命名常量集合的数据类型

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

let playerDirection: Direction = Direction.Up

/*
在这个示例中,`Direction` 枚举定义了一组可能的方向,包括上、下、左、右。
可以通过枚举值的名字直接引用相应的值。
枚举值的默认行为是从 0 开始自增,但你也可以手动指定枚举值的数值。
*/

枚举可以帮助你在代码中使用易读的符号来表示固定的值,从而提高代码的可读性和可维护性。

TS 中常量枚举和普通枚举区别

  • 普通枚举: 普通枚举在编译后会生成真实的 JavaScript 对象以及对应的映射关系
  • 常量枚举: 其实就是是在 enum关键字前使用 const 修饰符,常量枚举会在编译阶段被移除,不会产生真实的枚举对象,被替换为相应的数值

因此当我们不需要一个对象,只需要对象的值,就可以使用常量枚举,这样就可以避免在编译时生成多余的代码和间接引用

TS 中 const 和 readonly 的区别

  • const用于变量,readonly用于属性
  • const在运行时检查,readonly在编译时检查
  • 使用const变量保存的数组,可以使用push,pop等方法。但是如果使用Readonly Array声明的数组不能使用push,pop等方法

TS 中 type 和 interface 的区别?

  • 它们都支持拓展但:type的拓展通过交叉操作符&实现,interface通过extends关键字实现
  • 同名的interface会自动合并,而type不行
  • type 关键字用于为现有类型创建别名。它可以用来命名任何类型,包括原始类型、联合类型、元组以及其他任何你想要命名的类型。
//基本类型例如:
type MyString = string
type MyNumber = number 
type MyBoolean = boolean

//元组类型
type ArrType = [number, number, string]
  • interface可以被类实现 implements,用来约束类的结构和行为,而type不能
    interface还可以通过extends关键字来继承其他接口,实现接口的复用
interface Printable {
    print: () => void;
}

class MyClass implements Printable {
    print() {
        console.log("Printing...");
    }
}
/*
在这个示例中,`MyClass` 类实现了 `Printable` 接口中定义的 `print` 方法。
如果 `MyClass` 没有实现 `Printable` 接口中定义的 `print` 方法,TypeScript 编译器将会报错。
`implements` 关键字有助于确保代码符合特定的接口或类的约定,提供了一种方式来强制执行特定类型的结构。这样做有助于防止在代码中引入潜在的错误或不一致性。
*/

总结:基本上,如果你要描述一个对象的形状或者你需要扩展一个对象的类型,你应该使用 interface。如果你需要定义一个类型,并且这个类型不仅仅是对象,你可能会倾向于使用 type

TS 中 keyof 和 typeof 作用 ?

keyof:keyof 操作符用于获取对象类型的所有键(key)的联合类型。它允许你从对象类型中提取键的名称,以便进一步操作这些键。

type Point = { x: number; y: number };
type PointKeys = keyof Point; // "x" | "y"

typeof: typeof 操作符用于获取变量或对象的类型。它可以用于提取变量的类型,也可以用于创建类型声明。

const obj = { a: 1, b: 2 };
type ObjType = typeof obj; // { a: number, b: number }
type 可以使用 typeof 获取实例的类型进行赋值  
let div = document.createElement('div');  
type B = typeof div

TS 中的常用工具类型(Utility Type)

  1. Partial<T>: 将类型 T 中的所有属性设置为可选。
  2. Required<T>: 将类型 T 中的所有属性设置为必选。
  3. Readonly<T>: 将类型 T 中的所有属性设置为只读。
  4. Record<K, T>: 创建一个由类型 K 中的键映射到类型 T 中的值的对象。
type Weekday = 'Monday' | 'Tuesday' | 'Wednesday' 

type DailySchedule = Record<Weekday, string>

const schedule: DailySchedule = {
  Monday: 'Gym',
  Tuesday: 'Work',
  Wednesday: 'Meeting',
}
  1. Omit<T, K>: 从类型 T 中排除指定的属性 K
interface Person {
 name: string
 age: number
 address: string
}

type PersonWithoutAddress = Omit<Person, 'address'>;

const person: PersonWithoutAddress = {
 name: 'John',
 age: 30,
}
  1. Pick<T, K>: 从类型 T 中选中指定的属性 K
interface Person {
  name: string
  age: number
  address: string
}

type PersonBasicInfo = Pick<Person, 'name' | 'age'>

const person: PersonBasicInfo = {
  name: 'John',
  age: 30,
}

更多工具类型列表和详细说明:TypeScript Handbook - Utility Types

TS 中的类

一个类包括以下内容:

  • 构造器(Constructor)
  • 属性(Properties)
  • 方法(Methods)
// 定义一个简单的类
class MyClass {
    // 类的属性
    myProperty: string;

    // 类的构造函数
    constructor(initialValue: string) {
        this.myProperty = initialValue;
    }

    // 类的方法
    myMethod(): void {
        console.log(this.myProperty);
    }
}

// 创建类的实例
const myObject = new MyClass("initial value");

// 调用类的方法
myObject.myMethod(); // 输出 "initial value"
  • 继承(Inheritance)
  • 封装(Encapsulation)
  • 多态(Polymorphism)
  • 抽象(Abstraction)

TS 中什么是方法重载?

允许你为同一个方法提供多个不同的函数类型定义。通过方法重载,你可以根据不同的参数类型或参数数量来调用不同的函数实现,从而增强函数的灵活性和可重用性。

class Example {
    // 方法重载
    public process(value: string): void;
    public process(value: number): void;
    public process(value: any): void {
        if (typeof value === "string") {
            console.log(`You passed a string: ${value}`);
        } else if (typeof value === "number") {
            console.log(`You passed a number: ${value}`);
        }
    }
}

const example = new Example();
example.process("hello"); // Output: You passed a string: hello
example.process(42); // Output: You passed a number: 42

TypeScript 编译器会根据调用方法时传递的参数类型或数量来自动选择调用适当的函数实现。

TS 中访问修饰符有哪些

TypeScript支持访问修饰符 public,private 和 protected,它们决定了类成员的可访问性。

  • 公共(public),类的所有成员,其子类以及该类的实例都可以访问。
  • 受保护(protected),该类及其子类的所有成员都可以访问它们。 但是该类的实例无法访问。
  • 私有(private),只有类的成员可以访问它们。

如果未指定访问修饰符,则它是隐式公共的,因为它符合 JavaScript 的便利性。

TS 中的装饰器

装饰器是一种特殊类型的声明用于在类声明、方法、属性或参数上附加元数据,并以声明性的方式扩展类的行为
可以通过在声明前面添加 @装饰器名 的语法来实现
通俗的来说就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性,方法,参数的功能
装饰器的分类: 类装饰器、属性装饰器、方法装饰器、参数装饰器

//类装饰器:应用于类声明上,用于修改类的行为或添加元数据。
function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    };
}

@classDecorator
class MyClass {
    property = "property";
}

console.log(new MyClass()); // 输出:MyClass { newProperty: 'new property', hello: 'override' }
--------------------------------------------------------------------------------
//方法装饰器: 应用于类方法上,用于修改方法的行为或添加元数据。
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("Method Decorator called on: ", target, propertyKey, descriptor);
}

class MyClass {
    @methodDecorator
    myMethod() {
        console.log("Hello, world!");
    }
}
--------------------------------------------------------------------------------
//属性装饰器:应用于类属性上,用于修改属性的行为或添加元数据。
function propertyDecorator(target: any, propertyKey: string) {
    console.log("Property Decorator called on: ", target, propertyKey);
}

class MyClass {
    @propertyDecorator
    myProperty: string;
}


WebSocket

定义:

WebSocket 是一种在单个TCP连接上进行全双工通信的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

特点包括:

  1. 在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输
  2. 建立在 TCP 协议之上,服务器端的实现比较容易。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信。
  6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

Webpack

什么是 Webpack?它的作用是什么?

Webpack是一个现代 JavaScript 应用程序的静态模块打包工具。它将各种资源,如 JavaScript、样式表、图片等,视作模块,并生成对应的静态资源。

Webpack 的核心概念有哪些?

Webpack的核心概念包括入口(Entry)、输出(Output)、加载器(Loader)、插件(Plugin)、模式(Mode)、代码拆分(Code Splitting)等。

什么是 Loader 和 Plugin?它们有什么作用?

Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

Loader 用于对模块的源代码进行转换,而 Plugin 用于扩展 Webpack 的功能,比如打包优化、资源管理、注入环境变量等。

Webpack的构建流程是什么?

  • 初始化参数:解析webpack配置参数,合并shell传入和webpack.config.js文件配置的参数,形成最后的配置结果;
  • 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件 监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的run方法开始执行编译;
  • 确定入口:从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去;
  • 编译模块:递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译并输出:递归完事后,得到每个文件结果,包含每个模块以及他们之间的依赖关系,根据entry或分包配置生成代码块chunk;
  • 输出完成:输出所有的chunk到文件系统;

如何配置 Webpack?常见的配置文件是什么?

Webpack 的配置通常使用一个名为 webpack.config.js 的文件,通过配置 entry、output、module、plugins 等选项来定义打包的行为。

什么是 Webpack 的模式(Mode)?有哪些模式可用?

Webpack 的模式包括开发模式(development)、生产模式(production)和其他自定义模式,用于控制打包过程中的优化行为。

如何处理 Webpack 中的图片和样式表?

Webpack 使用不同类型的 Loader 来处理各种资源,比如 Babel Loader 处理 JavaScript,style-loader 和 css-loader 处理样式表,file-loader 和 url-loader 处理图片等。

什么是 Code Splitting?如何在 Webpack 中实现它?

Code Splitting 是一种优化技术,用于将代码拆分成多个小块,实现按需加载,从而提高应用程序的加载速度和性能。

如何优化 Webpack 的构建性能?

优化 Webpack 的构建性能可以通过配置合适的 Loader 和 Plugin、使用缓存、优化打包输出、减少不必要的计算等方式实现。

什么是 Webpack 的热模块替换(Hot Module Replacement)?如何实现它?

  • 通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
  • socket server 是一个 websocket 的长连接,双方可以通信
  • 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
  • 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
  • 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新

什么是 Tree Shaking?它如何在 Webpack 中实现的?

Tree Shaking 是一种用于删除未使用代码的技术,通过静态分析来确定哪些代码可以安全地删除,从而减少打包文件的体积。

什么是 Webpack 的代码分析工具?你使用过哪些?

Webpack 的代码分析工具包括 webpack-bundle-analyzer、source-map-explorer 等,用于帮助开发者分析打包后的代码结构和体积。

Webpack 和 Gulp/Grunt 相比有什么优势?

Webpack 相比 Gulp/Grunt 具有更强大的模块打包能力和更丰富的插件生态,可以更灵活地处理复杂的构建任务。

什么是 Webpack 的 DevServer?它是如何工作的?

Webpack 的 DevServer 是一个内置的开发服务器,它提供了诸如热模块替换、实时重新加载等功能,用于提高开发环境的效率和体验。

如何在 Webpack 中处理 ES6+ 代码?

Webpack 可以通过 Babel Loader 来处理 ES6+ 代码,将其转换为浏览器可识别的 ES5 代码。

什么是 Webpack 的 Bundle?如何优化 Bundle 的大小和性能?

优化 Bundle 的大小和性能可以通过代码拆分、压缩代码、提取公共模块、使用动态导入等方式来实现。