前端
Web前端开发是从网页制作演变而来的,名称上有很明显的时代特征。在互联网的演化进程中,网页制作是Web 1.0时代的产物,那时网站的主要内容都是静态的,用户使用网站的行为也以浏览为主。2005年以后,互联网进入Web 2.0时代,各种类似桌面软件的Web应用大量涌现,网站的前端由此发生了翻天覆地的变化。网页不再只是承载单一的文字和图片,各种富媒体让网页的内容更加生动,网页上软件化的交互形式为用户提供了更好的使用体验,这些都是基于前端技术实现的。

查看更多相关内容
说一下 splice 和 slice 的功能用法`splice()` 和 `slice()` 都是 JavaScript 中用来处理数组的方法,但它们的功能和用法有所不同。
### splice()
`splice()` 方法通过删除或替换现有元素或在数组中添加新元素来改变数组的内容。其基本语法如下:
```javascript
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
```
- **start**: 指定修改的开始位置(数组索引)。
- **deleteCount**: (可选)整数,表示要从数组中删除的元素数量。
- **item1, item2, ...**: (可选)要添加进数组的新元素。
**示例**:
```javascript
let myArray = ['a', 'b', 'c', 'd'];
myArray.splice(1, 2, 'x', 'y'); // 从索引1开始删除2个元素,并添加'x'和'y'
console.log(myArray); // 输出: ['a', 'x', 'y', 'd']
```
### slice()
`slice()` 方法则返回一个新的数组,包含从开始到结束(不包括结束)选择的数组的一部分。原始数组不会被修改。其基本语法如下:
```javascript
array.slice(begin[, end])
```
- **begin**: 提取起始处的索引(从该索引开始提取元素)。
- **end**: (可选)提取结束处的索引(到该索引之前的元素会被提取)。
**示例**:
```javascript
let myArray = ['a', 'b', 'c', 'd'];
let newArray = myArray.slice(1, 3); // 提取从索引1到索引2的元素
console.log(newArray); // 输出: ['b', 'c']
console.log(myArray); // 原数组不变,输出: ['a', 'b', 'c', 'd']
```
总结来说,`splice()` 是一个可以在任何位置添加或删除元素的方法,这会改变原数组,而 `slice()` 用于创建一个新的数组,包含原数组的一部分,原数组不会改变。
前端 · 2月7日 16:44
介绍 AST(Abstract Syntax Tree)抽象语法树AST(抽象语法树)是源代码的抽象符号和语法结构的树状表示。它是编译器设计中的一个重要概念,用于表示编程语言中的程序结构,而不包括其实际的语法细节(如括号和语法糖)。
在解析阶段,编译器会读取源代码,进行词法分析生成令牌(Token),然后这些令牌会被进一步分析并构造成AST。每个节点代表程序中的一个构造,如表达式、声明或控制流语句。
AST使得编译器能够执行更多的分析和优化任务,例如类型检查、作用域解析、内存分配以及代码生成等。此外,AST也被用于静态代码分析工具中,以帮助开发者找到代码中的错误或进行代码复杂度分析。
例如,对于简单的数学表达式 `3 + 4 * 5` 的AST,根节点可能是一个加法表达式,它有两个子节点:左子节点是数字 `3`,右子节点是乘法表达式,乘法表达式又有两个子节点,分别是数字 `4` 和 `5`。
前端 · 2月7日 16:36
详细介绍 babel 的工作流程Babel 的工作流程主要包括以下几个步骤:
1. **解析(Parsing)**:
- Babel 首先将输入的 JavaScript 代码转换成一个抽象语法树(AST)。这一过程分为两个主要阶段:词法分析(将代码字符串拆解成有意义的代码块,称为 tokens)和语法分析(将 tokens 转换成表示程序结构的 AST)。
2. **转换(Transforming)**:
- 转换阶段是 Babel 工作流程的核心。在这个阶段,Babel 使用各种插件来处理 AST。插件可以访问、分析、替换、添加或删除 AST 的节点。常见的转换包括语法扩展(如 JSX、TypeScript 转换为 JavaScript)、ES6+ 代码转换为向后兼容的 ES5 代码等。
3. **生成(Code Generation)**:
- 经过转换的 AST 然后被转换回 JavaScript 代码。此过程包括根据 AST 的结构重新构造代码,同时还可能包括源代码映射(source maps)的生成,用于调试目的。
4. **输出(Output)**:
- 最终生成的 JavaScript 代码作为 Babel 的输出。这段代码已经被转换,可以在旧版浏览器和环境中运行,而无需担心兼容性问题。
通过这些步骤,Babel 允许开发者使用最新的 JavaScript 语言特性,而不必担心目标环境是否支持这些新特性。
前端 · 2月7日 16:36
JS 数组有哪些方法? 讲讲它们的区别跟使用场景JavaScript数组作为核心数据结构,掌握其方法能显著提升代码效率和可维护性。本文将系统分析常用数组方法,深入探讨它们的区别、适用场景及最佳实践,帮助开发者写出更简洁、高性能的代码。所有方法基于ECMAScript标准,重点聚焦于函数式方法,避免常见陷阱。对于更详细的信息,可参考[MDN文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array)。
## 常见数组方法分类
数组方法可大致分为以下几类,每类服务于特定需求:
* **迭代方法**:用于遍历和转换数组,如 `map`、`filter`、`reduce`、`forEach`,适合声明式编程。
* **变更方法**:直接修改原数组,如 `push`、`pop`、`shift`、`unshift`、`splice`、`sort`,适用于栈操作或原地修改。
* **生成方法**:创建新数组或字符串,如 `slice`、`concat`、`join`,常用于数据处理。
* **其他方法**:如 `fill`、`from`、`includes`、`indexOf`,提供额外功能。
> **关键提示**:函数式方法(如 `map`、`filter`)返回新数组,**不修改原数组**,而变更方法(如 `push`)直接操作原数组。选择时需权衡性能和可读性。
### 迭代方法详解
迭代方法是数组处理的核心,强调**纯函数**特性,避免副作用。
#### map
**作用**:创建新数组,其中每个元素是调用回调函数的结果。不修改原数组。
**参数**:回调函数(`item`, `index`, `array`)
**使用场景**:数据转换,如数字列表转字符串、计算倍数。**避免**在回调中修改原数组,保持函数式纯度。
**代码示例**:
```javascript
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
// 原数组未被修改
console.log(numbers); // [1, 2, 3]
```
#### filter
**作用**:创建新数组,包含通过测试的元素。不修改原数组。
**参数**:回调函数(`item`, `index`, `array`)
**使用场景**:数据过滤,如筛选偶数、有效对象。**与map对比**:`map`转换所有元素,`filter`仅保留满足条件的元素。
**代码示例**:
```javascript
const numbers = [1, 2, 3, 4];
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]
// 原数组未被修改
console.log(numbers); // [1, 2, 3, 4]
```
#### reduce
**作用**:将数组元素归约成单个值(如总和、最大值)。不修改原数组。
**参数**:回调函数(`accumulator`, `currentValue`, `index`, `array`),**初始值**可指定(如 `0`)。
**使用场景**:聚合计算、链式操作。**性能提示**:对大型数组,避免嵌套循环,`reduce` 更高效。
**代码示例**:
```javascript
const numbers = [1, 2, 3, 4];
const sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 10
const max = numbers.reduce((acc, num) => Math.max(acc, num), -Infinity);
console.log(max); // 4
```
#### forEach
**作用**:对数组每个元素执行回调,但**不返回新数组**。不修改原数组。
**参数**:回调函数(`item`, `index`, `array`)
**使用场景**:遍历操作,如DOM修改。**避免使用**:因无返回值,**不适合链式操作**,仅用于副作用。
**代码示例**:
```javascript
const items = ['a', 'b', 'c'];
items.forEach(item => {
console.log(`Item: ${item}`);
});
// 输出: Item: a
// Item: b
// Item: c
// 原数组未被修改
console.log(items); // ['a', 'b', 'c']
```
### 变更方法详解
变更方法**直接修改原数组**,适用于原地操作,但可能破坏函数式纯度。
#### push/pop
**作用**:`push`添加元素到末尾,`pop`移除末尾元素(栈操作)。
**参数**:`push`接收多个值;`pop`无参数。
**使用场景**:栈实现、队列操作。**性能提示**:对于频繁操作,避免在循环中使用,考虑`slice`等替代方案。
**代码示例**:
```javascript
const stack = [];
stack.push('item1', 'item2');
console.log(stack); // ['item1', 'item2']
const last = stack.pop();
console.log(last); // 'item2'
console.log(stack); // ['item1']
```
#### splice
**作用**:插入、删除或替换数组元素,返回被移除的元素。
**参数**:`start`索引,`deleteCount`,`items`(可选)。
**使用场景**:动态数组修改。**注意事项**:修改原数组,可能导致意外副作用。
**代码示例**:
```javascript
const arr = [1, 2, 3, 4];
const removed = arr.splice(1, 2, 'a', 'b');
console.log(removed); // [2, 3]
console.log(arr); // [1, 'a', 'b', 4]
```
#### sort
**作用**:对数组元素排序,**默认按字符串规则**(需显式指定比较函数)。
**参数**:可选比较函数(`a, b`)。
**使用场景**:数据排序。**性能提示**:对大型数组,使用`Array.prototype.sort`可能慢,优先使用`Array.from`和稳定排序。
**代码示例**:
```javascript
const nums = [3, 1, 4, 2];
nums.sort((a, b) => a - b);
console.log(nums); // [1, 2, 3, 4]
// 对字符串排序
const names = ['Alice', 'Bob', 'Charlie'];
console.log(names.sort()); // ['Alice', 'Bob', 'Charlie']
```
### 生成方法详解
生成方法**返回新数组或字符串**,不修改原数组,适合数据处理。
#### slice
**作用**:返回新数组,包含从`start`到`end`(不含)的元素。
**参数**:`start`(索引,负值表示倒数),`end`(可选,索引)。
**使用场景**:复制数组片段、避免原地修改。**关键区别**:`slice` vs `splice`——`slice`不修改原数组,`splice`会修改。
**代码示例**:
```javascript
const arr = [1, 2, 3, 4];
const sub = arr.slice(1, 3);
console.log(sub); // [2, 3]
console.log(arr); // [1, 2, 3, 4] // 原数组未被修改
```
#### concat
**作用**:连接多个数组或值,返回新数组。
**参数**:一个或多个数组/值。
**使用场景**:合并数组、拼接数据。**性能提示**:对大型数组,避免嵌套`concat`,使用`[...arr1, ...arr2]`更高效。
**代码示例**:
```javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = arr1.concat(arr2);
console.log(merged); // [1, 2, 3, 4]
```
#### join
**作用**:将数组元素连接成字符串,用指定分隔符。
**参数**:分隔符(默认`','`)。
**使用场景**:生成字符串、日志输出。**注意事项**:对大型数组,可能产生内存问题,避免过度使用。
**代码示例**:
```javascript
const fruits = ['apple', 'banana', 'cherry'];
const str = fruits.join(', ');
console.log(str); // 'apple, banana, cherry'
```
## 方法选择指南
掌握方法区别后,需根据场景选择最优方案:
* **map vs filter**:`map`用于转换所有元素(如`[1,2,3]` → `[2,4,6]`),`filter`用于过滤(如`[1,2,3,4]` → `[2,4]`)。**选择建议**:数据转换用`map`,数据筛选用`filter`。
* **避免副作用**:`forEach`适合遍历副作用(如DOM操作),但不适合链式操作;`map`和`filter`返回新数组,适合纯函数式代码。
* **性能优化**:
* 对大型数组,优先使用`slice`(不修改原数组)而非`splice`(修改原数组)。
* 计算聚合时,`reduce` 比 `for` 循环更高效且可读。
* 避免在循环中使用`push`,改用`array.map().push()` 或 `array.concat()`。
* **安全实践**:
* 始终优先使用函数式方法(`map`、`filter`),避免`for`循环,提升代码可测试性。
* 对原地操作(如`splice`),确保数据副本,防止意外副作用。
> **实践建议**:在开发中,使用`console.log`验证数组行为,例如:
>
>
## 结论
JavaScript数组方法是前端开发的核心工具。本文系统分析了关键方法的区别与使用场景,强调函数式方法的优势(如`map`、`filter`)和变更方法的适用性。**最佳实践**:优先使用声明式方法,避免副作用;性能敏感场景,选择高效操作;持续学习新特性(如`Array.from`和`Array.of`)。掌握这些方法,能显著提升代码质量,使开发更高效、可维护。记住:**数组方法的正确选择是性能优化和代码健壮性的关键**。

