做网站的ui框架,做网站是,自助建设外贸网站,网站分为那几个模块第三部分#xff1a;高级主题
第十章 高级函数与范式
在现代 JavaScript 开发中#xff0c;高级函数与函数式编程范式正在逐渐成为开发者追求的目标。这种范式关注于函数的使用#xff0c;消除副作用#xff0c;提高代码的可读性和可维护性。
10.1. 高阶函数
高阶函数是…第三部分高级主题
第十章 高级函数与范式
在现代 JavaScript 开发中高级函数与函数式编程范式正在逐渐成为开发者追求的目标。这种范式关注于函数的使用消除副作用提高代码的可读性和可维护性。
10.1. 高阶函数
高阶函数是指那些可以接受其他函数作为参数或者返回一个函数作为结果的函数。高阶函数是函数式编程的基础其强大的灵活性使得实现许多编程模式成为可能。JavaScript 中大量使用高阶函数来处理异步操作、回调函数、数组操作等。
10.1.1 接收函数作为参数
高阶函数的一个常用情形是需要传递回调函数。例如数组的方法如 map、filter、reduce 等都接收一个函数作为参数对数组中的每个元素进行处理。以下是 map 方法的示例
const numbers [1, 2, 3];// map 接受一个函数作为参数用于对数组的每个元素进行处理
const doubled numbers.map((num) num * 2);console.log(doubled); // 输出: [2, 4, 6]知识点
回调函数传递给高阶函数的函数称为回调函数。无副作用理想情况下回调函数会遵循无副作用原则只改变返回值不改变外部状态。
10.1.2 返回函数
高阶函数可以返回另一个函数常用于创建工厂函数或实现柯里化Currying等设计模式。
function createMultiplier(multiplier) {// 返回一个新的函数这个新函数会捕捉 createMultiplier 的环境即 multiplier 的值return function (num) {return num * multiplier; // 使用闭包记住 multiplier 的值};
}const double createMultiplier(2); // double 是一个新函数
console.log(double(5)); // 输出: 10知识点
闭包Closure返回的函数内部引用了外部函数的变量如 multiplier因此形成闭包环境使得那些变量的值可以被记住。工厂函数Factory Function创建并返回特定功能的对象或函数例如 createMultiplier它返回一个特定乘法功能的函数。
10.1.3 高阶函数的实际应用
高阶函数通常用于以下场景
事件处理通过传递回调函数处理用户界面事件。数组操作使用 map、filter、reduce 以及其他数组操作这些方法接收将作用于每个元素的函数。函数组合通过返回函数来创建新的功能比如结合多个函数实现复杂数据转换。惰性求值通过返回函数延迟求值将计算推迟到需要时进行。
高阶函数在 JavaScript 中的重要性不仅在于其功能强大也在于其提升了代码的可读性和可维护性。通过这类抽象方式开发者可以更方便地管理代码逻辑和数据操作。
10.2. 函数式编程基础
函数式编程Functional Programming, FP是一种编程范式它强调函数的使用来进行计算。这种编程风格强调使用表达式来替代命令语句并通过函数组合和不可变性来提高代码的可靠性和简洁性。
在函数式编程中有几个核心概念如纯函数、不可变性、函数组合等这里将逐一进行讲解。
10.2.1. 纯函数Pure Functions
纯函数是函数式编程的基本单位。这类函数在给定相同输入时总是产生相同的输出不依赖任何外部可变状态。这种特性使得纯函数容易测试和推理。
function add(a, b) {return a b;
}
// add(2, 3) 不论在何时调用总是返回 5因为它不依赖外部环境或状态知识点
确定性纯函数会在相同的参数输入情况下始终产生相同的输出。无副作用纯函数不会改变外部状态或变量不会造成任何可观察到的副作用。
10.2.2. 不可变性Immutability
不可变性指的是数据一旦被创建就不能被修改在需要更新数据时函数式编程通常会返回新的数据结构而不是直接修改原有的数据。
const arr [1, 2, 3];
// 使用 concat 方法不会修改原 arr而是返回新的数组
const newArr arr.concat(4); // arr: [1, 2, 3], newArr: [1, 2, 3, 4]知识点
数据持久化使用不可变数据可以保证数据的历史是可以追溯的便于调试和恢复。避免共享状态问题通过确保数据的不可变性可以避免复杂的共享状态管理问题。
10.2.3. 函数组合Function Composition
函数组合是指将简单的函数按一定顺序结合在一起以完成更复杂的操作。通过这种方式可以提高代码的可复用性和可读性。
const add (x) x 1;
const multiply (x) x * 2;const addThenMultiply (x) multiply(add(x));
console.log(addThenMultiply(2)); // 结果为 6因为 (2 1) * 2 6知识点
高阶函数函数组合常依赖于高阶函数即以其他函数为参数或返回值的函数。管道和组合在复杂的结合中经常使用需要用管道pipeline或组合compose辅助函数来实现函数顺序的流水线式处理。
10.2.4. 惰性求值Lazy Evaluation
虽然未在上述代码示例中体现但惰性求值是函数式编程中的一个重要概念。惰性求值指的是在需要时才进行计算从而提高性能和内存使用效率。例如使用 ES6 的生成器函数可实现惰性求值。
function* lazySequence() {let i 0;while (true) {yield i;}
}
const numbers lazySequence();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1这一编程范式的目的在于通过使用纯函数、不可变性、和组合来提高程序的可靠性、可测试性和可维护性是一种强大的编程模式尤其适合集成和运行于并发、性能要求高的场景下。
10.3. 记忆化与柯里化
在现代 JavaScript 开发中记忆化和柯里化是提高函数效率和灵活性的重要技术。这些技术在处理复杂计算和提高代码可复用性方面非常有用。
10.3.1. 记忆化Memoization
记忆化是一种优化技术通过缓存函数调用的结果来避免不必要的重复计算从而提高性能。它尤其在需要多次计算相同输入的递归算法中非常有效。
function memoize(fn) {const cache {}; // 创建一个空的缓存对象return function (...args) {const key JSON.stringify(args); // 将参数序列化为字符串作为缓存的键if (cache[key]) { // 如果缓存中存在该键返回缓存的结果return cache[key];}const result fn(...args); // 计算结果cache[key] result; // 将结果存储在缓存中return result; // 返回结果};
}const factorial memoize((n) {if (n 1) return 1; // 基础情况阶乘的最小值为1return n * factorial(n - 1); // 递归调用并缓存结果
});// 使用memoized版本计算阶乘
console.log(factorial(5)); // 120
console.log(factorial(6)); // 利用缓存计算得出720知识点
缓存Cache使用对象根据输入存储结果避免重复计算。高阶函数memoize 是一个接收函数并返回新函数的高阶函数。JSON.stringify用于将参数转换为字符串构造缓存键适用于基本数据类型。
10.3.2. 柯里化Currying
柯里化是将接受多个参数的函数转换为嵌套的、每次接收一个参数的函数序列。它提高了函数的可重用性和灵活性使得部分应用变得简单。
function curry(fn) {return function curried(...args) {if (args.length fn.length) { // 如果参数数量足够调用原函数return fn.apply(this, args);} else {return function (...nextArgs) { // 返回一个新的函数等待更多参数return curried.apply(this, args.concat(nextArgs));};}};
}function add(a, b) {return a b; // 简单的加法函数
}const curriedAdd curry(add);
console.log(curriedAdd(1)(2)); // 3调用curriedAdd函数以部分应用知识点
函数柯里化转换多参数函数使其变为多个单参数函数嵌套。灵活性柯里化函数可以灵活地应用部分参数再应用其余参数。可配置性允许生成专用函数版本以应用常用配置。
通过以上技术JavaScript 开发者可以有效利用函数式编程范式使代码更模块化、可测试和可维护。记忆化优化了性能避免重复计算而柯里化提高了函数的灵活性和可复用能力。
第十一章 元编程
元编程是一种程序可以操控自身结构的编程技巧它允许开发者在运行时检查、扩展和修改代码。这一能力在 ECMAScript 中是通过 Symbol 类型、Proxy 和 Reflect以及迭代器和生成器提供的。
11.1. Symbol 类型
Symbol 是 ECMAScript 6 (ES6) 引入的一种新的原始数据类型。它的主要功能是创建一个唯一且不可变的数据类型这在需要独特标识符的场景下非常有用。Symbol 是 JavaScript 中第七种基础数据类型之前的六种分别是Undefined、Null、Boolean、Number、String 和 Object。
11.1.1. 创建 Symbol
Symbol 通过 Symbol() 函数创建每次调用 Symbol() 都会返回一个独一无二的 Symbol 值。尽管可以通过传递一个可选的字符串作为描述但这个字符串仅用于帮助调试而不影响不同 Symbol 之间的唯一性。
const symbol1 Symbol(description); // 带描述的 Symbol
const symbol2 Symbol(description); // 即使描述相同两个 Symbol 依然是唯一的
console.log(symbol1 symbol2); // falsesymbol1 和 symbol2 是唯一且不同的知识点
唯一性每个 Symbol 都是唯一的即使它们有相同的描述。可选描述传递给 Symbol() 的字符串仅用于调试和日志记录并不影响唯一性。
11.1.2. Symbol 作为对象属性
Symbol 非常适合作为对象属性的键确保属性不会在未经预期的情况下与其他属性发生命名冲突。
const mySymbol Symbol(); // 不带描述的 Symbol
const obj {[mySymbol]: value // 使用 Symbol 作为对象属性的键
};
console.log(obj[mySymbol]); // 输出: value知识点
计算属性名在对象字面量中使用方括号 [] 来定义 symbol 作为键。属性保护对应 Symbol 的属性不会出现在 for...in 循环之中也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。它们依然会被反映在 Object.getOwnPropertySymbols() 内。
11.1.3. 防止命名冲突与 Symbol 的应用
由于 Symbol 的唯一性它们能够有效地防止在对象属性上的命名冲突这对于例如创建大型库或者框架时尤其重要允许开发人员通过使用 Symbol 在不破坏现有代码的情况下添加新的属性或功能。
示例用 Symbol 定义方法对方法名进行保护以防被第三方覆盖。
const toStringSymbol Symbol(toString);class MyClass {// 定义一个 Symbol 属性的方法[toStringSymbol]() {return MyClass instance;}
}const instance new MyClass();
console.log(instance[toStringSymbol]()); // MyClass instance知识点
不可变性和不可猜测性相较于字符串使用 Symbol 作为方法名或属性键更加安全。使用场景适用于需要以唯一属性名或方法名扩展对象而不干扰其他代码的场景。
11.1.4. 内置 Symbol
ES6 还提供了内置的 Symbol 值用以暴露一些语言内部行为的钩子。例如
Symbol.iterator用于定义对象的默认迭代器。Symbol.asyncIterator用于定义对象的异步迭代器。Symbol.hasInstance用于定义 instanceof 操作符的行为。Symbol.toPrimitive用于对象转换为原始值的行为定义。
Symbol 是 JavaScript 中设计独特且不可变的标识符利用它的独特性和隔离能力开发者可以设计更安全、更灵活的代码。
11.2. Proxy 和 Reflect
Proxy 和 Reflect 是 ES6 引入的特性为 JavaScript 提供了拦截和定义对象基本操作的能力使得开发者能够在运行时修改一些核心语言行为。这些特性增加了动态操作对象的能力并且为插件式的扩展库提供了便利。
11.2.1. Proxy
Proxy 对象用于定义基本操作如属性查找、赋值、枚举、函数调用等的自定义行为。通过设置 traps操作捕获器可以拦截并重新定义这些操作。
基本用法
const target {}; // 要代理的目标对象
const handler {// get trap拦截获取属性值的操作get: function(obj, prop) {return prop in obj ? obj[prop] : default value; // 如果属性存在返回属性值否则返回 default value}
};const proxy new Proxy(target, handler);
console.log(proxy.someProperty); // 输出: default value知识点
target要拦截的目标对象。handler一个定义了捕获器对象可以包含各种拦截操作的方法。get trap当读取属性时被调用。用途可以用于监听对象的操作创建虚拟属性或实现更复杂的逻辑如访问控制或验证。
11.2.2. Reflect
Reflect 是一个内置对象提供了一些与 Object 操作相对应的静态方法。这些方法和 Proxy 中的 handler 有直接映射关系用于简化操作和避免重复实现。
基本用法
const obj { x: 10 };// Reflect.has检查对象中是否存在某个属性类似于 in 操作符
console.log(Reflect.has(obj, x)); // 输出: true// Reflect.set设置对象的属性值类似于赋值操作
Reflect.set(obj, y, 20);
console.log(obj.y); // 输出: 20知识点
方法Reflect 提供的方法与 Proxy 的捕获器方法一一对应例如 Reflect.get, Reflect.set, Reflect.has, Reflect.defineProperty 等。返回真假值通常 Reflect 的方法会返回布尔值表示操作是否成功这和许多旧有的对象操作方法返回 undefined 的方式不同。简化代码Reflect 可以避免直接操作对象时的重复代码增加程序的可读性。
11.2.3. 综合应用
通过 Proxy 和 Reflect我们可以更灵活地控制对象的行为实现例如验证、虚拟属性、隐藏原始数据等高级功能。以下是一个利用 Proxy 和 Reflect 的综合用例
const target {name: Alice
};const handler {get: function(obj, prop) {console.log(Accessing property ${prop});return Reflect.get(obj, prop); // 使用 Reflect.get 来获取属性值,确保原始行为的调用},set: function(obj, prop, value) {console.log(Setting property ${prop} to ${value});return Reflect.set(obj, prop, value); // 使用 Reflect.set 来设置属性值}
};const proxy new Proxy(target, handler);proxy.name; // 输出: Accessing property name 以及返回 Alice
proxy.age 25; // 输出: Setting property age to 25
console.log(target.age); // 输出: 25在这个示例中
日志记录每次访问或修改属性时都会输出日志这展示了如何利用 Proxy 捕获器来扩展对象的功能。保留原始行为通过 Reflect 保持了操作的原始行为保证了应用的预期结果。
11.3. 迭代器和生成器Iterator, Generator
在 JavaScript 中迭代器和生成器提供了一种强大而灵活的迭代机制能够逐步处理集合数据。它们让开发者能够创建自定义的迭代逻辑这在处理大型数据集或异步流时尤为有用。
11.3.1. 迭代器Iterators
定义与用法
迭代器是一种对象它实现了一个特定的接口即 Iterator 接口。这个接口规定了一个 next() 方法该方法返回一个包含两个属性的对象
value序列中的当前迭代值。done布尔值指示序列是否已经迭代完毕即没有更多的值可供迭代。
示例
const array [1, 2, 3];
// 使用 Symbol.iterator 得到数组的迭代器对象
const iterator array[Symbol.iterator]();// 每次调用 next() 返回序列的下一个对象
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }知识点
Symbol.iterator是一个内置的迭代器工厂方法。对于可迭代对象如数组、字符串、Set、Map它会返回默认的迭代器。for...of 循环使用迭代器的最常见方式它自动调用 next()并处理返回的值。
应用场景
序列化访问集合数组、字符串等自定义数据结构的遍历实现
11.3.2. 生成器Generators
定义与特性
生成器是实现迭代器接口的函数以 function* 语法声明。与普通函数相比生成器可以通过 yield 关键字在执行期间暂停和恢复。每次 yield 都会返回一个对象其中 value 是传递给 yield 的值done 指示生成器是否结束。
示例
function* generatorFunction() {yield 1; // 暂停并返回 { value: 1, done: false }yield 2; // 暂停并返回 { value: 2, done: false }yield 3; // 暂停并返回 { value: 3, done: false }
}const generator generatorFunction();console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }知识点
function* 声明用来定义一个生成器函数。yield 关键字生成器函数中的暂停点返回一个对象且维持其执行上下文状态以便后续恢复执行。next() 调用恢复生成器函数的执行直到遇到下一个 yield 或结束。
应用场景
惰性求值处理大规模数据时减少内存消耗。复杂迭代控制设计自定义迭代逻辑。异步编程与 async、await 等结合使用处理异步任务流。
通过学习迭代器和生成器特性你可以利用 ECMAScript 元编程特性动态操控 JavaScript 应用程序的行为提升代码品质和性能突破常规编码的限制。
第十二章 正则表达式
正则表达式Regular Expressions是一个强大的工具广泛用于字符串的搜索和替换。在 ECMAScript 中正则表达式对象由 RegExp 类表示能够通过字面量和构造函数创建。
12.1. 正则表达式语法
正则表达式Regular Expressions, RegExp是一个强大的工具用于匹配、查找和替换字符串中的模式。掌握正则表达式的基本语法可以帮助我们处理和分析文本数据。
12.1.1. 正则表达式定义方式
JavaScript 中定义正则表达式有两种方式
字面量语法: 直接在一对斜杠之间定义模式其中可以包含可选的修饰符。例如 /abc/i 是一个匹配字符串中 abc 不区分大小写的正则表达式。构造函数语法: 使用 RegExp 构造函数来创建。这对于动态创建正则表达式特别有用。例如new RegExp(abc, i) 等同于字面量语法中的 /abc/i。
// 字面量语法
const regex1 /abc/i;// 构造函数语法
const regex2 new RegExp(abc, i);12.1.2. 基本模式
.: 匹配除换行符之外的任意单个字符适用于通配字符。^: 匹配输入的开始位置。用于确保模式必须在字符串开始出现。$: 匹配输入的结束位置。用于确保模式必须在字符串末尾出现。*: 匹配前面的子表达式零次或多次Kleene Star。: 匹配前面的子表达式一次或多次。?: 匹配前面的子表达式零次或一次。{n,m}: 匹配前面的子表达式至少 n 次但不超过 m 次。
// 使用示例
const string hello world!;
console.log(/^hello/.test(string)); // true, 因为 hello 在开始
console.log(/world!$/.test(string)); // true, 因为 world! 在结尾
console.log(/l{2,3}/.test(string)); // true, 因为存在 ll12.1.3. 字符类
[abc]: 匹配方括号中任一字符类似于 a|b|c。[^abc]: 匹配不在方括号中的任一字符。[a-z]: 匹配小写字母范围内的任一字符。\d: 匹配任何数字字符等价于 [0-9]。\D: 匹配任何非数字字符。\w: 匹配任何字母、数字或下划线字符等价于 [a-zA-Z0-9_]。\W: 匹配任何非字母、数字或下划线字符。\s: 匹配空白字符包括空格、制表符和换行符。\S: 匹配任何非空白字符。
// 使用示例
console.log(/\d/.test(123abc)); // true, 因为包含数字
console.log(/\D/.test(abc)); // true, 因为包含非数字
console.log(/\w/.test(abc_123)); // true, 因为匹配字母、数字或下划线
console.log(/\s/.test(abc 123)); // true, 因为有空格12.1.4. 特殊字符和分组
|: 逻辑“或”操作用于匹配多个可能的选项。( ): 用于分组可以将正则的多个部分进行组合并捕获匹配内容。(?:x): 仅进行分组而不捕获称为非捕获分组可用于匹配但不需要引用组内容。(?x): 正向前瞻断言特定条件后的位置应该匹配模式 x不消耗字符。(?!x): 负向前瞻断言特定条件后的位置不匹配模式 x不消耗字符。
// 使用示例
console.log(/foo|bar/.test(foo)); // true, 因为 foo 是或选项
console.log(/(?:hello)/.test(hello world)); // true, 非捕获分组匹配但不创建组
console.log(/(?world)/.test(hello world)); // true, 因为存在 world 后面的位置
console.log(/(?!world)/.test(hello there)); // true, 因为 world 不在后面通过以上正则表达式语法基础开发者可以对字符串进行复杂的匹配和处理从而实现高效的文本解析和验证。
12.2. 常见应用与匹配模式
正则表达式是一种强大的工具用于在字符串中搜索、匹配和替换特定的模式。在日常编程中正则表达式适用于多种实际场景例如表单验证、文本替换和数据提取等。
12.2.1 电子邮件验证
电子邮件地址需要符合特定的格式以确保它是有效的。下面的正则表达式匹配基本的电子邮件结构
const emailRegex /^[^\s][^\s]\.[^\s]$/;
console.log(emailRegex.test(examplemail.com)); // true解析
^ 和 $表示行的开始和结束。确保整个字符串都被匹配。[^\s]匹配任意长度的、除空格和 外的字符序列。匹配真正的 符号。\.匹配点号 .使用 \ 进行转义因为 . 是一个特殊字符。表示前面的一个或多个字符。
12.2.2 电话号码匹配
电话格式可能因国家而异。以下示例是匹配美国的标准格式
const phoneRegex /^\d{3}-\d{3}-\d{4}$/;
console.log(phoneRegex.test(123-456-7890)); // true解析
\d{3}匹配三个数字。-匹配连接的连字符。完整模式 ^\d{3}-\d{3}-\d{4}$ 确保电话号码格式为 123-456-7890。
12.2.3 URL 验证
用于验证 URL 的正则表达式稍微复杂的原因是 URL 结构多样
const urlRegex /^(https?:\/\/)?([\da-z.-])\.([a-z.]{2,6})([\/\w.-]*)*\/?$/;
console.log(urlRegex.test(https://www.example.com)); // true解析
(https?:\/\/)?可选的开头部分匹配 http:// 或 https://。([\da-z.-])匹配主机名部分包括字母、数字、点和短横线。([a-z.]{2,6})匹配域名的后缀部分长度从 2 到 6 个字符。([\/\w.-]*)*可选的路径部分包括 /、字母、数字、点、下划线和短横线。
12.2.4 正则表达式替换
使用 String.prototype.replace() 可以替换符合模式的子字符串这对修改文本中的特定部分非常有用
const text The sky is blue.;
const newText text.replace(/\bblue\b/, green); // The sky is green.解析
\b表示单词边界确保只替换完整的单词 blue 而不是例如 blues 的一部分。
12.2.5 提取指定模式字符串
可以使用 String.prototype.match() 方法提取符合特定模式的内容。比如提取日期信息
const data My birthday is on 2020-01-01 and anniversary is on 2021-02-02;
const dates data.match(/\d{4}-\d{2}-\d{2}/g); // [2020-01-01, 2021-02-02]解析
\d{4}匹配 4 个连续数字年份。-匹配分隔年的破折号。\d{2}匹配 2 个数字月和日。g全局搜索标志返回所有符合条件的匹配。
通过熟练掌握正则表达式的使用可以极大提高字符串处理的效率减少代码复杂度。
注意事项
性能复杂的正则表达式可能会对性能产生影响尤其是在大文本或需要实时处理的情况下。可读性正则表达式往往难以阅读和理解维护时尤其麻烦因此在设计正则表达式时应尽量保持简单和高效。测试和验证在实现正则表达式之前使用工具和调试环境来测试和验证模式的正确性。
第十三章 性能优化
性能优化在任何编程语言中都是至关重要的尤其是在涉及用户体验的前端开发中。在这一章中我们将专注于如何在 ECMAScript 中管理内存与优化性能以确保程序高效运行。
13.1. 内存管理与垃圾回收
JavaScript 在内存管理方面提供了自动垃圾回收机制这意味着程序员不必显式地释放内存。然而理解垃圾回收的工作原理可以帮助我们编写更高效的代码。知道怎么合理地使用内存可以提升程序的性能和响应速度。
13.1.1. 内存生命周期
JavaScript 的内存管理可以概念化为三部分分配、使用、和释放内存。 分配内存当创建变量、对象或数据结构时JavaScript 自动分配内存。 每次我们声明一个变量时JavaScript 会在内存中分配一个特定的空间来存储该变量的数据。例如数值、对象、数组等类型无论是堆内存用于对象、数组等还是栈内存用于基本数据类型JavaScript 都会自动处理内存的分配。 使用内存利用分配的内存来执行程序逻辑。 程序运行时进行各种操作如计算、操作数据结构、调用函数等这些操作必然会利用到预先分配的内存。 释放内存不再需要的内存由垃圾回收器Garbage Collector, GC自动回收。 当对象不再可访问时内存便成为垃圾。这时垃圾回收器会自动释放这些不再需要的内存空间使其可供其他新创建的对象使用。
13.1.2. 垃圾回收Garbage Collection
JavaScript 引擎中常用的垃圾回收机制是标记-清除算法Mark-and-Sweep。在这种机制下垃圾回收过程如下
标记阶段垃圾回收器GC会在堆内存中从根通常是全局对象开始递归遍历所有可达reachable的对象。清除阶段标记阶段结束后不在“本次标记阶段中被标记为可达”的对象则被标记为垃圾随后这些对象占用的内存会被释放。
理解这一过程对于优化 JavaScript 性能有直接的帮助因为理解 GC 的触发时机可以避免一些不必要的性能瓶颈。
13.1.3. 内存管理的最佳实践
要编写高效的 JavaScript 程序可遵循以下内存管理最佳实践 避免内存泄漏 意外的全局变量必须使用 let, const 或 var 声明变量未明确声明的变量会被提升为全局变量可能会持续占用内存。 忘记清除的定时器或事件监听定时器和事件监听在不需要时应通过 clearTimeout() 和 removeEventListener() 等方法清除否则它们保持活动状态会引用相关的内存导致内存泄漏。 闭包虽然闭包是 JavaScript 的强大特性但若不当使用例如在循环中创建大量匿名的闭包函数可能会无意中保持变量存在导致内存占用持续增长。 优化大数据处理 使用 ArrayBuffer 和 TypedArray对于处理大型数据集、图像处理或科学计算直接操作缓冲区和类型化数组可以更有效管理内存。 避免反复创建大型对象和字符串对于大型对象的频繁创建和销毁可以使用对象池化Object Pooling的方法这样通过复用对象减少垃圾回收的工作量。 合理使用生产环境工具利用浏览器开发者工具如 Chrome DevTools 的内存工具查找内存泄漏和优化内存使用以确保应用程序的稳定性和性能。
仔细管理内存分配和减少不必要的内存占用将帮助保持 JavaScript 应用程序快速高效并最大限度地降低垃圾回收对性能的影响。
13.2. 性能调优技巧
在编写高效的 JavaScript 代码时除了内存管理优化代码执行性能也很重要。性能调优是提升应用响应速度和用户体验的关键环节。以下是一些实用的性能优化技巧
13.2.1. 减少重排和重绘 批量操作 DOM DOM 操作通常是浏览器性能的瓶颈因为它会导致浏览器重排reflow和重绘repaint。建议使用 文档片段DocumentFragment进行批量 DOM 操作以减少对页面的多次更新。例如创建大量 DOM 元素时可以先在 DocumentFragment 中构建好最后一次性附加到实际 DOM。 const fragment document.createDocumentFragment();
for (let i 0; i 1000; i) {const div document.createElement(div);fragment.appendChild(div);
}
document.body.appendChild(fragment);最小化样式计算 尽量合并对元素样式的多次更新可以使用 classList 来批量修改样式而不是单独设置 className以避免触发多次重排。 element.classList.add(new-class);
element.classList.remove(old-class);13.2.2. 异步编程 requestAnimationFrame() 与 setTimeout() 或 setInterval() 相比requestAnimationFrame() 更适合执行动画代码它能够更精确地与屏幕刷新同步从而提供更平滑的动画效果减少卡顿。 function animate() {// animation logic hererequestAnimationFrame(animate);
}
requestAnimationFrame(animate);Web Worker 使用 Web Workers 来执行耗时的计算任务以避免阻塞主线程。Web Worker 运行在后台线程与页面的 UI 线程独立从而避免 UI 卡顿。 const worker new Worker(worker.js);
worker.postMessage(start);
worker.onmessage function(event) {console.log(Result from worker:, event.data);
};13.2.3. 减少网络负担 懒加载 通过懒加载技术仅在用户需要时加载资源如图片和组件。例如图片可以设置为只有在进入视口时才加载。 img srcdefault.jpg data-srcreal-image.jpg classlazyload压缩和缩小资源 使用工具如 UglifyJS、Terser、Webpack 等对 JavaScript 和 CSS 文件进行压缩和缩小减少传输体积提升加载速度。 缓存 利用浏览器缓存策略和 CDNs 来减少加载时间和服务器负担。
13.2.4. 使用性能工具 浏览器开发者工具 浏览器提供的开发者工具如 Chrome DevTools 可以进行详细的性能分析识别出长时间运行的脚本、耗时的重排、重绘等性能瓶颈。 Lighthouse 使用 Google 的 Lighthouse 工具进行网页性能评估可以生成分析报告并给出优化建议。 内存概况Memory Profiling 通过内存 Profiler 分析应用程序的内存消耗找出内存泄漏和过高内存占用的原因。
通过应用这些性能优化技巧您将能够编写在现代网络环境中表现出色的高效 JavaScript 应用程序。这不仅能提升应用响应速度还能显著改善用户体验。