> **延伸阅读**:在现代JavaScript中,数组方法与迭代器结合,可实现更高级的流式处理。例如,使用`Array.from`转换可迭代对象:
>
>
前端 · 2月7日 13:47
JavaScript 原型中 prototype 和proto区别是什么?在JavaScript中,`prototype`属性和`__proto__`属性(通常读作"proto")是有关于对象原型链的概念,但它们在使用和目的上有所不同。
### `prototype`属性
`prototype`是函数对象(Function objects)的一个属性。当你使用构造函数创建一个新对象时,这个新对象的内部`[[Prototype]]`(也就是它的`__proto__`属性)会被赋值为构造函数的`prototype`属性。这意味着,使用同一个构造函数创建的所有对象都会共享同一个`prototype`对象。
举个例子,如果我们有一个构造函数:
```javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
```
当我们创建一个`Person`实例时:
```javascript
var person1 = new Person("Alice");
```
`person1`对象的`[[Prototype]]`(即`__proto__`)会指向`Person.prototype`,这使得`person1`能够访问到`sayHello`方法。
### `__proto__`属性
`__proto__`是每个JavaScript对象都拥有的一个内部属性,它指向该对象的原型。这是一个从对象指向其构造函数的`prototype`属性的链接。根据ECMAScript标准,`__proto__`是`[[Prototype]]`的实现,而`[[Prototype]]`是对象的内部属性。在现代JavaScript开发中,通常推荐使用`Object.getPrototypeOf(obj)`来获取对象的原型,而不是直接使用`__proto__`,因为`__proto__`并不是所有JavaScript环境中都得到支持。
再次拿刚才的例子,`person1.__proto__`会指向`Person.prototype`,因为`person1`是由`Person`构造函数创建的。
### 小结
- `prototype`是函数特有的属性,用于当作构造函数时为实例对象指定原型。
- `__proto__`是每个对象都有的属性,指向该对象的原型。
在实践中,`prototype`用来实现基于原型的继承和共享属性/方法,而`__proto__`提供了一种访问和操作对象原型链的方式。然而,直接操作`__proto__`被视为不太安全的做法,尤其是在现代JavaScript编程中,应该利用`Object.getPrototypeOf()`和`Object.setPrototypeOf()`等方法来替代`__proto__`的直接使用。
前端 · 2024年8月9日 17:42
script 标签的 defer 和 async 有什么区别?当您在 HTML 文档中使用 `<script>` 标签引入 JavaScript 时,`defer` 和 `async` 属性可以控制脚本的加载和执行方式,它们之间的区别主要在于脚本加载的时间以及执行的时机。
### defer 属性
使用 `defer` 属性的 `<script>` 标签会让脚本在文档解析期间异步下载,但是会延迟到整个文档解析完毕之后、DOMContentLoaded 事件触发之前执行。这意味着带有 `defer` 的脚本总是在文档解析完成之后执行,保证了执行时 DOM 已经完全构建好。
#### 例子:
```html
<script src="example.js" defer></script>
```
如果您有多个带有 `defer` 属性的脚本,它们将按照在文档中出现的顺序执行,即便有些脚本可能会比其他脚本更早下载完成。
### async 属性
而 `async` 属性也允许脚本在文档解析时异步下载,但是它一旦下载完成就会立即执行,这可能会在文档的其余部分尚未解析完毕时发生。因此,使用 `async` 的脚本不能保证按照在页面中出现的顺序执行,也无法保证 DOM 完全构建完成。
#### 例子:
```html
<script src="example.js" async></script>
```
`async` 适用于那些不依赖于其他脚本且不依赖于 DOM 的脚本,例如,广告加载或者埋点脚本。
### 总结
- `defer` 确保脚本在文档完全解析和 DOM 构建完成后,但在 DOMContentLoaded 事件之前执行。
- `async` 确保脚本在下载完成后尽快执行,但可能会打断文档的解析过程。
- 没有这两个属性的 `<script>` 标签会立即下载并阻塞文档解析直到脚本执行完成。
在实际应用中,选择 `defer` 或 `async` 取决于脚本对文档解析的依赖性,以及脚本之间的依赖关系。如果您需要确保脚本按照顺序执行,并且在 DOM 完全构建后执行,那么 `defer` 是更好的选择。如果脚本的执行顺序不重要,并且想尽快获取并执行脚本,可以使用 `async`。
前端 · 2024年8月5日 12:52
javascript 中垃圾回收的方法有哪些?JavaScript中的垃圾回收(garbage collection)是一种自动内存管理机制,它帮助开发者不需要手动释放分配的内存。在JavaScript中,垃圾回收主要采用了以下几种方法:
### 1. 标记清除(Mark and Sweep)
这是最常见的垃圾回收算法。当变量进入环境时,就“标记”这个变量为“进入环境”。当变量离开环境时,则“标记”这个变量为“离开环境”。垃圾收集器会定期运行,它会检查所有的变量,以及它们引用的其他变量是否还在环境中。如果一个变量已经不再环境中,且没有任何其他变量引用它,那么这个变量占用的内存就会被回收。
#### 例子:
```javascript
function processData() {
var data = { /* 大量数据 */ };
// 使用data进行处理
}
processData();
// processData执行完毕后,data变量离开环境,变成无法访问的状态,会被标记为可回收。
```
### 2. 引用计数(Reference Counting)
引用计数是另一种垃圾回收机制。在这个系统中,每一个值都有一个“引用数”,表示有多少变量或资源引用这个值。如果引用数变为0,则表示该值不再需要,其占用的内存可以被回收。这种方法的一个问题是循环引用:如果两个对象互相引用,即便它们已经不再需要,它们的引用数也不会降到0,导致内存无法被回收。
#### 例子:
```javascript
function referenceCycle() {
var objectA = {};
var objectB = {};
objectA.other = objectB;
objectB.other = objectA;
}
referenceCycle();
// 即使referenceCycle函数执行结束,objectA和objectB因为相互引用,它们的引用数都不为0,造成内存泄漏。
```
### 3. 分代收集(Generational Collection)
分代收集是基于对象存活时间的假设,将对象分为两组:“新生代”和“老生代”。新创建的对象属于新生代,对象如果存活足够长的时间,就会被移动到老生代。通常新生代使用标记-复制(mark-copy)算法,老生代使用标记-清除(mark-sweep)或标记-整理(mark-compact)算法。
### 4. 标记-整理(Mark-Compact)
这种方法是对标记-清除的改进。在标记阶段,标记所有活动对象,然后在整理阶段,将所有活动的对象移动到内存的一端,然后清理掉边界之外的内存。
### 5. 增量收集(Incremental Collection)
增量收集是将垃圾回收分成小片段执行,每次只处理一部分对象,然后暂停,让程序执行。这种方式可以减少垃圾收集过程中的停顿时间。
### 6. 闲时收集(Idle-time Collection)
某些JavaScript引擎会利用CPU空闲时间来执行垃圾回收的工作,以避免影响到程序的执行效率。
前端 · 2024年8月5日 12:52
React 如何做性能优化?有哪些常见手段?React 在性能优化方面提供了多种策略和工具,以确保用户界面高效、平滑且响应迅速。以下是一些常用的性能优化手段:
### 1. 使用 `shouldComponentUpdate` 和 `React.PureComponent`
在类组件中,通过实现 `shouldComponentUpdate` 方法可以控制组件是否需要更新。当组件的状态或属性改变时,此方法会被调用,并根据返回的布尔值决定是否进行渲染。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">shouldComponentUpdate</span><span class="token">(</span><span class="token parameter">nextProps</span><span class="token parameter">,</span><span class="token parameter"> nextState</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">// 只有当特定的属性或状态改变时才更新组件</span><span>
</span><span> </span><span class="token control-flow">return</span><span> nextProps</span><span class="token">.</span><span class="token property-access">id</span><span> </span><span class="token">!==</span><span> </span><span class="token">this</span><span class="token">.</span><span class="token property-access">props</span><span class="token">.</span><span class="token property-access">id</span><span> </span><span class="token">||</span><span> nextState</span><span class="token">.</span><span class="token property-access">count</span><span> </span><span class="token">!==</span><span> </span><span class="token">this</span><span class="token">.</span><span class="token property-access">state</span><span class="token">.</span><span class="token property-access">count</span><span class="token">;</span><span>
</span><span></span><span class="token">}</span></code></div></section></pre>
对于那些拥有不可变的属性和状态的组件,可以使用 `React.PureComponent`,它通过浅比较 props 和 state 来减少不必要的渲染。
### 2. 使用 Hooks(如 `React.memo` 和 `useMemo`)
对于函数组件,`React.memo` 是一个高阶组件,它仅在组件的 props 发生变化时才会重新渲染组件。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> </span><span class="token maybe-class-name">MyComponent</span><span> </span><span class="token">=</span><span> </span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token method property-access">memo</span><span class="token">(</span><span class="token">function</span><span> </span><span class="token maybe-class-name">MyComponent</span><span class="token">(</span><span class="token parameter">props</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">/* 只有props改变时,组件才会重新渲染 */</span><span>
</span><span></span><span class="token">}</span><span class="token">)</span><span class="token">;</span></code></div></section></pre>
`useMemo` 和 `useCallback` 钩子可以用来缓存复杂计算的结果和回调函数,避免在每次渲染时都重新计算和创建新的函数实例。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> memoizedValue </span><span class="token">=</span><span> </span><span class="token">useMemo</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token">computeExpensiveValue</span><span class="token">(</span><span>a</span><span class="token">,</span><span> b</span><span class="token">)</span><span class="token">,</span><span> </span><span class="token">[</span><span>a</span><span class="token">,</span><span> b</span><span class="token">]</span><span class="token">)</span><span class="token">;</span><span>
</span><span></span><span class="token">const</span><span> memoizedCallback </span><span class="token">=</span><span> </span><span class="token">useCallback</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token">// 一个依赖特定props的回调函数</span><span>
</span><span></span><span class="token">}</span><span class="token">,</span><span> </span><span class="token">[</span><span>props</span><span class="token">]</span><span class="token">)</span><span class="token">;</span></code></div></section></pre>
### 3. 避免不必要的 DOM 更新
当操作 DOM 时,应尽量减少更新次数和范围。可以使用虚拟列表(比如 `react-window` 或 `react-virtualized`)来仅渲染可视区域的元素,从而提高长列表的性能。
### 4. 懒加载组件和路由
使用 `React.lazy` 可以实现组件级别的代码拆分,这样可以将不同的组件打包成单独的代码块,并在需要时才加载它们。
同时,结合 `React Router` 的 `Suspense` 组件,可以实现路由级别的懒加载,仅当路由被访问时才加载对应的组件。
<pre><section class="markdown-code"><div class="markdown-code__head"><div class="markdown-code__head-language"><span>javascript</span><span class="i-icon i-icon-copy"><svg width="16" height="16" viewBox="0 0 48 48" fill="none"><path d="M13 12.4316V7.8125C13 6.2592 14.2592 5 15.8125 5H40.1875C41.7408 5 43 6.2592 43 7.8125V32.1875C43 33.7408 41.7408 35 40.1875 35H35.5163" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path><path d="M32.1875 13H7.8125C6.2592 13 5 14.2592 5 15.8125V40.1875C5 41.7408 6.2592 43 7.8125 43H32.1875C33.7408 43 35 41.7408 35 40.1875V15.8125C35 14.2592 33.7408 13 32.1875 13Z" fill="none" stroke="#333" stroke-width="4" stroke-linejoin="round"></path></svg></span></div></div><div><code class="language-javascript"><span class="token">const</span><span> </span><span class="token maybe-class-name">OtherComponent</span><span> </span><span class="token">=</span><span> </span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token method property-access">lazy</span><span class="token">(</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token arrow">=></span><span> </span><span class="token module">import</span><span class="token">(</span><span class="token">'./OtherComponent'</span><span class="token">)</span><span class="token">)</span><span class="token">;</span><span>
</span>
<span></span><span class="token">function</span><span> </span><span class="token maybe-class-name">MyComponent</span><span class="token">(</span><span class="token">)</span><span> </span><span class="token">{</span><span>
</span><span> </span><span class="token control-flow">return</span><span> </span><span class="token">(</span><span>
</span><span> </span><span class="token"><</span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token property-access maybe-class-name">Suspense</span><span> fallback</span><span class="token">=</span><span class="token">{</span><span class="token"><</span><span>div</span><span class="token">></span><span class="token maybe-class-name">Loading</span><span class="token spread">...</span><span class="token"><</span><span class="token">/</span><span>div</span><span class="token">></span><span class="token">}</span><span class="token">></span><span>
</span><span> </span><span class="token"><</span><span class="token maybe-class-name">OtherComponent</span><span> </span><span class="token">/</span><span class="token">></span><span>
</span><span> </span><span class="token"><</span><span class="token">/</span><span class="token maybe-class-name">React</span><span class="token">.</span><span class="token property-access maybe-class-name">Suspense</span><span class="token">></span><span>
</span><span> </span><span class="token">)</span><span class="token">;</span><span>
</span><span></span><span class="token">}</span></code></div></section></pre>
### 5. 使用 Web Workers
对于复杂或计算密集型任务,可以使用 Web Workers 在后台线程中执行,避免阻塞主线程导致用户界面卡顿。
### 6. 优化条件渲染
避免不必要的渲染,例如,可以将条件渲染逻辑移到可能更改状态的事件处理函数中,而不是在渲染方法中进行。
### 7. 状态升级
将子组件的本地状态提升到父组件中,这样可以减少不必要的子组件渲染,因为状态的变化会集中处理。
### 8. 使用不可变数据结构
使用不可变数据可以更容易地检测到状态和属性的变化,这使得组件的更新检查更高效。库如 `Immutable.js` 可以用来帮助创建不可变数据。
### 9. 使用生产版本的 React
开发中通常使用的是开发版本的 React,它包含了许多有用的警告和错误信息。但在生产中,应该使用经过压
缩和优化的生产版本,它删除了这些额外的警告和检查,以减少库的大小并提升性能。
### 10. 分析和监控
使用性能分析工具,如 React DevTools 中的 Profiler,可以帮助识别渲染性能瓶颈。它可以记录组件的渲染时间,并帮助你找到可以优化的部分。
### 11. 避免内联对象和数组的传递
对于那些接收对象或数组作为 props 的组件,应避免在渲染方法中直接创建新的内联对象或数组,因为这会导致 props 始终不相等,从而触发不必要的渲染。
```javascript
<MyComponent items={[1, 2, 3]} /> // 每次渲染都会创建一个新的数组,不推荐这样做
// 更好的做法是在组件外部定义这个数组
const items = [1, 2, 3];
<MyComponent items={items} />
```
### 12. 使用键(keys)来优化列表渲染
当渲染列表时,应该为每个列表项指定一个唯一的 key。这有助于 React 确定哪些项已更改、添加或删除,从而提高列表渲染的效率。
```javascript
data.map((item) => <ListItem key={item.id} {...item} />)
```
### 13. 使用 Context 时的优化
当使用 React Context API 时,应该注意其可能对性能的影响。Context 的变动会导致所有消费该 Context 的组件重新渲染。为了避免不必要的渲染,可以分割 Context 或是使用 `useMemo` 跟 `useCallback` 来传递稳定的上下文值。
### 14. 避免过度渲染和过度传递 props
审视组件间的 props 传递,确保不会传递额外的 props。如果一个组件不需要某个 prop,那么就不应该传递它,因为这可能会导致不必要的组件渲染。
### 15. 服务器端渲染 (SSR)
服务器端渲染可以加快首次页面加载时间,并提升搜索引擎优化(SEO)。通过在服务器上生成 HTML,可以减少客户端的工作量,从而提升性能。
### 实施示例
假设我们有一个用户列表组件,其中包含大量用户数据。我们可以应用以下优化:
- 使用 `React.memo` 封装用户列表项组件,仅在 props 变化时重新渲染。
- 通过 `useMemo` 缓存用户列表计算,避免在每次渲染时重新计算。
- 使用虚拟列表库,如 `react-window`,仅渲染可视区域内的用户,以优化长列表性能。
- 如果用户列表是通过路由导航到达的,可以使用 `React.lazy` 和 `Suspense` 实现路由懒加载。
- 通过 React DevTools 的 Profiler 分析用户列表的渲染性能,找出任何潜在的性能瓶颈进行优化。
通过应用这些优化技巧,我们可以显著提高大型 React 应用的性能和用户体验。
前端 · 2024年8月5日 12:52
什么是闭包?什么场景需要使用闭包?### 什么是闭包?
在计算机科学中,闭包(Closure)是指一个函数绑定了其外部作用域的变量,因此这个函数可以在其定义环境之外被调用时仍能访问到那些绑定的变量。简单来说,闭包让你可以从一个函数内部访问到其外部函数作用域的变量。
闭包的特点是:
1. **函数嵌套**:通常闭包包含一个函数内定义的另一个函数。
2. **环境捕获**:内部函数会捕获定义它的外部函数的作用域中的变量。
3. **作用域链**:内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
在 JavaScript 中,闭包是一种非常常见和强大的特性,因为 JavaScript 是词法作用域的语言,函数的作用域在函数定义时就已经确定了。
### 什么场景需要使用闭包?
闭包通常用于以下几种场景:
1. **数据封装和私有化**:
使用闭包可以创建私有变量,这些变量只能被特定的函数访问和修改,从而模拟出类似私有属性的效果。这在模块模式中尤其常见。
**例子**:一个简单的计数器函数,利用闭包可以隐藏计数器的值,只能通过特定的函数来操作。
```javascript
function createCounter() {
let count = 0;
return {
increment: function() { count += 1; },
decrement: function() { count -= 1; },
getCount: function() { return count; }
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出 1
```
2. **回调函数**:
在异步编程中,闭包常用于回调函数中,以确保异步操作完成时能够访问到定义回调时的环境状态。
**例子**:在一个异步请求中使用闭包记住请求开始时的状态。
```javascript
function fetchData(url, callback) {
// 假设这里发起了一个异步请求
setTimeout(() => { // 模拟异步操作
const data = 'fetched data'; // 假设这是响应数据
callback(data);
}, 1000);
}
function requestData() {
const requestTimestamp = Date.now();
fetchData('https://example.com/data', function(data) {
console.log(`Request took ${Date.now() - requestTimestamp} ms`);
console.log(`Data received: ${data}`);
});
}
requestData();
```
3. **函数工厂**:
闭包可以用来创建可以记住和操作环境状态的函数,这些函数根据不同的参数创建出来,具有不同的行为。
**例子**:根据不同的倍数创建乘法函数。
```javascript
function createMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 输出 10
console.log(triple(5)); // 输出 15
```
4. **节流和防抖**:
在 JavaScript 的 DOM 事件处理中,为了优化性能,防止过多的事件处理函数被频繁触发,会使用闭包来实现函数节流(throttle)和防抖(debounce)。
**例子**:使用防抖确保事件处理函数在特定时间内只执行一次。
前端 · 2024年8月5日 12:52
HTTP 协议 1.0 和 1.1 和 2.0 有什么区别?HTTP(超文本传输协议)是 Web 上交换数据的基础协议,随着 Web 技术的发展,HTTP 也经历了多个版本的迭代。下面我会详细介绍 HTTP 1.0、1.1 和 2.0 这三个版本的区别:
### HTTP 1.0
- **无状态连接**:HTTP 1.0 是无状态的,也就是说每次请求都需要建立一个新的TCP连接,完成数据传输后连接就会关闭。这种方式在每次请求都需要经历 TCP 连接的建立和断开过程,导致性能上的不足。
- **限制性能**:由于每次请求都要建立新的连接,所以并发多个请求会导致大量的延迟和性能问题。
- **无宿主名(Host)字段**:HTTP 1.0 不支持 Host 头部。这意味着同一个物理服务器上无法托管多个域名的网站。
### HTTP 1.1
- **持久连接**:HTTP 1.1 默认采用持久连接(也称为“keep-alive”),允许在一个TCP连接上发送和接收多个HTTP请求/响应,从而减少了TCP连接的开销。
- **管线化**:HTTP 1.1 引入了请求的管线化,理论上客户端可以在收到前一个响应之前发送下一个请求,减少了请求的延迟。但实际上,由于某些浏览器和服务器的实现问题,这个特性并未广泛使用。
- **新增头部字段**:例如 Host(它允许在同一物理服务器上虚拟托管多个域名)、Etag(实体标签,可以协助缓存验证)、Accept-Encoding(指定客户端可以接收的内容编码类型)等。
- **缓存控制**:更复杂和灵活的缓存控制机制,使得客户端和服务器可以更有效地协商数据的缓存,减少不必要的数据传输。
- **分块传输编码**:允许服务器开始发送响应而不需要先知道全部内容的总大小。
### HTTP 2.0
- **二进制协议**:HTTP/1.x 是文本协议,而 HTTP/2 是二进制协议,提供了更高效的解析和网络传输。
- **多路复用**:在同一个连接上并行交错地发送多个请求和响应,而不会互相影响,极大地提高了传输效率和减少了延迟。
- **流优先级**:可以为 HTTP/2 连接上的流设置优先级,允许更重要的资源先被发送。
- **服务器推送**:服务器可以对一个客户端请求发送多个响应,允许服务器主动推送资源给客户端,进一步提升页面加载效率。
- **头部压缩**:HTTP/2 引入了 HPACK 压缩格式,用于减小头部大小,以减少传输延迟。
举例来说,一个明显的性能改进是在使用HTTP/2时浏览一个网站:由于多路复用和头部压缩等特性,相比于 HTTP/1.1,网页的加载时间可以显著减少,尤其是在网络条件较差或加载资源较多的场景下。此外,HTTP/2 的服务器推送功能允许服务器预先推送静态资源,比如 CSS 或 JavaScript 文件,这可以进一步提高加载速度,因为浏览器不必等待解析 HTML 再去请求这些资源。
计算机基础 · 2024年8月5日 12:51