JavaScript 中有哪些基本数据类型 (Primitive Types)?
What are the primitive data types in JavaScript?
- *考察点:基础知识的掌握。*
共 141 道题目
What are the primitive data types in JavaScript?
What are the primitive data types in JavaScript?
考察点:基础知识的掌握。
答案:
JavaScript 中的基本数据类型(原始类型)是指直接存储在栈内存中的简单数据类型,它们是不可变的。ES5 中共有 6 种基本数据类型。
基本数据类型列表:
Number(数字):
let num = 42;
let float = 3.14;
let negative = -10;
// 包括整数、浮点数、正数、负数、零
String(字符串):
let str1 = "hello";
let str2 = 'world';
let str3 = "123";
// 用引号包围的字符序列
Boolean(布尔值):
let isTrue = true;
let isFalse = false;
// 只有 true 和 false 两个值
Null(空值):
let emptyValue = null;
// 表示一个空的对象引用
Undefined(未定义):
let undefinedVar;
console.log(undefinedVar); // undefined
// 变量声明但未赋值时的默认值
特殊说明:
检测数据类型:
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof true); // "boolean"
console.log(typeof null); // "object" (这是一个历史遗留的bug)
console.log(typeof undefined); // "undefined"
实际应用:
What is the difference between == and ===?
What is the difference between == and ===?
考察点:对类型转换和严格比较的理解。
答案:
== 和 === 是 JavaScript 中两种不同的比较运算符,它们的主要区别在于是否进行类型转换。
主要区别:
== (宽松相等/抽象相等):
=== (严格相等):
代码示例对比:
// 类型转换示例
console.log(5 == "5"); // true (字符串"5"被转换为数字5)
console.log(5 === "5"); // false (类型不同)
console.log(true == 1); // true (true被转换为1)
console.log(true === 1); // false (类型不同)
console.log(null == undefined); // true (特殊规则)
console.log(null === undefined); // false (类型不同)
console.log(0 == false); // true (false被转换为0)
console.log(0 === false); // false (类型不同)
console.log("" == 0); // true (空字符串被转换为0)
console.log("" === 0); // false (类型不同)
类型转换规则:
使用 == 时,JavaScript 会按照以下规则进行类型转换:
===最佳实践:
// 推荐使用 ===
function isEqual(a, b) {
return a === b;
}
// 需要类型转换时,显式进行
function compareNumbers(a, b) {
return Number(a) === Number(b);
}
实际应用:
=== 避免意外的类型转换=== 确保数据类型正确性===== 以提高代码质量What is scope? How many types of scope are there in JavaScript?
What is scope? How many types of scope are there in JavaScript?
考察点:对变量可访问性规则的理解。
答案:
作用域是指变量和函数的可访问性和生存周期的规则。它决定了在代码的哪些部分可以访问特定的变量或函数。JavaScript 中的作用域遵循词法作用域(静态作用域)规则。
ES5 中的作用域类型:
全局作用域 (Global Scope):
var globalVar = "我是全局变量";
function showGlobal() {
console.log(globalVar); // 可以访问全局变量
}
console.log(globalVar); // "我是全局变量"
函数作用域 (Function Scope):
function myFunction() {
var functionVar = "我是函数作用域变量";
console.log(functionVar); // 可以访问
}
myFunction(); // "我是函数作用域变量"
// console.log(functionVar); // ReferenceError: functionVar is not defined
作用域特性:
变量查找机制:
var global = "全局";
function outer() {
var outerVar = "外层";
function inner() {
var innerVar = "内层";
console.log(innerVar); // "内层" (当前作用域)
console.log(outerVar); // "外层" (父级作用域)
console.log(global); // "全局" (全局作用域)
}
inner();
}
outer();
作用域链工作原理:
function createCounter() {
var count = 0;
return function() {
count++; // 访问父级作用域的变量
return count;
};
}
var counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
// count 变量被内部函数引用,形成闭包
注意事项:
没有块级作用域:ES5 中 var 声明的变量没有块级作用域
if (true) {
var blockVar = "块内变量";
}
console.log(blockVar); // "块内变量" (仍然可以访问)
for (var i = 0; i < 3; i++) {
// i 在循环结束后仍然存在
}
console.log(i); // 3
变量提升:声明会被提升到作用域顶部
function example() {
console.log(x); // undefined (而不是 ReferenceError)
var x = 1;
}
实际应用:
What is hoisting?
What is hoisting?
考察点:对 JavaScript 代码执行顺序的理解。
答案:
变量提升是 JavaScript 引擎在编译阶段将变量和函数声明移动到其作用域顶部的行为。这意味着你可以在声明之前使用变量和函数,但需要注意提升的具体规则和限制。
提升的工作原理:
var 变量提升:
// 实际代码
console.log(x); // undefined (不是 ReferenceError)
var x = 5;
console.log(x); // 5
// 引擎实际执行的等价代码
var x; // 声明被提升,初始化为 undefined
console.log(x); // undefined
x = 5; // 赋值留在原地
console.log(x); // 5
函数声明提升:
// 可以在声明前调用函数
sayHello(); // "Hello!"
function sayHello() {
console.log("Hello!");
}
// 整个函数定义都被提升了
函数表达式不会提升:
// 这会报错
sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function() {
console.log("Goodbye!");
};
// 等价于:
var sayGoodbye; // 只有变量声明被提升,值为 undefined
sayGoodbye(); // 尝试调用 undefined,导致 TypeError
sayGoodbye = function() {
console.log("Goodbye!");
};
提升的详细规则:
变量声明提升但赋值不提升:
function example() {
console.log(a); // undefined
console.log(b); // undefined
var a = 1;
var b = 2;
console.log(a); // 1
console.log(b); // 2
}
函数声明优先于变量声明:
function test() {
console.log(foo); // function foo() {...}
var foo = "variable";
function foo() {
return "function";
}
console.log(foo); // "variable"
}
同名函数声明,后者覆盖前者:
function duplicate() {
console.log(func()); // "second"
function func() {
return "first";
}
function func() {
return "second";
}
}
常见陷阱:
// 循环中的变量提升
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3 次 3,而不是 0,1,2
}, 100);
}
// 条件语句中的提升
function conditionalHoisting() {
if (false) {
var x = 1; // 声明仍然会被提升
}
console.log(x); // undefined (不是 ReferenceError)
}
最佳实践:
实际应用:
What are the possible return values of the typeof operator?
What are the possible return values of the typeof operator?
考察点:对数据类型检测方法的掌握。
答案:
typeof 操作符用于检测变量的数据类型,返回一个表示类型的字符串。在 ES5 中,typeof 有 7 种可能的返回值。
所有可能的返回值:
“undefined” - 未定义的值
console.log(typeof undefined); // "undefined"
console.log(typeof undeclaredVar); // "undefined"
var declared;
console.log(typeof declared); // "undefined"
“boolean” - 布尔值
console.log(typeof true); // "boolean"
console.log(typeof false); // "boolean"
console.log(typeof Boolean(1)); // "boolean"
“number” - 数字
console.log(typeof 42); // "number"
console.log(typeof 3.14); // "number"
console.log(typeof NaN); // "number"
console.log(typeof Infinity); // "number"
“string” - 字符串
console.log(typeof "hello"); // "string"
console.log(typeof 'world'); // "string"
console.log(typeof String(123)); // "string"
“object” - 对象(包括 null)
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof null); // "object" (历史遗留的 bug)
console.log(typeof new Date()); // "object"
console.log(typeof /regex/); // "object"
“function” - 函数
console.log(typeof function(){}); // "function"
console.log(typeof Math.max); // "function"
console.log(typeof Array); // "function"
特殊情况和注意事项:
历史遗留问题:
console.log(typeof null); // "object" (这是一个著名的 JavaScript bug)
// 正确检测 null 的方法
function isNull(value) {
return value === null;
}
// 检测真正的对象(排除 null)
function isObject(value) {
return typeof value === "object" && value !== null;
}
数组检测问题:
console.log(typeof [1, 2, 3]); // "object"
// 正确检测数组的方法
console.log(Array.isArray([1, 2, 3])); // true (ES5+)
console.log([1, 2, 3] instanceof Array); // true
console.log(Object.prototype.toString.call([1, 2, 3])); // "[object Array]"
未声明变量的安全检测:
// 检测未声明的变量不会报错
if (typeof someUndeclaredVar === "undefined") {
console.log("变量未声明或值为 undefined");
}
// 这样会报 ReferenceError
// if (someUndeclaredVar === undefined) { ... }
实际应用场景:
// 类型检查函数
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}
// 参数类型验证
function processData(data) {
if (typeof data !== "object" || data === null) {
throw new Error("Expected an object");
}
// 处理对象数据
}
// 功能检测
if (typeof localStorage !== "undefined") {
// 浏览器支持 localStorage
localStorage.setItem("key", "value");
}
类型检测最佳实践:
typeof 检测基本类型Array.isArray() 检测数组value === null 检测 nullinstanceof 检测对象实例类型Object.prototype.toString.call() 进行精确类型检测What is the difference between null and undefined?
What is the difference between null and undefined?
考察点:对特殊值的理解和使用场景。
答案:
null 和 undefined 都表示"没有值",但它们有不同的含义和使用场景。理解它们的区别对于编写健壮的 JavaScript 代码至关重要。
基本定义:
主要区别:
1. 类型检测:
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (历史遗留问题)
console.log(undefined == null); // true (类型转换)
console.log(undefined === null); // false (严格比较)
2. 产生方式不同:
// undefined 的产生情况
var declaredButNotAssigned;
console.log(declaredButNotAssigned); // undefined
function noReturn() {
// 没有返回值
}
console.log(noReturn()); // undefined
var obj = {};
console.log(obj.nonExistentProperty); // undefined
function hasParameter(param) {
console.log(param); // 如果不传参数,则为 undefined
}
hasParameter(); // undefined
// null 通常是主动赋值
var intentionallyEmpty = null;
var element = document.getElementById("nonexistent"); // 可能返回 null
3. 数值转换:
console.log(Number(undefined)); // NaN
console.log(Number(null)); // 0
console.log(undefined + 1); // NaN
console.log(null + 1); // 1
使用场景对比:
undefined 的使用场景:
// 检测变量是否已定义
if (typeof someVariable !== "undefined") {
// 变量存在
}
// 函数参数默认值处理
function greet(name) {
if (name === undefined) {
name = "Guest";
}
console.log("Hello, " + name);
}
// 检测对象属性是否存在
var user = {name: "John"};
if (user.age === undefined) {
console.log("年龄未设置");
}
null 的使用场景:
// 主动清空对象引用
var data = {name: "John"};
data = null; // 释放内存
// 表示空的对象值
var result = findUser("nonexistent");
if (result === null) {
console.log("用户不存在");
}
// 初始化将来会被赋值的变量
var currentUser = null; // 明确表示当前没有用户登录
检测方法:
function checkValue(value) {
// 检测 undefined
if (value === undefined) {
return "值为 undefined";
}
// 检测 null
if (value === null) {
return "值为 null";
}
// 检测是否为空值(null 或 undefined)
if (value == null) { // 等同于 value === null || value === undefined
return "值为空";
}
return "有值:" + value;
}
最佳实践:
// 推荐的空值检测方式
function isEmpty(value) {
return value === null || value === undefined;
}
// 安全的属性访问
function getProperty(obj, prop) {
if (obj === null || obj === undefined) {
return undefined;
}
return obj[prop];
}
// 设置默认值
function setDefault(value, defaultValue) {
return (value === null || value === undefined) ? defaultValue : value;
}
实际应用:
What are falsy values? What falsy values exist in JavaScript?
What are falsy values? What falsy values exist in JavaScript?
考察点:对布尔转换规则的理解。
答案:
Falsy 值是在布尔上下文中被转换为 false 的值。当这些值用在条件语句、逻辑运算或任何需要布尔值的地方时,JavaScript 会将它们视为 false。
JavaScript 中的 7 个 falsy 值:
false - 布尔值 false
console.log(Boolean(false)); // false
if (false) {
console.log("不会执行");
}
0 - 数字零
console.log(Boolean(0)); // false
if (0) {
console.log("不会执行");
}
-0 - 负零
console.log(Boolean(-0)); // false
console.log(0 === -0); // true
“” - 空字符串
console.log(Boolean("")); // false
console.log(Boolean('')); // false
if ("") {
console.log("不会执行");
}
null - 空值
console.log(Boolean(null)); // false
if (null) {
console.log("不会执行");
}
undefined - 未定义
console.log(Boolean(undefined)); // false
if (undefined) {
console.log("不会执行");
}
NaN - 非数字
console.log(Boolean(NaN)); // false
if (NaN) {
console.log("不会执行");
}
所有其他值都是 truthy 值:
// 这些都是 truthy 值
console.log(Boolean("0")); // true (字符串"0")
console.log(Boolean("false")); // true (字符串"false")
console.log(Boolean([])); // true (空数组)
console.log(Boolean({})); // true (空对象)
console.log(Boolean(function(){})); // true (函数)
console.log(Boolean(1)); // true (非零数字)
console.log(Boolean(-1)); // true (负数)
console.log(Boolean(Infinity)); // true (无穷大)
实际应用场景:
条件判断:
function processValue(value) {
if (value) {
console.log("值存在且有效:" + value);
} else {
console.log("值为空或无效");
}
}
processValue("hello"); // "值存在且有效:hello"
processValue(0); // "值为空或无效"
processValue(""); // "值为空或无效"
processValue(null); // "值为空或无效"
默认值设置:
// 使用 || 运算符设置默认值
function greet(name) {
name = name || "Guest"; // 如果 name 是 falsy,使用 "Guest"
console.log("Hello, " + name);
}
greet(""); // "Hello, Guest"
greet("John"); // "Hello, John"
greet(null); // "Hello, Guest"
数据过滤:
var array = [1, 0, "", "hello", null, undefined, "world", false, NaN];
// 过滤掉所有 falsy 值
var truthyValues = array.filter(function(item) {
return Boolean(item);
});
console.log(truthyValues); // [1, "hello", "world"]
// 或者更简洁的写法
var truthyValues2 = array.filter(Boolean);
console.log(truthyValues2); // [1, "hello", "world"]
表单验证:
function validateForm(formData) {
var errors = [];
if (!formData.username) {
errors.push("用户名不能为空");
}
if (!formData.email) {
errors.push("邮箱不能为空");
}
if (!formData.age || formData.age <= 0) {
errors.push("年龄必须大于0");
}
return errors;
}
注意事项:
// 这些值虽然看起来"空",但它们是 truthy 的
console.log(Boolean("0")); // true
console.log(Boolean("false")); // true
console.log(Boolean([])); // true
console.log(Boolean({})); // true
// 特殊的数字情况
console.log(Boolean(0)); // false
console.log(Boolean(-0)); // false
console.log(Boolean(0.0)); // false
// NaN 的特殊性
console.log(NaN == NaN); // false
console.log(Boolean(NaN)); // false
类型转换函数:
// 显式转换为布尔值的方法
function toBool(value) {
return Boolean(value);
}
// 或使用双重否定
function toBool2(value) {
return !!value;
}
console.log(toBool("")); // false
console.log(toBool2(0)); // false
实际应用:
How to detect if a variable is an array?
How to detect if a variable is an array?
考察点:数组检测方法的掌握。
答案:
检测数组是 JavaScript 中的一个经典问题,因为 typeof 操作符对数组返回 “object”,所以需要使用专门的方法来准确判断。
推荐方法:Array.isArray() (ES5+):
console.log(Array.isArray([])); // true
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray("string")); // false
console.log(Array.isArray(null)); // false
// 函数中使用
function processArray(data) {
if (Array.isArray(data)) {
console.log("这是一个数组,长度为:" + data.length);
} else {
console.log("不是数组");
}
}
其他检测方法:
1. instanceof 操作符:
console.log([] instanceof Array); // true
console.log([1, 2, 3] instanceof Array); // true
console.log({} instanceof Array); // false
// 注意:跨框架问题
// 如果数组来自不同的 iframe,instanceof 可能失效
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
var iframeArray = iframe.contentWindow.Array;
var arr = new iframeArray();
console.log(arr instanceof Array); // false (在某些情况下)
2. Object.prototype.toString.call():
function isArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
console.log(isArray([])); // true
console.log(isArray([1, 2, 3])); // true
console.log(isArray({})); // false
console.log(isArray("string")); // false
// 这种方法最可靠,即使跨框架也能正确工作
3. constructor 属性:
function isArrayByConstructor(value) {
return value && value.constructor === Array;
}
console.log(isArrayByConstructor([])); // true
console.log(isArrayByConstructor(null)); // false
// 注意:constructor 可能被修改
var arr = [];
arr.constructor = Object;
console.log(arr.constructor === Array); // false (被修改了)
方法对比和选择:
var testCases = [
[],
[1, 2, 3],
{},
"string",
null,
undefined,
arguments // 函数的 arguments 对象
];
function compareArrayDetectionMethods() {
testCases.forEach(function(testCase) {
console.log("测试值:", testCase);
console.log("Array.isArray:", Array.isArray(testCase));
console.log("instanceof:", testCase instanceof Array);
console.log("toString:", Object.prototype.toString.call(testCase) === '[object Array]');
console.log("---");
});
}
兼容性处理(为不支持 Array.isArray 的环境):
// Array.isArray 的 polyfill
if (!Array.isArray) {
Array.isArray = function(value) {
return Object.prototype.toString.call(value) === '[object Array]';
};
}
// 或者创建自己的检测函数
function isArray(value) {
// 优先使用原生方法
if (Array.isArray) {
return Array.isArray(value);
}
// 回退到 toString 方法
return Object.prototype.toString.call(value) === '[object Array]';
}
实际应用场景:
// 函数参数处理
function processData(data) {
if (Array.isArray(data)) {
// 处理数组数据
data.forEach(function(item) {
console.log("处理数组项:", item);
});
} else {
// 将单个值转换为数组
data = [data];
console.log("转换为数组:", data);
}
}
processData([1, 2, 3]); // 处理数组
processData("single"); // 转换为数组
// API 响应处理
function handleAPIResponse(response) {
var items = response.data;
// 确保 items 是数组
if (!Array.isArray(items)) {
console.warn("API 返回的数据不是数组,进行处理");
items = items ? [items] : [];
}
return items;
}
// 类型安全的数组操作
function safeArrayOperation(arr, operation) {
if (!Array.isArray(arr)) {
throw new TypeError("期望数组类型,但收到:" + typeof arr);
}
return operation(arr);
}
性能比较:
// 性能测试(仅供参考)
var testArray = [1, 2, 3, 4, 5];
var iterations = 1000000;
console.time("Array.isArray");
for (var i = 0; i < iterations; i++) {
Array.isArray(testArray);
}
console.timeEnd("Array.isArray");
console.time("instanceof");
for (var i = 0; i < iterations; i++) {
testArray instanceof Array;
}
console.timeEnd("instanceof");
console.time("toString");
for (var i = 0; i < iterations; i++) {
Object.prototype.toString.call(testArray) === '[object Array]';
}
console.timeEnd("toString");
最佳实践:
Array.isArray():标准方法,性能好,兼容性强typeof:对数组返回 “object”,无法区分数组和对象instanceof:在跨框架环境中可能失效toString 方法:最可靠但性能稍差,适合作为 polyfillWhat is the difference between primitive types and reference types in JavaScript?
What is the difference between primitive types and reference types in JavaScript?
考察点:对内存分配和数据传递方式的理解。
答案:
原始类型和引用类型在内存存储、数据传递、比较方式等方面有着根本性的区别。理解这些差异对于掌握 JavaScript 的内存管理和避免常见陷阱至关重要。
基本定义:
内存存储方式:
原始类型 - 栈内存存储:
var a = 10;
var b = a; // 复制值
b = 20;
console.log(a); // 10 (a 不受影响)
console.log(b); // 20
// 内存示意:
// 栈内存
// a: 10
// b: 20 (独立的内存空间)
引用类型 - 堆内存存储:
var obj1 = {name: "John"};
var obj2 = obj1; // 复制引用(地址)
obj2.name = "Jane";
console.log(obj1.name); // "Jane" (obj1 也被修改了)
console.log(obj2.name); // "Jane"
// 内存示意:
// 栈内存 堆内存
// obj1: 地址1 → {name: "Jane"}
// obj2: 地址1 → (指向同一个对象)
数据传递方式:
按值传递(原始类型):
function modifyPrimitive(value) {
value = value + 10;
console.log("函数内:", value); // 15
}
var num = 5;
modifyPrimitive(num);
console.log("函数外:", num); // 5 (原值不变)
按引用传递(引用类型):
function modifyObject(obj) {
obj.value = obj.value + 10;
console.log("函数内:", obj.value); // 15
}
var myObj = {value: 5};
modifyObject(myObj);
console.log("函数外:", myObj.value); // 15 (原对象被修改)
// 但重新赋值不会影响原对象
function reassignObject(obj) {
obj = {value: 100}; // 这只是改变了局部变量 obj 的指向
}
reassignObject(myObj);
console.log("重新赋值后:", myObj.value); // 15 (原对象未变)
比较方式差异:
原始类型 - 值比较:
var a = 5;
var b = 5;
console.log(a === b); // true (值相等)
var str1 = "hello";
var str2 = "hello";
console.log(str1 === str2); // true (值相等)
引用类型 - 引用比较:
var obj1 = {name: "John"};
var obj2 = {name: "John"};
console.log(obj1 === obj2); // false (不同的对象,不同的引用)
var obj3 = obj1;
console.log(obj1 === obj3); // true (相同的引用)
// 数组也是如此
var arr1 = [1, 2, 3];
var arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false (不同的数组对象)
复制操作的区别:
原始类型 - 深复制(自动):
var original = 42;
var copy = original;
copy = 100;
console.log(original); // 42 (不受影响)
console.log(copy); // 100
引用类型 - 浅复制:
var originalObj = {
name: "John",
details: {age: 25}
};
// 浅复制
var shallowCopy = {};
for (var key in originalObj) {
shallowCopy[key] = originalObj[key];
}
shallowCopy.name = "Jane"; // 不影响原对象
shallowCopy.details.age = 30; // 影响原对象(共享嵌套对象)
console.log(originalObj.name); // "John"
console.log(originalObj.details.age); // 30 (被修改了)
深复制的实现:
// 简单的深复制函数(仅处理普通对象和数组)
function deepCopy(obj) {
if (obj === null || typeof obj !== "object") {
return obj; // 原始类型直接返回
}
if (obj instanceof Array) {
var arrCopy = [];
for (var i = 0; i < obj.length; i++) {
arrCopy[i] = deepCopy(obj[i]);
}
return arrCopy;
}
var objCopy = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
objCopy[key] = deepCopy(obj[key]);
}
}
return objCopy;
}
// 使用示例
var original = {
name: "John",
hobbies: ["reading", "swimming"],
details: {age: 25}
};
var deepCopied = deepCopy(original);
deepCopied.name = "Jane";
deepCopied.hobbies.push("coding");
deepCopied.details.age = 30;
console.log(original.name); // "John" (不受影响)
console.log(original.hobbies); // ["reading", "swimming"] (不受影响)
console.log(original.details.age); // 25 (不受影响)
类型检测的差异:
// 原始类型检测
function isPrimitive(value) {
return value !== Object(value);
}
console.log(isPrimitive(5)); // true
console.log(isPrimitive("string")); // true
console.log(isPrimitive({})); // false
console.log(isPrimitive([])); // false
// 引用类型检测
function isObject(value) {
return value === Object(value);
}
实际应用场景:
// 状态管理中的陷阱
var appState = {
user: {name: "John"},
items: [1, 2, 3]
};
function updateUser(state, newName) {
// 错误:直接修改原状态
// state.user.name = newName;
// 正确:创建新对象
return {
user: {name: newName},
items: state.items.slice() // 复制数组
};
}
// 函数参数验证
function processUserData(userData) {
// 防御性复制,避免修改原始数据
var userCopy = deepCopy(userData);
// 安全地修改副本
userCopy.processed = true;
return userCopy;
}
最佳实践:
What are function declarations and function expressions? What are their differences?
What are function declarations and function expressions? What are their differences?
考察点:对函数定义方式的理解。
答案:
函数声明和函数表达式是 JavaScript 中创建函数的两种主要方式,它们在语法、提升行为、使用时机等方面有着重要区别。
基本语法对比:
函数声明(Function Declaration):
function functionName() {
// 函数体
return "这是函数声明";
}
// 具名函数声明
function greet(name) {
return "Hello, " + name;
}
函数表达式(Function Expression):
// 匿名函数表达式
var functionName = function() {
return "这是函数表达式";
};
// 具名函数表达式
var greet = function greetFunction(name) {
return "Hello, " + name;
};
主要区别:
1. 提升行为(Hoisting):
// 函数声明 - 完全提升
console.log(declaredFunction()); // "可以调用" (正常工作)
function declaredFunction() {
return "可以调用";
}
// 函数表达式 - 变量提升,但函数不提升
console.log(expressedFunction); // undefined
// console.log(expressedFunction()); // TypeError: expressedFunction is not a function
var expressedFunction = function() {
return "现在可以调用了";
};
console.log(expressedFunction()); // "现在可以调用了"
2. 条件创建:
var condition = true;
// 函数声明在条件语句中(不推荐,行为不一致)
if (condition) {
function conditionalFunction() {
return "在条件中声明";
}
}
// 函数表达式在条件语句中(推荐)
var conditionalFunction2;
if (condition) {
conditionalFunction2 = function() {
return "在条件中表达";
};
}
3. 立即执行函数表达式(IIFE):
// 函数表达式可以立即执行
(function() {
console.log("立即执行的函数表达式");
})();
// 或者
(function(name) {
console.log("Hello, " + name);
})("World");
// 函数声明不能直接立即执行
// function() { console.log("错误"); }(); // 语法错误
函数表达式的变体:
匿名函数表达式:
var anonymous = function() {
return "匿名函数";
};
console.log(anonymous.name); // "" (ES5中为空字符串)
具名函数表达式:
var named = function namedFunction() {
return "具名函数表达式";
};
console.log(named.name); // "namedFunction"
// 名称只在函数内部可见
var factorial = function fact(n) {
return n <= 1 ? 1 : n * fact(n - 1); // 递归调用自己
};
console.log(factorial(5)); // 120
// console.log(fact(5)); // ReferenceError: fact is not defined
使用场景对比:
函数声明适用场景:
// 1. 需要在整个作用域中都能访问的函数
function utilityFunction() {
return "工具函数";
}
// 2. 主要的业务逻辑函数
function processUserData(userData) {
// 处理用户数据的主要逻辑
return userData;
}
// 3. 递归函数(名称固定,便于调用)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
函数表达式适用场景:
// 1. 条件性创建函数
var mathOperation;
if (needAdvancedMath) {
mathOperation = function(a, b) {
return Math.pow(a, b);
};
} else {
mathOperation = function(a, b) {
return a * b;
};
}
// 2. 作为其他函数的参数
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map(function(num) {
return num * 2;
});
// 3. 模块模式
var myModule = (function() {
var privateVar = "私有变量";
return {
publicMethod: function() {
return privateVar;
}
};
})();
// 4. 事件处理
button.addEventListener('click', function() {
console.log("按钮被点击");
});
性能和内存考虑:
// 函数声明 - 在解析阶段就创建
function declared() {
return "声明的函数";
}
// 函数表达式 - 在执行阶段创建
var expressed = function() {
return "表达式函数";
};
// 在循环中创建函数的差异
var functions = [];
// 避免在循环中使用函数声明
for (var i = 0; i < 3; i++) {
// 推荐:使用函数表达式
functions.push(function(index) {
return function() {
return "函数 " + index;
};
}(i));
}
调试和错误处理:
// 具名函数表达式在调试时更有用
var processData = function processUserInformation(data) {
try {
// 处理数据
return data.processed;
} catch (error) {
console.log("processUserInformation 中出错:", error);
// 错误堆栈中会显示函数名 processUserInformation
throw error;
}
};
// 匿名函数在错误堆栈中显示为 anonymous
var process2 = function(data) {
// 错误堆栈中显示为 anonymous 或类似的通用名称
return data.processed;
};
最佳实践:
// 1. 优先使用函数声明,除非需要条件创建
function mainFunction() {
return "主要功能";
}
// 2. 回调函数使用函数表达式
setTimeout(function() {
console.log("延迟执行");
}, 1000);
// 3. 需要立即执行时使用函数表达式
(function initializeApp() {
console.log("应用初始化");
})();
// 4. 模块模式使用函数表达式
var Calculator = (function() {
var result = 0;
return {
add: function(value) {
result += value;
return this;
},
getResult: function() {
return result;
}
};
})();
实际应用:
What does the for...in loop do? What should be noted when using it?
What does the for…in loop do? What should be noted when using it?
考察点:对象遍历和原型链的基础理解。
答案:
for...in 循环用于遍历对象的可枚举属性,包括对象自身的属性和从原型链继承的属性。它是 ES5 中遍历对象属性的主要方式。
基本语法和用法:
var obj = {
name: "John",
age: 25,
city: "New York"
};
for (var key in obj) {
console.log(key + ": " + obj[key]);
}
// 输出:
// name: John
// age: 25
// city: New York
遍历数组(不推荐):
var array = ["a", "b", "c"];
array.customProperty = "自定义属性";
for (var index in array) {
console.log(index + ": " + array[index]);
}
// 输出:
// 0: a
// 1: b
// 2: c
// customProperty: 自定义属性 (意外的输出)
重要注意事项:
1. 会遍历原型链上的属性:
function Person(name) {
this.name = name;
}
Person.prototype.species = "Homo sapiens";
Person.prototype.greet = function() {
return "Hello, I'm " + this.name;
};
var john = new Person("John");
john.age = 25;
for (var key in john) {
console.log(key + ": " + john[key]);
}
// 输出:
// name: John
// age: 25
// species: Homo sapiens (来自原型)
// greet: function() {...} (来自原型)
2. 使用 hasOwnProperty 过滤自身属性:
function Person(name) {
this.name = name;
}
Person.prototype.species = "Homo sapiens";
var john = new Person("John");
john.age = 25;
// 只遍历自身属性
for (var key in john) {
if (john.hasOwnProperty(key)) {
console.log(key + ": " + john[key]);
}
}
// 输出:
// name: John
// age: 25
3. 遍历顺序不保证:
var obj = {};
obj[3] = "三";
obj[1] = "一";
obj[2] = "二";
obj["a"] = "字母a";
obj["b"] = "字母b";
for (var key in obj) {
console.log(key + ": " + obj[key]);
}
// 在不同的 JavaScript 引擎中,输出顺序可能不同
// 现代浏览器通常按照:数字键(升序) → 字符串键(定义顺序)
4. 不适合遍历数组:
var fruits = ["apple", "banana", "orange"];
fruits.extraProperty = "not a fruit";
// 不推荐:for...in 遍历数组
for (var index in fruits) {
console.log(fruits[index]);
}
// 输出包括:apple, banana, orange, not a fruit
// 推荐:使用传统 for 循环
for (var i = 0; i < fruits.length; i++) {
console.log(fruits[i]);
}
// 只输出:apple, banana, orange
安全的遍历模式:
// 基本对象属性遍历
function forInSafe(obj) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}
}
// 创建一个安全的遍历函数
function getOwnProperties(obj) {
var props = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
props.push(key);
}
}
return props;
}
var person = {name: "John", age: 25};
console.log(getOwnProperties(person)); // ["name", "age"]
实际应用场景:
1. 对象属性复制:
function shallowCopy(source) {
var target = {};
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
var original = {name: "John", age: 25};
var copy = shallowCopy(original);
2. 对象属性验证:
function validateObject(obj, requiredFields) {
for (var i = 0; i < requiredFields.length; i++) {
var field = requiredFields[i];
var found = false;
for (var key in obj) {
if (obj.hasOwnProperty(key) && key === field) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
3. 动态属性处理:
function processFormData(formData) {
var processedData = {};
for (var field in formData) {
if (formData.hasOwnProperty(field)) {
// 处理字符串字段
if (typeof formData[field] === 'string') {
processedData[field] = formData[field].trim();
} else {
processedData[field] = formData[field];
}
}
}
return processedData;
}
ES5 中的替代方法:
var obj = {name: "John", age: 25, city: "NYC"};
// 1. Object.keys() - 获取自身可枚举属性名数组
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
console.log(key + ": " + obj[key]);
}
// 2. 结合 forEach 使用
Object.keys(obj).forEach(function(key) {
console.log(key + ": " + obj[key]);
});
性能考虑:
var obj = {};
for (var i = 0; i < 1000; i++) {
obj["key" + i] = "value" + i;
}
// for...in 的性能测试
console.time("for...in with hasOwnProperty");
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
// 处理属性
}
}
console.timeEnd("for...in with hasOwnProperty");
// Object.keys 的性能测试
console.time("Object.keys");
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
// 处理属性
}
console.timeEnd("Object.keys");
最佳实践:
hasOwnProperty 检查属性是否为对象自身属性for...in 遍历数组,使用传统 for 循环或 forEachObject.keys() 获得更可控的遍历方式Are strings mutable in JavaScript?
考察点:对字符串特性的理解。
答案:
JavaScript 中的字符串是不可变的(immutable)。这意味着一旦创建了一个字符串,就不能修改它的内容。所有看起来"修改"字符串的操作实际上都是创建新的字符串。
字符串不可变性的证明:
var str = "Hello";
console.log(str[0]); // "H"
// 尝试修改字符串的某个字符
str[0] = "h";
console.log(str); // 仍然是 "Hello",没有被修改
// 严格模式下会抛出错误
"use strict";
var str2 = "World";
// str2[0] = "w"; // TypeError: Cannot assign to read only property '0'
字符串操作创建新字符串:
var original = "Hello";
var modified = original + " World";
console.log(original); // "Hello" (原字符串未变)
console.log(modified); // "Hello World" (新字符串)
// 字符串方法都返回新字符串
var text = "JavaScript";
var upperCase = text.toUpperCase();
var slice = text.slice(0, 4);
console.log(text); // "JavaScript" (原字符串不变)
console.log(upperCase); // "JAVASCRIPT" (新字符串)
console.log(slice); // "Java" (新字符串)
常见字符串方法的不可变性:
var str = "Hello World";
// 这些方法都返回新字符串,不修改原字符串
console.log(str.charAt(0)); // "H"
console.log(str.substring(0, 5)); // "Hello"
console.log(str.indexOf("World")); // 6
console.log(str.replace("World", "JS")); // "Hello JS"
console.log(str.split(" ")); // ["Hello", "World"]
console.log(str.toLowerCase()); // "hello world"
console.log(str.trim()); // "Hello World"
console.log(str); // 原字符串仍然是 "Hello World"
内存影响和性能考虑:
// 低效的字符串拼接(创建多个临时字符串)
function inefficientConcat() {
var result = "";
for (var i = 0; i < 1000; i++) {
result += "a"; // 每次都创建新字符串
}
return result;
}
// 更高效的方法:使用数组
function efficientConcat() {
var parts = [];
for (var i = 0; i < 1000; i++) {
parts.push("a");
}
return parts.join("");
}
console.time("inefficient");
inefficientConcat();
console.timeEnd("inefficient");
console.time("efficient");
efficientConcat();
console.timeEnd("efficient");
变量重新赋值 vs 字符串修改:
var str = "Hello";
// 这不是修改字符串,而是让变量指向新字符串
str = str + " World"; // 创建新字符串 "Hello World"
str = "Goodbye"; // 创建新字符串 "Goodbye"
// 原来的字符串 "Hello" 和 "Hello World" 变成垃圾,等待回收
字符串不可变性的实际应用:
1. 安全的字符串传递:
function processPassword(password) {
// 字符串不可变,所以原密码不会被意外修改
var processed = password.toLowerCase().trim();
// 一些处理逻辑
return processed;
}
var userPassword = "MySecret123";
var result = processPassword(userPassword);
console.log(userPassword); // 仍然是 "MySecret123"
2. 字符串作为对象键的可靠性:
var cache = {};
var key1 = "user_123";
var key2 = "user_123";
cache[key1] = "John";
// 字符串不可变性保证了键的一致性
console.log(cache[key2]); // "John" (能正确访问)
console.log(key1 === key2); // true (值相等)
3. 函数式编程的优势:
// 字符串操作是纯函数,没有副作用
function formatName(name) {
return name.trim().toLowerCase().replace(/\s+/g, '_');
}
var originalName = " John Doe ";
var formatted = formatName(originalName);
console.log(originalName); // " John Doe " (未被修改)
console.log(formatted); // "john_doe"
字符串构建的最佳实践:
// 少量拼接:使用 + 操作符
function simpleConcat(firstName, lastName) {
return firstName + " " + lastName;
}
// 大量拼接:使用数组 join
function buildHTML(items) {
var html = ["<ul>"];
for (var i = 0; i < items.length; i++) {
html.push("<li>" + items[i] + "</li>");
}
html.push("</ul>");
return html.join("");
}
// 模板构建
function createMessage(name, age, city) {
var parts = [
"Hello, my name is ",
name,
". I am ",
age,
" years old and I live in ",
city,
"."
];
return parts.join("");
}
与可变数据类型的对比:
// 字符串(不可变)
var str = "Hello";
var str2 = str;
str = str + " World"; // 创建新字符串
console.log(str2); // 仍然是 "Hello"
// 数组(可变)
var arr = [1, 2, 3];
var arr2 = arr;
arr.push(4); // 修改原数组
console.log(arr2); // [1, 2, 3, 4] (也被修改了)
调试和开发中的考虑:
// 字符串比较可以直接使用 ===
function compareStrings(str1, str2) {
return str1 === str2; // 直接比较值
}
// 字符串在调试时很安全
function debugLog(message) {
console.log("Debug: " + message);
// message 不会被意外修改
}
实际应用:
What are parseInt() and parseFloat()? What are their differences?
What are parseInt() and parseFloat()? What are their differences?
考察点:数据类型转换方法的掌握。
答案:
parseInt() 和 parseFloat() 是 JavaScript 中用于将字符串转换为数字的全局函数。它们在解析规则、返回值类型和使用场景上有重要区别。
基本语法和功能:
parseInt() - 解析整数:
// 基本语法:parseInt(string, radix)
console.log(parseInt("42")); // 42
console.log(parseInt("42.5")); // 42 (忽略小数部分)
console.log(parseInt("42abc")); // 42 (忽略后面的字符)
console.log(parseInt(" 42 ")); // 42 (忽略前后空白)
parseFloat() - 解析浮点数:
// 基本语法:parseFloat(string)
console.log(parseFloat("42.5")); // 42.5
console.log(parseFloat("42")); // 42
console.log(parseFloat("42.5abc")); // 42.5 (忽略后面的字符)
console.log(parseFloat(" 42.5 ")); // 42.5 (忽略前后空白)
主要区别:
1. 返回值类型:
console.log(parseInt("42")); // 42 (整数)
console.log(parseInt("42.9")); // 42 (截断小数部分)
console.log(parseFloat("42")); // 42 (数字)
console.log(parseFloat("42.9")); // 42.9 (保留小数)
// 类型检查
console.log(Number.isInteger(parseInt("42.9"))); // true
console.log(Number.isInteger(parseFloat("42.9"))); // false
2. 基数(radix)支持:
// parseInt 支持指定基数
console.log(parseInt("1010", 2)); // 10 (二进制转十进制)
console.log(parseInt("FF", 16)); // 255 (十六进制转十进制)
console.log(parseInt("77", 8)); // 63 (八进制转十进制)
// parseFloat 不支持基数,始终按十进制解析
console.log(parseFloat("1010")); // 1010
console.log(parseFloat("FF")); // NaN (不是有效的十进制数)
3. 解析规则差异:
// 科学记数法
console.log(parseInt("1e2")); // 1 (遇到 'e' 停止解析)
console.log(parseFloat("1e2")); // 100 (正确解析科学记数法)
console.log(parseFloat("1.5e2")); // 150
// 十六进制前缀
console.log(parseInt("0x10")); // 16 (识别十六进制前缀)
console.log(parseFloat("0x10")); // 0 (遇到 'x' 停止解析)
错误处理和边界情况:
// 无效输入
console.log(parseInt("")); // NaN
console.log(parseInt("abc")); // NaN
console.log(parseFloat("")); // NaN
console.log(parseFloat("abc")); // NaN
// 特殊值
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseFloat(null)); // NaN
console.log(parseFloat(undefined)); // NaN
// 数字类型输入
console.log(parseInt(42.9)); // 42
console.log(parseFloat(42.9)); // 42.9
parseInt 基数的重要性:
// 不指定基数的潜在问题(ES5 中)
console.log(parseInt("010")); // 可能是 8 或 10,取决于实现
console.log(parseInt("08")); // 可能是 0 或 8
// 总是指定基数(推荐)
console.log(parseInt("010", 10)); // 10 (明确十进制)
console.log(parseInt("010", 8)); // 8 (明确八进制)
console.log(parseInt("08", 10)); // 8 (明确十进制)
// 常见基数
console.log(parseInt("1010", 2)); // 10 (二进制)
console.log(parseInt("777", 8)); // 511 (八进制)
console.log(parseInt("FF", 16)); // 255 (十六进制)
实际应用场景:
parseInt 应用场景:
// 解析整数 ID
function getUserId(idString) {
var id = parseInt(idString, 10);
return isNaN(id) ? null : id;
}
// 分页参数处理
function getPageNumber(pageParam) {
var page = parseInt(pageParam, 10);
return page > 0 ? page : 1;
}
// CSS 像素值解析
function parsePixelValue(cssValue) {
// "20px" → 20
return parseInt(cssValue, 10);
}
console.log(parsePixelValue("20px")); // 20
console.log(parsePixelValue("1.5em")); // 1
parseFloat 应用场景:
// 价格解析
function parsePrice(priceString) {
var price = parseFloat(priceString);
return isNaN(price) ? 0 : price;
}
// 坐标解析
function parseCoordinate(coordString) {
return parseFloat(coordString);
}
// 科学记数法处理
function parseScientific(scientificString) {
return parseFloat(scientificString);
}
console.log(parsePrice("$19.99")); // 19.99
console.log(parseCoordinate("123.456")); // 123.456
console.log(parseScientific("1.5e3")); // 1500
与其他转换方法的比较:
var testStrings = ["42", "42.5", "42abc", " 42 ", "", "abc"];
testStrings.forEach(function(str) {
console.log("输入: '" + str + "'");
console.log("parseInt:", parseInt(str, 10));
console.log("parseFloat:", parseFloat(str));
console.log("Number:", Number(str));
console.log("+ 操作符:", +str);
console.log("---");
});
// 主要区别:
// parseInt/parseFloat: 部分解析,遇到无效字符停止
// Number/+: 全字符串必须是有效数字,否则返回 NaN
输入验证和安全使用:
// 安全的解析函数
function safeParseInt(value, defaultValue) {
if (typeof value !== 'string') {
value = String(value);
}
var result = parseInt(value, 10);
return isNaN(result) ? (defaultValue || 0) : result;
}
function safeParseFloat(value, defaultValue) {
if (typeof value !== 'string') {
value = String(value);
}
var result = parseFloat(value);
return isNaN(result) ? (defaultValue || 0) : result;
}
// 表单数据处理
function processFormData(formData) {
return {
age: safeParseInt(formData.age),
salary: safeParseFloat(formData.salary),
page: safeParseInt(formData.page, 1)
};
}
性能考虑:
// 性能测试示例
var testData = ["123", "123.456", "123abc", "1e3"];
var iterations = 100000;
console.time("parseInt");
for (var i = 0; i < iterations; i++) {
for (var j = 0; j < testData.length; j++) {
parseInt(testData[j], 10);
}
}
console.timeEnd("parseInt");
console.time("parseFloat");
for (var i = 0; i < iterations; i++) {
for (var j = 0; j < testData.length; j++) {
parseFloat(testData[j]);
}
}
console.timeEnd("parseFloat");
最佳实践:
// 1. 总是为 parseInt 指定基数
var num = parseInt(userInput, 10);
// 2. 验证解析结果
function parseAndValidate(input, type) {
var result;
if (type === 'int') {
result = parseInt(input, 10);
} else if (type === 'float') {
result = parseFloat(input);
}
if (isNaN(result)) {
throw new Error("无效的数字输入: " + input);
}
return result;
}
// 3. 处理边界情况
function robustParse(input, type, min, max) {
var result = type === 'int' ?
parseInt(input, 10) :
parseFloat(input);
if (isNaN(result)) return null;
if (typeof min === 'number' && result < min) return min;
if (typeof max === 'number' && result > max) return max;
return result;
}
实际应用:
What are global variables? What problems can arise from using global variables?
What are global variables? What problems can arise from using global variables?
考察点:变量作用域和代码质量意识。
答案:
全局变量是在全局作用域中声明的变量,可以在代码的任何地方访问。虽然使用方便,但会带来诸多问题,应该谨慎使用。
全局变量的定义方式:
// 1. 在全局作用域中使用 var 声明
var globalVar1 = "全局变量1";
// 2. 不使用 var 声明(隐式全局变量,不推荐)
globalVar2 = "全局变量2";
// 3. 作为 window 对象的属性(浏览器环境)
window.globalVar3 = "全局变量3";
// 4. 函数外部声明的变量
function someFunction() {
// 函数内部可以访问全局变量
console.log(globalVar1); // "全局变量1"
}
全局变量的问题:
1. 命名冲突(Name Collision):
// 库 A
var utils = {
format: function(str) {
return "A: " + str;
}
};
// 库 B(覆盖了库 A 的 utils)
var utils = {
format: function(str) {
return "B: " + str;
}
};
console.log(utils.format("test")); // "B: test" (库 A 的功能丢失)
// 第三方库可能意外覆盖全局变量
var $ = "我的变量";
// 引入 jQuery 后
// var $ = jQuery; // $ 被覆盖
2. 意外修改和副作用:
var counter = 0;
function incrementA() {
counter++; // 修改全局变量
}
function incrementB() {
counter += 2; // 也修改同一个全局变量
}
function displayCounter() {
console.log("Counter: " + counter);
}
incrementA(); // counter = 1
incrementB(); // counter = 3
displayCounter(); // "Counter: 3" (可能不是期望的结果)
// 在复杂应用中很难追踪谁修改了全局变量
3. 内存泄漏风险:
var largeData = []; // 全局变量
function processData() {
// 向全局数组添加大量数据
for (var i = 0; i < 100000; i++) {
largeData.push({id: i, data: "large data " + i});
}
}
// 全局变量不会被垃圾回收,即使不再需要
// largeData 会一直占用内存,直到页面关闭
4. 测试困难:
var appState = {
isLoggedIn: false,
currentUser: null
};
function login(user) {
appState.isLoggedIn = true;
appState.currentUser = user;
}
function logout() {
appState.isLoggedIn = false;
appState.currentUser = null;
}
// 测试困难:每个测试都需要重置全局状态
function testLogin() {
// 需要先重置全局状态
appState.isLoggedIn = false;
appState.currentUser = null;
login({name: "John"});
// 测试逻辑...
}
5. 代码耦合度高:
var currentTheme = "light";
function applyTheme() {
if (currentTheme === "dark") {
document.body.className = "dark-theme";
} else {
document.body.className = "light-theme";
}
}
function saveUserPreference(theme) {
currentTheme = theme; // 直接依赖全局变量
applyTheme();
}
// 所有函数都依赖 currentTheme 全局变量
// 修改 currentTheme 的结构会影响多个函数
避免全局变量的解决方案:
1. 使用命名空间模式:
// 创建一个全局对象来包含所有相关功能
var MyApp = MyApp || {};
MyApp.utils = {
format: function(str) {
return "Formatted: " + str;
},
validate: function(input) {
return input && input.length > 0;
}
};
MyApp.config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
// 使用时通过命名空间访问
console.log(MyApp.utils.format("test"));
2. 立即执行函数表达式(IIFE):
// 创建私有作用域,避免污染全局空间
(function() {
var privateVar = "私有变量";
function privateFunction() {
return privateVar;
}
// 只暴露需要的接口到全局
window.MyModule = {
getPrivateVar: function() {
return privateFunction();
}
};
})();
// 外部无法直接访问 privateVar 和 privateFunction
console.log(MyModule.getPrivateVar()); // "私有变量"
3. 模块模式:
var Calculator = (function() {
var result = 0; // 私有变量
function add(value) {
result += value;
return Calculator; // 支持链式调用
}
function subtract(value) {
result -= value;
return Calculator;
}
function getResult() {
return result;
}
function reset() {
result = 0;
return Calculator;
}
// 公开接口
return {
add: add,
subtract: subtract,
getResult: getResult,
reset: reset
};
})();
// 使用模块
Calculator.add(10).subtract(3).add(5);
console.log(Calculator.getResult()); // 12
4. 参数传递代替全局访问:
// 不好的方式:依赖全局变量
var config = {theme: "dark", language: "en"};
function renderPage() {
if (config.theme === "dark") {
// 渲染暗色主题
}
}
// 好的方式:通过参数传递
function renderPage(config) {
if (config.theme === "dark") {
// 渲染暗色主题
}
}
// 调用时传递配置
var appConfig = {theme: "dark", language: "en"};
renderPage(appConfig);
5. 使用闭包管理状态:
function createCounter(initialValue) {
var count = initialValue || 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
},
getValue: function() {
return count;
}
};
}
// 创建独立的计数器实例
var counter1 = createCounter(0);
var counter2 = createCounter(100);
console.log(counter1.increment()); // 1
console.log(counter2.increment()); // 101
// 两个计数器互不影响
检测和清理全局变量:
// 检测意外的全局变量
(function() {
var knownGlobals = ['window', 'document', 'console', 'setTimeout'];
for (var prop in window) {
if (window.hasOwnProperty(prop) && knownGlobals.indexOf(prop) === -1) {
console.warn("发现全局变量: " + prop);
}
}
})();
// 使用严格模式防止意外全局变量
(function() {
"use strict";
// myVar = "test"; // 这会抛出 ReferenceError
var myVar = "test"; // 正确的声明方式
})();
最佳实践:
var 声明变量"use strict" 防止意外全局变量How to create objects in JavaScript? What methods are available?
How to create objects in JavaScript? What methods are available?
考察点:对象创建方法的基础掌握。
答案:
JavaScript 中有多种创建对象的方式,每种方法都有其特点和适用场景。了解这些方法有助于选择最适合的对象创建模式。
1. 对象字面量(Object Literal):
// 最简单直接的方式
var person = {
name: "John",
age: 25,
greet: function() {
return "Hello, I'm " + this.name;
}
};
console.log(person.name); // "John"
console.log(person.greet()); // "Hello, I'm John"
// 动态添加属性
person.city = "New York";
person.sayAge = function() {
return "I'm " + this.age + " years old";
};
2. Object 构造函数:
// 使用 new Object()
var person = new Object();
person.name = "John";
person.age = 25;
person.greet = function() {
return "Hello, I'm " + this.name;
};
// 等价的简化写法
var person2 = {};
person2.name = "Jane";
person2.age = 30;
3. 自定义构造函数:
// 定义构造函数
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return "Hello, I'm " + this.name;
};
}
// 创建实例
var person1 = new Person("John", 25);
var person2 = new Person("Jane", 30);
console.log(person1.greet()); // "Hello, I'm John"
console.log(person2.greet()); // "Hello, I'm Jane"
// 检查实例类型
console.log(person1 instanceof Person); // true
4. 使用原型模式:
// 构造函数 + 原型
function Person(name, age) {
this.name = name;
this.age = age;
}
// 在原型上定义方法(所有实例共享)
Person.prototype.greet = function() {
return "Hello, I'm " + this.name;
};
Person.prototype.species = "Homo sapiens";
var person1 = new Person("John", 25);
var person2 = new Person("Jane", 30);
// 实例共享原型上的方法和属性
console.log(person1.greet === person2.greet); // true (同一个函数)
console.log(person1.species); // "Homo sapiens"
5. Object.create() 方法(ES5):
// 基于已有对象创建新对象
var personPrototype = {
greet: function() {
return "Hello, I'm " + this.name;
},
setAge: function(age) {
this.age = age;
}
};
// 创建继承自 personPrototype 的对象
var person = Object.create(personPrototype);
person.name = "John";
person.age = 25;
console.log(person.greet()); // "Hello, I'm John"
// 可以指定属性描述符
var person2 = Object.create(personPrototype, {
name: {
value: "Jane",
writable: true,
enumerable: true
},
age: {
value: 30,
writable: true,
enumerable: true
}
});
6. 工厂模式:
// 工厂函数创建对象
function createPerson(name, age, job) {
var person = {};
person.name = name;
person.age = age;
person.job = job;
person.greet = function() {
return "Hello, I'm " + this.name + ", I'm a " + this.job;
};
return person;
}
var person1 = createPerson("John", 25, "Developer");
var person2 = createPerson("Jane", 30, "Designer");
console.log(person1.greet()); // "Hello, I'm John, I'm a Developer"
// 注意:工厂模式创建的对象没有特定的类型标识
console.log(person1 instanceof Object); // true
// console.log(person1 instanceof Person); // 无法判断具体类型
各种方法的比较:
// 性能比较示例
function performanceTest() {
var iterations = 100000;
var start, end;
// 1. 对象字面量
start = Date.now();
for (var i = 0; i < iterations; i++) {
var obj1 = {name: "John", age: 25};
}
end = Date.now();
console.log("对象字面量: " + (end - start) + "ms");
// 2. new Object()
start = Date.now();
for (var i = 0; i < iterations; i++) {
var obj2 = new Object();
obj2.name = "John";
obj2.age = 25;
}
end = Date.now();
console.log("new Object(): " + (end - start) + "ms");
// 3. 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
start = Date.now();
for (var i = 0; i < iterations; i++) {
var obj3 = new Person("John", 25);
}
end = Date.now();
console.log("构造函数: " + (end - start) + "ms");
}
混合使用模式:
// 构造函数 + 原型的混合模式(推荐)
function Person(name, age) {
// 实例属性
this.name = name;
this.age = age;
this.friends = []; // 每个实例有独立的数组
}
// 共享方法
Person.prototype.greet = function() {
return "Hello, I'm " + this.name;
};
Person.prototype.addFriend = function(friend) {
this.friends.push(friend);
};
// 静态属性
Person.species = "Homo sapiens";
var person1 = new Person("John", 25);
var person2 = new Person("Jane", 30);
person1.addFriend("Alice");
person2.addFriend("Bob");
console.log(person1.friends); // ["Alice"]
console.log(person2.friends); // ["Bob"] (独立的数组)
实际应用场景:
简单数据对象 - 使用字面量:
var config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3
};
var userInfo = {
id: 123,
name: "John",
email: "[email protected]"
};
需要多个相似实例 - 使用构造函数:
function Task(title, priority) {
this.title = title;
this.priority = priority || "normal";
this.completed = false;
this.createdAt = new Date();
}
Task.prototype.complete = function() {
this.completed = true;
this.completedAt = new Date();
};
Task.prototype.getStatus = function() {
return this.completed ? "完成" : "进行中";
};
var task1 = new Task("学习 JavaScript", "high");
var task2 = new Task("写文档", "low");
继承和原型链 - 使用 Object.create:
// 基类
var Animal = {
init: function(name) {
this.name = name;
},
makeSound: function() {
return "Some sound";
}
};
// 继承
var Dog = Object.create(Animal);
Dog.makeSound = function() {
return "Woof!";
};
Dog.wagTail = function() {
return this.name + " wags tail";
};
var myDog = Object.create(Dog);
myDog.init("Buddy");
console.log(myDog.makeSound()); // "Woof!"
console.log(myDog.wagTail()); // "Buddy wags tail"
选择指南:
最佳实践:
Please explain how the ‘this’ keyword behaves in different scenarios.
考察点:JavaScript 中最核心也最复杂的概念之一。
答案:
this 关键字的值取决于函数的调用方式,而不是函数定义的位置。理解 this 的绑定规则对于掌握 JavaScript 至关重要。
this 绑定的四种规则:
1. 默认绑定(Default Binding):
function globalFunction() {
console.log(this); // 全局对象 (浏览器中是 window)
}
globalFunction(); // this 指向全局对象
// 严格模式下的差异
function strictFunction() {
"use strict";
console.log(this); // undefined
}
strictFunction(); // 严格模式下 this 是 undefined
2. 隐式绑定(Implicit Binding):
var obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
console.log(this); // obj 对象
}
};
obj.greet(); // this 指向 obj,输出 "Hello, John"
// 多层对象嵌套
var person = {
name: "Alice",
address: {
city: "New York",
getCity: function() {
console.log(this.city); // "New York"
console.log(this); // address 对象
}
}
};
person.address.getCity(); // this 指向 address 对象
3. 显式绑定(Explicit Binding):
function introduce() {
console.log("My name is " + this.name);
}
var person1 = {name: "John"};
var person2 = {name: "Jane"};
// 使用 call
introduce.call(person1); // "My name is John"
introduce.call(person2); // "My name is Jane"
// 使用 apply
introduce.apply(person1); // "My name is John"
// 使用 bind
var boundIntroduce = introduce.bind(person1);
boundIntroduce(); // "My name is John"
4. new 绑定(new Binding):
function Person(name) {
this.name = name;
this.greet = function() {
console.log("Hello, I'm " + this.name);
};
console.log(this); // 新创建的对象实例
}
var john = new Person("John");
john.greet(); // this 指向 john 实例
常见的 this 指向场景:
函数作为对象方法调用:
var calculator = {
result: 0,
add: function(value) {
this.result += value; // this 指向 calculator
return this;
},
multiply: function(value) {
this.result *= value; // this 指向 calculator
return this;
},
getResult: function() {
return this.result; // this 指向 calculator
}
};
calculator.add(5).multiply(2); // 链式调用
console.log(calculator.getResult()); // 10
函数赋值后调用(this 丢失):
var obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name);
}
};
obj.greet(); // "Hello, John" (this 是 obj)
// 将方法赋值给变量
var greetFunc = obj.greet;
greetFunc(); // "Hello, undefined" (this 是全局对象)
// 解决方案:使用 bind
var boundGreet = obj.greet.bind(obj);
boundGreet(); // "Hello, John"
事件处理中的 this:
var button = document.getElementById("myButton");
var handler = {
message: "Button clicked!",
handleClick: function() {
console.log(this.message); // 在事件处理中,this 通常指向触发事件的元素
console.log(this); // button 元素
}
};
// 直接绑定会丢失原始的 this
button.addEventListener('click', handler.handleClick); // this 指向 button
// 使用 bind 保持 this 指向
button.addEventListener('click', handler.handleClick.bind(handler)); // this 指向 handler
回调函数中的 this:
function Timer() {
this.seconds = 0;
// 错误方式:this 会丢失
setInterval(function() {
this.seconds++; // this 指向全局对象
console.log(this.seconds); // undefined 或报错
}, 1000);
}
function TimerCorrect() {
var self = this; // 保存 this 引用
this.seconds = 0;
// 方式一:使用变量保存 this
setInterval(function() {
self.seconds++;
console.log(self.seconds);
}, 1000);
}
function TimerBind() {
this.seconds = 0;
// 方式二:使用 bind
setInterval(function() {
this.seconds++;
console.log(this.seconds);
}.bind(this), 1000);
}
箭头函数中的 this(注:ES6 特性,但需要了解):
// 注意:ES5 中没有箭头函数,这里仅作对比说明
var obj = {
name: "John",
// 普通函数
regularMethod: function() {
console.log(this.name); // "John"
setTimeout(function() {
console.log(this.name); // undefined (this 丢失)
}, 1000);
},
// 在 ES5 中的解决方案
es5Solution: function() {
var self = this;
console.log(this.name); // "John"
setTimeout(function() {
console.log(self.name); // "John" (使用保存的引用)
}, 1000);
}
};
call、apply、bind 的详细用法:
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
var person = {name: "John"};
// call:逐个传递参数
greet.call(person, "Hello", "!"); // "Hello, John!"
// apply:参数以数组形式传递
greet.apply(person, ["Hi", "."]); // "Hi, John."
// bind:创建绑定函数,稍后调用
var boundGreet = greet.bind(person, "Hey");
boundGreet("!!!"); // "Hey, John!!!"
复杂场景中的 this:
var module = {
x: 42,
getX: function() {
return this.x;
}
};
var unboundGetX = module.getX;
console.log(unboundGetX()); // undefined (this 指向全局)
var boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42
// 嵌套函数中的 this
var obj = {
name: "outer",
outerMethod: function() {
console.log(this.name); // "outer"
function innerFunction() {
console.log(this.name); // undefined (内部函数的 this 指向全局)
}
innerFunction();
// 修正方法
var self = this;
function correctedInner() {
console.log(self.name); // "outer"
}
correctedInner();
}
};
this 绑定的优先级:
function test() {
console.log(this.name);
}
var obj1 = {name: "obj1", test: test};
var obj2 = {name: "obj2"};
// 1. new 绑定优先级最高
function Test(name) {
this.name = name;
}
var boundTest = Test.bind(obj1);
var instance = new boundTest("new binding"); // this 指向新实例
console.log(instance.name); // "new binding"
// 2. 显式绑定优先于隐式绑定
obj1.test.call(obj2); // "obj2" (call 覆盖了隐式绑定)
// 3. 隐式绑定优先于默认绑定
obj1.test(); // "obj1" (隐式绑定)
实际应用和最佳实践:
// 创建一个安全的方法绑定工具
function createBoundMethod(obj, methodName) {
return function() {
return obj[methodName].apply(obj, arguments);
};
}
var calculator = {
result: 0,
add: function(value) {
this.result += value;
return this.result;
}
};
var safeAdd = createBoundMethod(calculator, 'add');
console.log(safeAdd(5)); // 5
// 链式调用模式
var ChainableObject = function() {
this.value = 0;
};
ChainableObject.prototype.add = function(num) {
this.value += num;
return this; // 返回 this 支持链式调用
};
ChainableObject.prototype.multiply = function(num) {
this.value *= num;
return this;
};
var chain = new ChainableObject();
chain.add(5).multiply(2).add(3);
console.log(chain.value); // 13
调试 this 的技巧:
// 添加调试辅助函数
function debugThis(context) {
console.log("this 指向:", this);
console.log("预期上下文:", context);
console.log("this === context:", this === context);
}
// 在方法中使用
var obj = {
name: "test",
method: function() {
debugThis.call(this, obj);
}
};
最佳实践:
var self = this 模式What is a closure? What are its use cases? What are its drawbacks?
What is a closure? What are its use cases? What are its drawbacks?
考察点:对词法作用域、内存管理和高级函数应用的理解。
答案:
闭包是指一个函数可以访问其定义时所在的词法作用域,即使该函数在其词法作用域之外被调用。闭包让函数能够"记住"并访问其外部作用域的变量。
闭包的基本原理:
function outerFunction(x) {
// 外部函数的变量
var outerVariable = x;
// 内部函数形成闭包
function innerFunction(y) {
// 可以访问外部函数的变量
return outerVariable + y;
}
return innerFunction;
}
var closure = outerFunction(10);
console.log(closure(5)); // 15
// outerFunction 已执行完毕,但 innerFunction 仍能访问 outerVariable
闭包的工作机制:
function createCounter() {
var count = 0; // 私有变量
return function() {
count++; // 访问外部变量
return count;
};
}
var counter1 = createCounter();
var counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (独立的闭包环境)
console.log(counter1()); // 3
闭包的应用场景:
1. 数据私有化和封装:
function BankAccount(initialBalance) {
var balance = initialBalance; // 私有变量
return {
deposit: function(amount) {
if (amount > 0) {
balance += amount;
return balance;
}
throw new Error("存款金额必须大于0");
},
withdraw: function(amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return balance;
}
throw new Error("取款金额无效");
},
getBalance: function() {
return balance;
}
};
// balance 变量无法从外部直接访问
}
var account = BankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500); // 1500
account.withdraw(200); // 1300
// console.log(account.balance); // undefined (无法直接访问)
2. 函数工厂模式:
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
var double = createMultiplier(2);
var triple = createMultiplier(3);
var square = createMultiplier(4);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(square(5)); // 20
// 创建特定功能的函数
function createGreeter(greeting) {
return function(name) {
return greeting + ", " + name + "!";
};
}
var sayHello = createGreeter("Hello");
var sayGoodbye = createGreeter("Goodbye");
console.log(sayHello("John")); // "Hello, John!"
console.log(sayGoodbye("Jane")); // "Goodbye, Jane!"
3. 模块模式:
var Calculator = (function() {
var result = 0; // 私有状态
var history = []; // 私有历史记录
function addToHistory(operation, value, newResult) {
history.push({
operation: operation,
value: value,
result: newResult,
timestamp: new Date()
});
}
return {
add: function(value) {
result += value;
addToHistory('add', value, result);
return this;
},
subtract: function(value) {
result -= value;
addToHistory('subtract', value, result);
return this;
},
multiply: function(value) {
result *= value;
addToHistory('multiply', value, result);
return this;
},
getResult: function() {
return result;
},
getHistory: function() {
return history.slice(); // 返回副本,防止外部修改
},
clear: function() {
result = 0;
history = [];
return this;
}
};
})();
Calculator.add(10).multiply(2).subtract(5);
console.log(Calculator.getResult()); // 15
console.log(Calculator.getHistory()); // 操作历史
4. 事件处理和回调:
function setupEventHandlers() {
var clickCount = 0;
document.getElementById('button').addEventListener('click', function() {
clickCount++;
console.log('按钮被点击了 ' + clickCount + ' 次');
});
// 返回一个函数来获取点击次数
return function() {
return clickCount;
};
}
var getClickCount = setupEventHandlers();
// 防抖函数实现
function debounce(func, delay) {
var timeoutId;
return function() {
var context = this;
var args = arguments;
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}
var debouncedSearch = debounce(function(query) {
console.log('搜索: ' + query);
}, 300);
5. 循环中的闭包应用:
// 常见问题:循环中的异步操作
function createButtons() {
var buttons = [];
for (var i = 0; i < 3; i++) {
// 错误方式:所有按钮都会显示 3
buttons[i] = function() {
console.log('按钮 ' + i + ' 被点击'); // i 始终是 3
};
}
return buttons;
}
// 解决方案1:使用闭包
function createButtonsCorrect() {
var buttons = [];
for (var i = 0; i < 3; i++) {
buttons[i] = (function(index) {
return function() {
console.log('按钮 ' + index + ' 被点击');
};
})(i); // 立即执行,传入当前的 i 值
}
return buttons;
}
// 解决方案2:使用 bind
function createButtonsBind() {
var buttons = [];
function clickHandler(index) {
console.log('按钮 ' + index + ' 被点击');
}
for (var i = 0; i < 3; i++) {
buttons[i] = clickHandler.bind(null, i);
}
return buttons;
}
闭包的缺点和问题:
1. 内存泄漏风险:
// 可能导致内存泄漏的例子
function problemmaticClosure() {
var largeData = new Array(1000000).fill('data'); // 大量数据
var element = document.getElementById('someElement');
element.onclick = function() {
// 这个函数持有对 largeData 的引用
console.log('clicked');
};
return function() {
// 即使这个返回函数不使用 largeData
// largeData 仍然不会被垃圾回收
return "some result";
};
}
// 改进版本
function improvedClosure() {
var element = document.getElementById('someElement');
element.onclick = function() {
console.log('clicked');
};
// 不引用不必要的变量
return function() {
return "some result";
};
}
2. 性能影响:
// 性能测试:闭包 vs 普通函数
function normalFunction(a, b) {
return a + b;
}
function createClosureFunction() {
var cache = {};
return function(a, b) {
var key = a + ',' + b;
if (!(key in cache)) {
cache[key] = a + b;
}
return cache[key];
};
}
var closureFunction = createClosureFunction();
// 性能测试
console.time('normal function');
for (var i = 0; i < 1000000; i++) {
normalFunction(i, i + 1);
}
console.timeEnd('normal function');
console.time('closure function');
for (var i = 0; i < 1000000; i++) {
closureFunction(i, i + 1);
}
console.timeEnd('closure function');
3. 调试困难:
function complexClosure() {
var privateVar1 = "value1";
var privateVar2 = "value2";
function innerFunction1() {
var localVar = "local";
return function() {
// 在调试时很难追踪这些变量的值
return privateVar1 + localVar;
};
}
function innerFunction2() {
return function() {
return privateVar2;
};
}
return {
method1: innerFunction1(),
method2: innerFunction2()
};
}
闭包的最佳实践:
// 1. 避免不必要的闭包
function unnecessary() {
var x = 10;
// 不好:创建了不必要的闭包
return function(y) {
return y * 2; // 没有使用外部变量 x
};
}
// 好:直接返回函数
function better() {
return function(y) {
return y * 2;
};
}
// 2. 及时清理引用
function properCleanup() {
var element = document.getElementById('button');
var data = {/* 一些数据 */};
function clickHandler() {
// 使用 data
console.log(data);
}
element.addEventListener('click', clickHandler);
// 返回清理函数
return function cleanup() {
element.removeEventListener('click', clickHandler);
element = null;
data = null;
};
}
// 3. 使用弱引用模式
function createManager() {
var items = [];
return {
add: function(item) {
items.push(item);
},
clear: function() {
items = []; // 清理引用
},
// 提供安全的访问方式
getItems: function() {
return items.slice(); // 返回副本
}
};
}
实际应用示例:
// 配置管理器
function createConfig() {
var config = {};
var locked = false;
return {
set: function(key, value) {
if (locked) {
throw new Error("配置已锁定,无法修改");
}
config[key] = value;
},
get: function(key) {
return config[key];
},
lock: function() {
locked = true;
},
isLocked: function() {
return locked;
}
};
}
// 缓存装饰器
function memoize(fn) {
var cache = {};
return function() {
var key = JSON.stringify(arguments);
if (!(key in cache)) {
cache[key] = fn.apply(this, arguments);
}
return cache[key];
};
}
var expensiveFunction = memoize(function(n) {
console.log('计算中...');
return n * n;
});
console.log(expensiveFunction(5)); // 计算中... 25
console.log(expensiveFunction(5)); // 25 (从缓存获取)
实际应用:
Please explain prototype and prototype chain.
考察点:对 JavaScript 面向对象核心——原型继承的理解。
答案:
原型和原型链是 JavaScript 实现继承和对象属性共享的核心机制。每个 JavaScript 对象都有一个内部链接指向另一个对象,这个对象就是原型。
原型的基本概念:
// 每个函数都有一个 prototype 属性
function Person(name) {
this.name = name;
}
// 在原型上添加方法
Person.prototype.greet = function() {
return "Hello, I'm " + this.name;
};
Person.prototype.species = "Homo sapiens";
// 创建实例
var john = new Person("John");
var jane = new Person("Jane");
// 实例可以访问原型上的属性和方法
console.log(john.greet()); // "Hello, I'm John"
console.log(john.species); // "Homo sapiens"
console.log(jane.greet()); // "Hello, I'm Jane"
// 所有实例共享原型上的方法
console.log(john.greet === jane.greet); // true
原型链的工作原理:
// 原型链查找过程
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return this.name + " is eating";
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父构造函数
this.breed = breed;
}
// 建立原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
return this.name + " is barking";
};
var buddy = new Dog("Buddy", "Golden Retriever");
// 属性查找顺序:
// 1. buddy 实例本身
// 2. Dog.prototype
// 3. Animal.prototype
// 4. Object.prototype
console.log(buddy.name); // "Buddy" (实例属性)
console.log(buddy.bark()); // "Buddy is barking" (Dog.prototype)
console.log(buddy.eat()); // "Buddy is eating" (Animal.prototype)
console.log(buddy.toString()); // "[object Object]" (Object.prototype)
prototype vs proto:
function Person(name) {
this.name = name;
}
Person.prototype.greet = function() {
return "Hello!";
};
var john = new Person("John");
// prototype:函数的属性,指向原型对象
console.log(Person.prototype); // {greet: function, constructor: Person}
// __proto__:实例的内部属性,指向构造函数的原型
console.log(john.__proto__ === Person.prototype); // true
// constructor:原型对象指向构造函数
console.log(Person.prototype.constructor === Person); // true
console.log(john.constructor === Person); // true (通过原型链查找)
原型链的详细结构:
function grandParent() {}
grandParent.prototype.grandMethod = function() {
return "grand method";
};
function Parent() {}
Parent.prototype = Object.create(grandParent.prototype);
Parent.prototype.constructor = Parent;
Parent.prototype.parentMethod = function() {
return "parent method";
};
function Child() {}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.childMethod = function() {
return "child method";
};
var instance = new Child();
// 完整的原型链
console.log(instance.__proto__ === Child.prototype); // true
console.log(instance.__proto__.__proto__ === Parent.prototype); // true
console.log(instance.__proto__.__proto__.__proto__ === grandParent.prototype); // true
console.log(instance.__proto__.__proto__.__proto__.__proto__ === Object.prototype); // true
console.log(instance.__proto__.__proto__.__proto__.__proto__.__proto__ === null); // true
// 方法调用
console.log(instance.childMethod()); // "child method"
console.log(instance.parentMethod()); // "parent method"
console.log(instance.grandMethod()); // "grand method"
属性查找和遮蔽:
function Person(name) {
this.name = name;
}
Person.prototype.name = "Default Name";
Person.prototype.greet = function() {
return "Hello, " + this.name;
};
var john = new Person("John");
// 属性遮蔽
console.log(john.name); // "John" (实例属性遮蔽原型属性)
delete john.name;
console.log(john.name); // "Default Name" (实例属性被删除,访问原型属性)
// 方法遮蔽
john.greet = function() {
return "Hi, " + this.name;
};
console.log(john.greet()); // "Hi, Default Name" (实例方法遮蔽原型方法)
hasOwnProperty 和 in 操作符:
function Person(name) {
this.name = name;
}
Person.prototype.species = "Human";
var john = new Person("John");
// hasOwnProperty:检查自身属性
console.log(john.hasOwnProperty('name')); // true (实例属性)
console.log(john.hasOwnProperty('species')); // false (原型属性)
// in 操作符:检查整个原型链
console.log('name' in john); // true
console.log('species' in john); // true
console.log('toString' in john); // true (Object.prototype)
// 判断属性是否仅在原型中
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
console.log(hasPrototypeProperty(john, 'species')); // true
原型的动态性:
function Person() {}
var john = new Person();
var jane = new Person();
// 动态添加原型方法
Person.prototype.greet = function() {
return "Hello!";
};
// 已存在的实例也能访问新添加的方法
console.log(john.greet()); // "Hello!"
console.log(jane.greet()); // "Hello!"
// 但重写整个原型对象不会影响已创建的实例
Person.prototype = {
constructor: Person,
newMethod: function() {
return "New method";
}
};
var bob = new Person();
console.log(bob.newMethod()); // "New method"
// console.log(john.newMethod()); // TypeError: john.newMethod is not a function
原型继承的实现模式:
1. 原型链继承:
function Animal(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Animal.prototype.getName = function() {
return this.name;
};
function Dog(name, age) {
this.age = age;
}
Dog.prototype = new Animal(); // 继承
var dog1 = new Dog("Buddy", 3);
var dog2 = new Dog("Charlie", 5);
// 问题:共享引用类型属性
dog1.colors.push('green');
console.log(dog2.colors); // ['red', 'blue', 'green'] (被意外修改)
2. 借用构造函数:
function Animal(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function Dog(name, age) {
Animal.call(this, name); // 借用构造函数
this.age = age;
}
var dog1 = new Dog("Buddy", 3);
var dog2 = new Dog("Charlie", 5);
dog1.colors.push('green');
console.log(dog1.colors); // ['red', 'blue', 'green']
console.log(dog2.colors); // ['red', 'blue'] (不受影响)
// 问题:无法继承原型方法
3. 组合继承(推荐):
function Animal(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Animal.prototype.getName = function() {
return this.name;
};
function Dog(name, age) {
Animal.call(this, name); // 继承实例属性
this.age = age;
}
Dog.prototype = Object.create(Animal.prototype); // 继承原型方法
Dog.prototype.constructor = Dog;
Dog.prototype.getAge = function() {
return this.age;
};
var dog1 = new Dog("Buddy", 3);
console.log(dog1.getName()); // "Buddy" (继承的方法)
console.log(dog1.getAge()); // 3 (自己的方法)
原型链的实际应用:
1. 扩展内置对象(谨慎使用):
// 为 Array 添加自定义方法
Array.prototype.last = function() {
return this[this.length - 1];
};
var arr = [1, 2, 3, 4, 5];
console.log(arr.last()); // 5
// 为 String 添加方法
String.prototype.reverse = function() {
return this.split('').reverse().join('');
};
console.log("hello".reverse()); // "olleh"
// 注意:修改内置原型可能导致冲突
2. 插件和库的实现:
// 创建一个简单的 DOM 操作库
function $(selector) {
return new $.fn.init(selector);
}
$.fn = $.prototype = {
constructor: $,
init: function(selector) {
this.elements = document.querySelectorAll(selector);
this.length = this.elements.length;
return this;
},
addClass: function(className) {
for (var i = 0; i < this.length; i++) {
this.elements[i].classList.add(className);
}
return this; // 链式调用
},
removeClass: function(className) {
for (var i = 0; i < this.length; i++) {
this.elements[i].classList.remove(className);
}
return this;
}
};
$.fn.init.prototype = $.fn;
// 使用
$('.my-class').addClass('active').removeClass('inactive');
3. 类型检测和原型验证:
// 检测对象的原型链
function isInstanceOf(obj, constructor) {
var prototype = constructor.prototype;
var objProto = obj.__proto__;
while (objProto !== null) {
if (objProto === prototype) {
return true;
}
objProto = objProto.__proto__;
}
return false;
}
function Animal() {}
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
var dog = new Dog();
console.log(isInstanceOf(dog, Dog)); // true
console.log(isInstanceOf(dog, Animal)); // true
console.log(isInstanceOf(dog, Object)); // true
原型链性能优化:
// 避免深层原型链
function createShallowInheritance(parent, child) {
// 直接复制父原型的方法到子原型
for (var key in parent.prototype) {
if (parent.prototype.hasOwnProperty(key)) {
child.prototype[key] = parent.prototype[key];
}
}
}
// 缓存原型链查找结果
function createMethodCache(obj) {
var cache = {};
return function(methodName) {
if (!(methodName in cache)) {
cache[methodName] = obj[methodName];
}
return cache[methodName];
};
}
调试原型链:
// 原型链调试工具
function getPrototypeChain(obj) {
var chain = [];
var current = obj;
while (current !== null) {
chain.push({
object: current,
constructor: current.constructor ? current.constructor.name : 'Unknown',
isPrototype: current !== obj
});
current = Object.getPrototypeOf(current);
}
return chain;
}
function Person(name) {
this.name = name;
}
var john = new Person("John");
console.log(getPrototypeChain(john));
// 输出完整的原型链信息
最佳实践:
How to implement a bind function?
How to implement a bind function?
考察点:综合运用 this、apply、闭包和高阶函数等知识,代码实现能力。
答案:
bind 函数创建一个新函数,当调用时将其 this 关键字设置为提供的值,并在调用新函数时提供给定的参数序列。实现 bind 需要理解闭包、apply/call 和函数构造等概念。
基础版本的 bind 实现:
// 简单版本的 bind 实现
Function.prototype.myBind = function(context) {
var self = this; // 保存原函数
return function() {
return self.apply(context, arguments);
};
};
// 测试基础版本
function greet() {
return "Hello, " + this.name;
}
var person = {name: "John"};
var boundGreet = greet.myBind(person);
console.log(boundGreet()); // "Hello, John"
支持预设参数的 bind 实现:
Function.prototype.myBind = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1); // 提取预设参数
return function() {
// 合并预设参数和调用时参数
var bindArgs = args.concat(Array.prototype.slice.call(arguments));
return self.apply(context, bindArgs);
};
};
// 测试参数预设
function add(a, b, c) {
return a + b + c;
}
var addFive = add.myBind(null, 5); // 预设第一个参数为 5
console.log(addFive(2, 3)); // 10 (5 + 2 + 3)
var addFiveTen = add.myBind(null, 5, 10); // 预设前两个参数
console.log(addFiveTen(3)); // 18 (5 + 10 + 3)
完整版本:支持构造函数调用:
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
// 创建一个中间函数
var fNOP = function() {};
var fBound = function() {
var bindArgs = args.concat(Array.prototype.slice.call(arguments));
// 检查是否通过 new 调用
// 如果是通过 new 调用,this 应该指向新创建的实例
return self.apply(
this instanceof fNOP ? this : context,
bindArgs
);
};
// 维护原型链
if (this.prototype) {
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
// 测试构造函数调用
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return "Hello, I'm " + this.name;
};
var BoundPerson = Person.myBind(null, "John");
var john = new BoundPerson(25);
console.log(john.name); // "John"
console.log(john.age); // 25
console.log(john.greet()); // "Hello, I'm John"
console.log(john instanceof Person); // true
优化版本:使用 ES5 的 Object.create:
Function.prototype.myBind = function(context) {
if (typeof this !== "function") {
throw new TypeError("Function.prototype.bind called on incompatible " + typeof this);
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var bound = function() {
var bindArgs = args.concat(Array.prototype.slice.call(arguments));
if (this instanceof bound) {
// 通过 new 调用
var result = self.apply(this, bindArgs);
return (typeof result === "object" && result !== null) ? result : this;
} else {
// 普通调用
return self.apply(context, bindArgs);
}
};
// 维护原型链
if (self.prototype) {
bound.prototype = Object.create(self.prototype);
}
return bound;
};
处理边界情况的完整实现:
Function.prototype.myBind = function(context) {
// 类型检查
if (typeof this !== "function") {
throw new TypeError(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var bound = function() {
var bindArgs = args.concat(Array.prototype.slice.call(arguments));
if (this instanceof bound) {
// 构造函数调用
var result = self.apply(this, bindArgs);
// 如果构造函数返回对象,使用该对象;否则使用 this
if (Object(result) === result) {
return result;
}
return this;
}
// 普通函数调用
return self.apply(context, bindArgs);
};
// 原型链处理
var Empty = function() {};
if (self.prototype) {
Empty.prototype = self.prototype;
bound.prototype = new Empty();
Empty.prototype = null;
}
return bound;
};
// 测试边界情况
function TestConstructor(value) {
this.value = value;
// 构造函数可能返回对象
if (value === "return object") {
return {special: true};
}
}
var BoundTest = TestConstructor.myBind(null, "test");
var instance1 = new BoundTest();
console.log(instance1.value); // "test"
var BoundTestObject = TestConstructor.myBind(null, "return object");
var instance2 = new BoundTestObject();
console.log(instance2.special); // true
bind 的实际应用场景:
1. 事件处理中保持 this:
function Button(element) {
this.element = element;
this.clickCount = 0;
// 错误方式:this 会丢失
// this.element.addEventListener('click', this.handleClick);
// 正确方式:使用 bind 保持 this
this.element.addEventListener('click', this.handleClick.bind(this));
}
Button.prototype.handleClick = function(event) {
this.clickCount++;
console.log('按钮被点击了 ' + this.clickCount + ' 次');
};
var button = new Button(document.getElementById('myButton'));
2. 部分应用(Partial Application):
function multiply(a, b, c) {
return a * b * c;
}
// 创建特定的函数
var double = multiply.bind(null, 2); // 固定第一个参数为 2
var doubleByFive = multiply.bind(null, 2, 5); // 固定前两个参数
console.log(double(3, 4)); // 24 (2 * 3 * 4)
console.log(doubleByFive(6)); // 60 (2 * 5 * 6)
// 实用工具函数
var log = console.log.bind(console);
var warn = console.warn.bind(console);
var error = console.error.bind(console);
log("This is a log message");
3. 函数式编程中的应用:
var users = [
{name: "John", age: 25},
{name: "Jane", age: 30},
{name: "Bob", age: 35}
];
// 使用 bind 创建专用的映射函数
var getName = function(user) {
return user.name;
};
var getAge = function(user) {
return user.age;
};
var names = users.map(getName);
var ages = users.map(getAge);
// 使用 bind 创建比较函数
function compare(property, a, b) {
if (a[property] < b[property]) return -1;
if (a[property] > b[property]) return 1;
return 0;
}
var sortByAge = compare.bind(null, 'age');
var sortByName = compare.bind(null, 'name');
users.sort(sortByAge);
console.log(users); // 按年龄排序
4. 模块模式中的方法绑定:
var Calculator = (function() {
var result = 0;
function add(value) {
result += value;
return this;
}
function multiply(value) {
result *= value;
return this;
}
function getResult() {
return result;
}
function reset() {
result = 0;
return this;
}
// 返回绑定了正确 this 的方法
return {
add: add.bind(Calculator),
multiply: multiply.bind(Calculator),
getResult: getResult,
reset: reset.bind(Calculator)
};
})();
Calculator.add(5).multiply(2);
console.log(Calculator.getResult()); // 10
性能优化版本:
// 针对不需要构造函数功能的简化版本
Function.prototype.simpleBind = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
return function() {
return self.apply(
context,
args.concat(Array.prototype.slice.call(arguments))
);
};
};
// 带缓存的 bind 实现
Function.prototype.cachedBind = function(context) {
var self = this;
var cache = self._bindCache = self._bindCache || {};
var key = context || 'null';
if (cache[key]) {
return cache[key];
}
var bound = function() {
return self.apply(context, arguments);
};
cache[key] = bound;
return bound;
};
测试用例:
// 全面的测试用例
function runBindTests() {
console.log("开始 bind 实现测试...");
// 测试1:基本绑定
function basicFunction() {
return this.value;
}
var obj = {value: 42};
var bound = basicFunction.myBind(obj);
console.assert(bound() === 42, "基本绑定测试失败");
// 测试2:参数预设
function sum(a, b, c) {
return a + b + c;
}
var addTen = sum.myBind(null, 10);
console.assert(addTen(5, 2) === 17, "参数预设测试失败");
// 测试3:构造函数调用
function Constructor(name) {
this.name = name;
}
Constructor.prototype.getName = function() {
return this.name;
};
var BoundConstructor = Constructor.myBind(null, "Test");
var instance = new BoundConstructor();
console.assert(instance.name === "Test", "构造函数测试失败");
console.assert(instance instanceof Constructor, "原型链测试失败");
console.log("bind 实现测试完成!");
}
runBindTests();
最佳实践:
What are the differences between call, apply, and bind?
What are the differences between call, apply, and bind?
考察点:函数调用方式和上下文绑定的理解。
答案:
call、apply 和 bind 都是用来改变函数执行时 this 指向的方法,但它们在调用方式、参数传递和返回结果上有重要区别。
基本语法对比:
function greet(greeting, punctuation) {
return greeting + ", " + this.name + punctuation;
}
var person = {name: "John"};
// call:立即执行,参数逐个传递
var result1 = greet.call(person, "Hello", "!");
console.log(result1); // "Hello, John!"
// apply:立即执行,参数以数组形式传递
var result2 = greet.apply(person, ["Hi", "."]);
console.log(result2); // "Hi, John."
// bind:返回新函数,不立即执行
var boundGreet = greet.bind(person, "Hey");
var result3 = boundGreet("!!!");
console.log(result3); // "Hey, John!!!"
主要区别总结:
| 方法 | 执行时机 | 参数传递方式 | 返回值 | 用途 |
|---|---|---|---|---|
| call | 立即执行 | 逐个传递 | 函数执行结果 | 临时改变this并调用 |
| apply | 立即执行 | 数组形式传递 | 函数执行结果 | 参数数组已知时调用 |
| bind | 不执行 | 逐个传递(可部分应用) | 新绑定函数 | 创建绑定函数供后续调用 |
详细用法示例:
1. call 方法的应用:
// 借用其他对象的方法
var obj1 = {
name: "Object1",
greet: function() {
return "Hello from " + this.name;
}
};
var obj2 = {name: "Object2"};
// obj2 借用 obj1 的 greet 方法
console.log(obj1.greet.call(obj2)); // "Hello from Object2"
// 类型转换和检查
function getType() {
return Object.prototype.toString.call(this);
}
console.log(getType.call([])); // "[object Array]"
console.log(getType.call({})); // "[object Object]"
console.log(getType.call("string")); // "[object String]"
console.log(getType.call(42)); // "[object Number]"
// 链式调用父类构造函数
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name); // 调用父构造函数
this.breed = breed;
}
var dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.name); // "Buddy"
console.log(dog.breed); // "Golden Retriever"
2. apply 方法的应用:
// 数组处理 - 找最大值/最小值
var numbers = [1, 5, 3, 9, 2];
var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
console.log(max); // 9
console.log(min); // 1
// 数组合并
var arr1 = [1, 2, 3];
var arr2 = [4, 5, 6];
arr1.push.apply(arr1, arr2);
console.log(arr1); // [1, 2, 3, 4, 5, 6]
// 函数参数数量不确定的情况
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
function calculateSum(numbers) {
return sum.apply(null, numbers);
}
console.log(calculateSum([1, 2, 3, 4, 5])); // 15
// 类数组对象转换为真数组
function convertToArray() {
return Array.prototype.slice.apply(arguments);
}
console.log(convertToArray(1, 2, 3, 4)); // [1, 2, 3, 4]
3. bind 方法的应用:
// 事件处理器中保持 this
function Button(element, text) {
this.element = element;
this.text = text;
this.clickCount = 0;
// 使用 bind 确保事件处理器中的 this 指向正确
this.element.addEventListener('click', this.handleClick.bind(this));
}
Button.prototype.handleClick = function() {
this.clickCount++;
console.log(this.text + " 被点击了 " + this.clickCount + " 次");
};
// 部分应用(Partial Application)
function multiply(a, b, c) {
return a * b * c;
}
var multiplyByTwo = multiply.bind(null, 2);
var multiplyByTwoAndThree = multiply.bind(null, 2, 3);
console.log(multiplyByTwo(4, 5)); // 40 (2 * 4 * 5)
console.log(multiplyByTwoAndThree(6)); // 36 (2 * 3 * 6)
// 延迟执行
function delayedLog(message, delay) {
setTimeout(console.log.bind(console, message), delay);
}
delayedLog("Hello after 1 second", 1000);
性能和使用场景对比:
// 性能测试函数
function performanceTest() {
var obj = {value: 42};
function testFunction(a, b) {
return this.value + a + b;
}
var iterations = 1000000;
var start, end;
// call 性能测试
start = Date.now();
for (var i = 0; i < iterations; i++) {
testFunction.call(obj, 1, 2);
}
end = Date.now();
console.log("call: " + (end - start) + "ms");
// apply 性能测试
start = Date.now();
for (var i = 0; i < iterations; i++) {
testFunction.apply(obj, [1, 2]);
}
end = Date.now();
console.log("apply: " + (end - start) + "ms");
// bind 性能测试(创建绑定函数)
start = Date.now();
var boundFunction = testFunction.bind(obj, 1, 2);
for (var i = 0; i < iterations; i++) {
boundFunction();
}
end = Date.now();
console.log("bind: " + (end - start) + "ms");
}
// performanceTest();
实际应用场景:
1. 数组方法借用:
// NodeList 借用数组方法
var nodeList = document.querySelectorAll('div');
// 使用 call/apply 让 NodeList 使用数组方法
var nodeArray = Array.prototype.slice.call(nodeList);
// 过滤节点
var visibleNodes = Array.prototype.filter.call(nodeList, function(node) {
return node.style.display !== 'none';
});
// 遍历节点
Array.prototype.forEach.call(nodeList, function(node, index) {
console.log('Node ' + index + ':', node);
});
2. 函数式编程应用:
// 使用 call/apply 实现函数组合
function compose() {
var functions = Array.prototype.slice.call(arguments);
return function(value) {
return functions.reduceRight(function(acc, fn) {
return fn.call(null, acc);
}, value);
};
}
function addOne(x) { return x + 1; }
function double(x) { return x * 2; }
function square(x) { return x * x; }
var composedFunction = compose(square, double, addOne);
console.log(composedFunction(3)); // ((3 + 1) * 2)² = 64
// 使用 bind 创建偏函数
function createPartialFunction(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return fn.bind.apply(fn, [null].concat(args));
}
function add(a, b, c) {
return a + b + c;
}
var addFiveAndThree = createPartialFunction(add, 5, 3);
console.log(addFiveAndThree(2)); // 10
3. 模拟类的继承:
// 使用 call 实现继承
function Vehicle(type) {
this.type = type;
this.speed = 0;
}
Vehicle.prototype.accelerate = function(increment) {
this.speed += increment;
return this;
};
function Car(brand, model) {
Vehicle.call(this, 'car'); // 调用父构造函数
this.brand = brand;
this.model = model;
}
// 继承原型
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.prototype.honk = function() {
return this.brand + " " + this.model + " 鸣笛!";
};
var myCar = new Car("Toyota", "Camry");
myCar.accelerate(50);
console.log(myCar.speed); // 50
console.log(myCar.honk()); // "Toyota Camry 鸣笛!"
4. 条件执行和错误处理:
// 安全的方法调用
function safeCall(obj, methodName) {
if (obj && typeof obj[methodName] === 'function') {
var args = Array.prototype.slice.call(arguments, 2);
return obj[methodName].apply(obj, args);
}
return undefined;
}
var obj = {
greet: function(name) {
return "Hello, " + name;
}
};
console.log(safeCall(obj, 'greet', 'John')); // "Hello, John"
console.log(safeCall(obj, 'nonExistent')); // undefined
// 使用 bind 创建安全的回调
function createSafeCallback(callback, context) {
return function() {
try {
return callback.apply(context, arguments);
} catch (error) {
console.error('回调执行出错:', error);
}
};
}
function riskyCallback() {
throw new Error("Something went wrong");
}
var safeCallback = createSafeCallback(riskyCallback);
safeCallback(); // 不会中断程序执行
边界情况和注意事项:
// null/undefined 作为 this
function showThis() {
console.log(this);
}
showThis.call(null); // 全局对象(非严格模式)或 null(严格模式)
showThis.apply(undefined); // 全局对象(非严格模式)或 undefined(严格模式)
// 原始值作为 this
function showThisType() {
console.log(typeof this);
}
showThisType.call(42); // "object" (Number 包装对象)
showThisType.call("string"); // "object" (String 包装对象)
showThisType.call(true); // "object" (Boolean 包装对象)
// bind 的多次调用
function original() {
return this.value;
}
var obj1 = {value: 1};
var obj2 = {value: 2};
var bound1 = original.bind(obj1);
var bound2 = bound1.bind(obj2); // 二次绑定无效
console.log(bound1()); // 1
console.log(bound2()); // 1 (仍然绑定到 obj1)
选择指南:
最佳实践:
What is the scope chain? How does it work?
What is the scope chain? How does it work?
考察点:对变量查找机制和执行上下文的理解。
答案:
作用域链是 JavaScript 引擎查找变量时遵循的查找路径。当访问一个变量时,JavaScript 会沿着作用域链从内到外逐级查找,直到找到该变量或到达全局作用域。
作用域链的基本原理:
var globalVar = "全局变量";
function outerFunction() {
var outerVar = "外层变量";
function innerFunction() {
var innerVar = "内层变量";
// 变量查找顺序:
// 1. innerFunction 的局部作用域
// 2. outerFunction 的作用域
// 3. 全局作用域
console.log(innerVar); // "内层变量" (当前作用域)
console.log(outerVar); // "外层变量" (父级作用域)
console.log(globalVar); // "全局变量" (全局作用域)
}
innerFunction();
}
outerFunction();
词法作用域和作用域链:
var x = 10;
function outer() {
var x = 20;
function inner() {
var x = 30;
console.log("inner x:", x); // 30 (当前作用域)
}
inner();
console.log("outer x:", x); // 20 (outer 作用域)
}
outer();
console.log("global x:", x); // 10 (全局作用域)
// 作用域链演示
function demonstrateScope() {
var level1 = "Level 1";
function level2() {
var level2Var = "Level 2";
function level3() {
var level3Var = "Level 3";
// 可以访问所有上级作用域的变量
console.log(level3Var); // "Level 3"
console.log(level2Var); // "Level 2"
console.log(level1); // "Level 1"
console.log(x); // 10 (全局变量)
}
level3();
}
level2();
}
demonstrateScope();
作用域链的形成过程:
// 执行上下文和作用域链的关系
var global = "全局";
function createScopeChain() {
var outer = "外层";
return function() {
var inner = "内层";
// 当这个函数被创建时,它的作用域链包括:
// [内层作用域] -> [外层作用域] -> [全局作用域]
return function() {
var deepest = "最深层";
// 这个函数的作用域链:
// [最深层作用域] -> [内层作用域] -> [外层作用域] -> [全局作用域]
console.log(deepest); // 最深层
console.log(inner); // 内层
console.log(outer); // 外层
console.log(global); // 全局
};
};
}
var deepFunction = createScopeChain()();
deepFunction();
变量遮蔽(Variable Shadowing):
var name = "全局 name";
function outer() {
var name = "外层 name";
function inner() {
var name = "内层 name";
// 内层变量遮蔽了外层和全局的同名变量
console.log(name); // "内层 name"
// 无法直接访问被遮蔽的外层变量
// 但可以通过其他方式访问
console.log(window.name); // "全局 name" (浏览器环境)
}
inner();
console.log(name); // "外层 name"
}
outer();
console.log(name); // "全局 name"
// 避免变量遮蔽的方式
function betterNaming() {
var globalName = "全局名称";
function processUser() {
var userName = "用户名称";
function validateInput() {
var inputName = "输入名称";
// 清晰的变量命名避免了遮蔽
console.log(inputName); // 输入名称
console.log(userName); // 用户名称
console.log(globalName); // 全局名称
}
validateInput();
}
processUser();
}
闭包与作用域链:
function createCounter() {
var count = 0;
return function() {
// 这个函数保持了对外部作用域的引用
// 形成闭包,count 变量不会被垃圾回收
count++;
return count;
};
}
var counter1 = createCounter();
var counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (独立的作用域链)
// 多级闭包的作用域链
function createMultiLevelCounter() {
var outerCount = 0;
return function() {
var middleCount = 0;
outerCount++;
return function() {
var innerCount = 0;
middleCount++;
innerCount++;
return {
inner: innerCount, // 1 (每次都重新创建)
middle: middleCount, // 累加 (中间作用域)
outer: outerCount // 累加 (外层作用域)
};
};
};
}
var multiCounter = createMultiLevelCounter();
var innerCounter = multiCounter();
console.log(innerCounter()); // {inner: 1, middle: 1, outer: 1}
console.log(innerCounter()); // {inner: 1, middle: 2, outer: 1}
var anotherInner = multiCounter();
console.log(anotherInner()); // {inner: 1, middle: 1, outer: 2}
作用域链的性能影响:
var globalVar = "global";
function performanceExample() {
var level1Var = "level1";
function level2() {
var level2Var = "level2";
function level3() {
var level3Var = "level3";
function level4() {
// 深层嵌套的作用域链查找较慢
// 访问 globalVar 需要遍历 4 层作用域
var start = Date.now();
for (var i = 0; i < 100000; i++) {
// 查找全局变量较慢
var temp = globalVar;
}
console.log("全局变量访问时间:", Date.now() - start);
start = Date.now();
for (var i = 0; i < 100000; i++) {
// 访问局部变量较快
var temp = level3Var;
}
console.log("局部变量访问时间:", Date.now() - start);
}
level4();
}
level3();
}
level2();
}
// 性能优化:缓存外部变量
function optimizedFunction() {
var level1Var = "level1";
function level2() {
var level2Var = "level2";
function level3() {
var level3Var = "level3";
function level4() {
// 将频繁访问的外部变量缓存到局部
var cachedGlobal = globalVar;
var cachedLevel1 = level1Var;
var start = Date.now();
for (var i = 0; i < 100000; i++) {
// 访问缓存的局部变量
var temp = cachedGlobal;
}
console.log("缓存变量访问时间:", Date.now() - start);
}
level4();
}
level3();
}
level2();
}
with 语句对作用域链的影响:
// with 语句会修改作用域链(不推荐使用)
var obj = {
name: "Object Name",
value: 42
};
var name = "Global Name";
var value = 100;
function demonstrateWith() {
var name = "Function Name";
console.log(name); // "Function Name"
console.log(value); // 100 (全局)
with (obj) {
// with 将 obj 添加到作用域链顶部
console.log(name); // "Object Name" (来自 obj)
console.log(value); // 42 (来自 obj)
}
console.log(name); // "Function Name"
console.log(value); // 100 (全局)
}
demonstrateWith();
// with 的问题
function withProblems() {
var x = 1;
var obj = {y: 2};
with (obj) {
x = 3; // 修改外部变量?还是创建 obj.x?
y = 4; // 修改 obj.y
z = 5; // 创建全局变量 z?还是 obj.z?
}
console.log(x); // 3 (修改了外部变量)
console.log(obj.y); // 4 (修改了对象属性)
console.log(obj.z); // undefined
console.log(z); // 5 (创建了全局变量)
}
// 严格模式下 with 被禁用
function strictModeExample() {
"use strict";
var obj = {name: "test"};
// with (obj) { // SyntaxError: Strict mode code may not include a with statement
// console.log(name);
// }
}
eval 对作用域链的影响:
function demonstrateEval() {
var localVar = "local";
console.log(localVar); // "local"
// eval 可以访问当前作用域链
eval('console.log(localVar)'); // "local"
// eval 可以修改当前作用域
eval('var evalVar = "created by eval"');
console.log(evalVar); // "created by eval"
// eval 可以修改已存在的变量
eval('localVar = "modified by eval"');
console.log(localVar); // "modified by eval"
}
demonstrateEval();
// 间接调用 eval 使用全局作用域
function indirectEval() {
var localVar = "local";
var globalEval = eval;
// 间接调用 eval,作用域是全局
try {
globalEval('console.log(localVar)'); // ReferenceError
} catch (e) {
console.log("间接 eval 无法访问局部变量");
}
globalEval('var globalFromEval = "global"');
console.log(window.globalFromEval); // "global"
}
作用域链的实际应用:
// 模块模式利用作用域链
var MyModule = (function() {
var privateVar = "私有变量";
var privateCounter = 0;
function privateFunction() {
return "私有函数被调用";
}
return {
publicMethod: function() {
// 可以访问所有私有变量和函数
privateCounter++;
return privateFunction() + " - 计数器: " + privateCounter;
},
getPrivateVar: function() {
return privateVar;
},
setPrivateVar: function(value) {
privateVar = value;
}
};
})();
console.log(MyModule.publicMethod()); // "私有函数被调用 - 计数器: 1"
console.log(MyModule.getPrivateVar()); // "私有变量"
// 工厂模式中的作用域链
function createUser(name, age) {
var userData = {
name: name,
age: age,
created: new Date()
};
return {
getName: function() {
return userData.name;
},
getAge: function() {
return userData.age;
},
updateAge: function(newAge) {
if (newAge > userData.age) {
userData.age = newAge;
return true;
}
return false;
},
getCreatedTime: function() {
return userData.created;
}
};
}
var user = createUser("John", 25);
console.log(user.getName()); // "John"
user.updateAge(26);
console.log(user.getAge()); // 26
// userData 无法直接访问,被封装在作用域链中
调试作用域链:
// 作用域链调试工具
function debugScopeChain() {
var level1 = "Level 1 Variable";
function showScopeInfo() {
var level2 = "Level 2 Variable";
function innerFunction() {
var level3 = "Level 3 Variable";
// 模拟显示当前可访问的变量
console.log("当前作用域链中的变量:");
console.log("- level3:", level3);
console.log("- level2:", level2);
console.log("- level1:", level1);
// 使用 arguments.callee.caller 查看调用链(非严格模式)
var caller = arguments.callee.caller;
console.log("调用栈:", caller ? caller.name : "全局");
}
innerFunction();
}
showScopeInfo();
}
debugScopeChain();
最佳实践:
What is an Immediately Invoked Function Expression (IIFE)? What is its purpose?
What is an Immediately Invoked Function Expression (IIFE)? What is its purpose?
考察点:模块化和作用域隔离的理解。
答案:
IIFE(Immediately Invoked Function Expression)是一个函数表达式,它在定义后立即被调用执行。IIFE 是 JavaScript 中实现模块化和作用域隔离的重要模式。
IIFE 的基本语法:
// 最常见的 IIFE 形式
(function() {
console.log("这是一个 IIFE");
})();
// 另一种写法(推荐)
(function() {
console.log("另一种 IIFE 写法");
}());
// 带参数的 IIFE
(function(name, age) {
console.log("姓名:", name, "年龄:", age);
})("John", 25);
// 箭头函数 IIFE(ES6+,但在 ES5 环境中了解语法差异很有用)
(() => {
console.log("箭头函数 IIFE");
})();
// 有返回值的 IIFE
var result = (function() {
var privateVar = "私有变量";
return "返回值: " + privateVar;
})();
console.log(result); // "返回值: 私有变量"
IIFE 的语法解析:
// 为什么需要括号?
function myFunction() {
console.log("普通函数声明");
}(); // SyntaxError: Unexpected token )
// 函数表达式可以立即调用
var myFunc = function() {
console.log("函数表达式");
};
myFunc(); // 正常调用
// IIFE 的原理:将函数声明转换为函数表达式
(function() {
// 括号将函数声明转换为函数表达式
console.log("IIFE 执行");
})();
// 其他创建函数表达式的方式
+function() {
console.log("使用 + 操作符");
}();
-function() {
console.log("使用 - 操作符");
}();
!function() {
console.log("使用 ! 操作符");
}();
~function() {
console.log("使用 ~ 操作符");
}();
void function() {
console.log("使用 void 操作符");
}();
IIFE 的主要用途:
1. 创建私有作用域和避免全局污染:
// 全局变量污染的问题
var name = "Global Name";
var count = 0;
function increment() {
count++;
}
// 使用 IIFE 避免全局污染
(function() {
var name = "Local Name";
var count = 0;
function increment() {
count++;
console.log("Local count:", count);
}
increment(); // Local count: 1
increment(); // Local count: 2
})();
console.log(name); // "Global Name" (全局变量未被污染)
console.log(count); // 0 (全局变量未被修改)
// 多个脚本文件的命名冲突
// file1.js
(function() {
var utils = {
format: function(str) {
return str.toUpperCase();
}
};
// 使用 utils
console.log(utils.format("hello")); // "HELLO"
})();
// file2.js
(function() {
var utils = {
format: function(str) {
return str.toLowerCase();
}
};
// 使用不同的 utils,不会冲突
console.log(utils.format("WORLD")); // "world"
})();
2. 模块模式实现:
// 模块模式的经典实现
var MyModule = (function() {
// 私有变量和函数
var privateCounter = 0;
var privateArray = [];
function privateFunction() {
console.log("这是私有函数");
}
function validateInput(input) {
return typeof input === 'string' && input.length > 0;
}
// 返回公共接口
return {
// 公共方法
increment: function() {
privateCounter++;
return privateCounter;
},
decrement: function() {
privateCounter--;
return privateCounter;
},
getCounter: function() {
return privateCounter;
},
addItem: function(item) {
if (validateInput(item)) {
privateArray.push(item);
return true;
}
return false;
},
getItems: function() {
return privateArray.slice(); // 返回副本
},
reset: function() {
privateCounter = 0;
privateArray = [];
privateFunction(); // 调用私有函数
}
};
})();
// 使用模块
console.log(MyModule.increment()); // 1
console.log(MyModule.increment()); // 2
MyModule.addItem("item1");
MyModule.addItem("item2");
console.log(MyModule.getItems()); // ["item1", "item2"]
console.log(MyModule.getCounter()); // 2
// 私有变量无法直接访问
// console.log(MyModule.privateCounter); // undefined
3. 参数化的 IIFE:
// 传递全局对象
(function(global) {
var localVar = "局部变量";
// 在 IIFE 内部操作全局对象
global.myGlobalFunction = function() {
console.log("全局函数被调用");
};
global.config = global.config || {};
global.config.version = "1.0.0";
})(window || global || this);
// 传递 jQuery 对象(防止 $ 冲突)
(function($) {
// 在这里可以安全使用 $
$(document).ready(function() {
console.log("jQuery ready");
});
// 创建 jQuery 插件
$.fn.myPlugin = function() {
return this.each(function() {
console.log("Plugin applied to:", this);
});
};
})(jQuery);
// 传递 undefined(防止 undefined 被重写)
(function(undefined) {
var someVar;
// 安全地检查 undefined
if (someVar === undefined) {
console.log("someVar 是 undefined");
}
// 在老版本 JS 中,undefined 可能被重写
// 通过参数传递确保是真正的 undefined
})();
// 多参数的 IIFE
(function(win, doc, $, undefined) {
var app = {
init: function() {
this.bindEvents();
this.setupConfig();
},
bindEvents: function() {
$(doc).ready(function() {
console.log("应用初始化完成");
});
},
setupConfig: function() {
win.APP_CONFIG = {
debug: true,
version: "1.0"
};
}
};
// 暴露到全局
win.MyApp = app;
})(window, document, jQuery);
4. 循环中的闭包问题解决:
// 错误的循环闭包
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function() {
console.log("按钮", i, "被点击"); // 总是输出最后一个 i 值
};
}
// 使用 IIFE 解决闭包问题
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = (function(index) {
return function() {
console.log("按钮", index, "被点击"); // 正确的 index 值
};
})(i);
}
// 更简洁的 IIFE 解决方案
for (var i = 0; i < buttons.length; i++) {
(function(index) {
buttons[index].onclick = function() {
console.log("按钮", index, "被点击");
};
})(i);
}
// 数组循环的 IIFE 应用
var funcs = [];
// 错误的方式
for (var i = 0; i < 5; i++) {
funcs.push(function() {
return i; // 总是返回 5
});
}
console.log(funcs[0]()); // 5
console.log(funcs[2]()); // 5
// 使用 IIFE 修正
var correctFuncs = [];
for (var i = 0; i < 5; i++) {
correctFuncs.push((function(num) {
return function() {
return num; // 返回正确的值
};
})(i));
}
console.log(correctFuncs[0]()); // 0
console.log(correctFuncs[2]()); // 2
5. 库和框架的封装:
// 创建一个简单的工具库
var Utils = (function() {
var version = "1.0.0";
var cache = {};
// 私有辅助函数
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
// 公共 API
return {
version: version,
extend: function(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
},
clone: function(obj) {
var cacheKey = JSON.stringify(obj);
if (cache[cacheKey]) {
return cache[cacheKey];
}
var result;
if (isArray(obj)) {
result = [];
for (var i = 0; i < obj.length; i++) {
result[i] = this.clone(obj[i]);
}
} else if (isObject(obj)) {
result = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = this.clone(obj[key]);
}
}
} else {
result = obj;
}
cache[cacheKey] = result;
return result;
},
debounce: function(func, delay) {
var timeoutId;
return function() {
var context = this;
var args = arguments;
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}
};
})();
// 使用工具库
var obj1 = {a: 1, b: {c: 2}};
var obj2 = Utils.clone(obj1);
console.log(Utils.version); // "1.0.0"
6. 初始化代码:
// 应用初始化
(function() {
var app = {
config: {
debug: false,
apiUrl: 'https://api.example.com'
},
init: function() {
this.setupEnvironment();
this.loadDependencies();
this.bindEvents();
console.log("应用初始化完成");
},
setupEnvironment: function() {
if (location.hostname === 'localhost') {
this.config.debug = true;
this.config.apiUrl = 'http://localhost:3000';
}
},
loadDependencies: function() {
// 加载必要的依赖
if (this.config.debug) {
console.log("调试模式已启用");
}
},
bindEvents: function() {
var self = this;
document.addEventListener('DOMContentLoaded', function() {
self.onDOMReady();
});
},
onDOMReady: function() {
console.log("DOM 已准备就绪");
// 初始化 UI 组件
}
};
// 立即初始化
app.init();
})();
// 条件初始化
(function() {
if (typeof window !== 'undefined' && window.document) {
// 浏览器环境初始化
console.log("浏览器环境检测到");
// 检测特性支持
var features = {
localStorage: (function() {
try {
return 'localStorage' in window && window.localStorage !== null;
} catch (e) {
return false;
}
})(),
addEventListener: !!window.addEventListener,
querySelector: !!document.querySelector
};
window.FEATURES = features;
} else {
// Node.js 或其他环境
console.log("非浏览器环境");
}
})();
IIFE 的高级模式:
// 模块加载器模式
var ModuleLoader = (function() {
var modules = {};
var loading = {};
return {
define: function(name, dependencies, factory) {
if (modules[name]) {
throw new Error("模块 " + name + " 已经定义");
}
var deps = [];
for (var i = 0; i < dependencies.length; i++) {
if (!modules[dependencies[i]]) {
throw new Error("依赖模块 " + dependencies[i] + " 未找到");
}
deps.push(modules[dependencies[i]]);
}
modules[name] = factory.apply(null, deps);
},
require: function(name) {
if (!modules[name]) {
throw new Error("模块 " + name + " 未定义");
}
return modules[name];
}
};
})();
// 使用模块加载器
ModuleLoader.define('utils', [], function() {
return {
capitalize: function(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
};
});
ModuleLoader.define('greeting', ['utils'], function(utils) {
return {
sayHello: function(name) {
return "Hello, " + utils.capitalize(name) + "!";
}
};
});
var greeting = ModuleLoader.require('greeting');
console.log(greeting.sayHello('john')); // "Hello, John!"
// 命名空间模式
var MyNamespace = MyNamespace || {};
(function(ns) {
ns.Utils = ns.Utils || {};
ns.Utils.String = (function() {
return {
trim: function(str) {
return str.replace(/^\s+|\s+$/g, '');
},
format: function(template) {
var args = Array.prototype.slice.call(arguments, 1);
return template.replace(/\{(\d+)\}/g, function(match, index) {
return args[index] || match;
});
}
};
})();
ns.Utils.Array = (function() {
return {
unique: function(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
if (result.indexOf(arr[i]) === -1) {
result.push(arr[i]);
}
}
return result;
}
};
})();
})(MyNamespace);
// 使用命名空间
console.log(MyNamespace.Utils.String.format("Hello, {0}!", "World"));
console.log(MyNamespace.Utils.Array.unique([1, 2, 2, 3, 3, 4]));
IIFE 的注意事项:
// 分号问题
var a = 1
(function() {
console.log("这会被解释为 a(function()...)");
})(); // TypeError: 1 is not a function
// 正确的做法:在 IIFE 前加分号
var a = 1;
;(function() {
console.log("安全的 IIFE");
})();
// 或者在行末加分号
var b = 2;
(function() {
console.log("另一种安全的方式");
})();
// 返回值的使用
var result = (function(x, y) {
return x + y;
})(3, 4);
console.log(result); // 7
// 递归 IIFE
(function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
})(5); // 计算 5 的阶乘
// 条件 IIFE
var feature = (function() {
if ('localStorage' in window) {
return {
set: function(key, value) {
localStorage.setItem(key, value);
},
get: function(key) {
return localStorage.getItem(key);
}
};
} else {
// 降级方案
var storage = {};
return {
set: function(key, value) {
storage[key] = value;
},
get: function(key) {
return storage[key];
}
};
}
})();
最佳实践:
What are the different ways to implement inheritance in JavaScript?
What are the different ways to implement inheritance in JavaScript?
考察点:面向对象编程和继承模式的掌握。
答案:
JavaScript 中有多种继承实现方式,每种都有其特点和适用场景。主要包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承和寄生组合式继承。
1. 原型链继承(Prototype Chain Inheritance):
// 父类构造函数
function Animal(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Animal.prototype.getName = function() {
return this.name;
};
Animal.prototype.speak = function() {
console.log(this.name + " makes a sound");
};
// 子类构造函数
function Dog(name, breed) {
this.breed = breed;
}
// 实现继承:子类原型指向父类实例
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog; // 修复构造函数指向
// 子类特有方法
Dog.prototype.bark = function() {
console.log(this.name + " barks");
};
// 使用
var dog1 = new Dog("Buddy", "Golden Retriever");
dog1.name = "Buddy"; // 需要手动设置,因为没有调用父类构造函数
console.log(dog1.getName()); // "Buddy"
dog1.speak(); // "Buddy makes a sound"
dog1.bark(); // "Buddy barks"
var dog2 = new Dog("Max", "Labrador");
dog2.name = "Max";
dog1.colors.push('yellow');
console.log(dog2.colors); // ['red', 'blue', 'green', 'yellow'] - 共享引用类型属性
// 原型链继承的问题
console.log("原型链继承问题演示:");
console.log(dog1.colors === dog2.colors); // true - 共享引用类型
原型链继承的优缺点:
2. 构造函数继承(Constructor Inheritance):
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
this.colors = ['red', 'blue', 'green'];
this.getInfo = function() {
return this.name + " is " + this.age + " years old";
};
}
Animal.prototype.speak = function() {
console.log(this.name + " makes a sound");
};
// 子类使用构造函数继承
function Dog(name, age, breed) {
// 调用父类构造函数
Animal.call(this, name, age);
this.breed = breed;
}
Dog.prototype.bark = function() {
console.log(this.name + " barks");
};
// 使用
var dog1 = new Dog("Buddy", 3, "Golden Retriever");
var dog2 = new Dog("Max", 2, "Labrador");
console.log(dog1.getInfo()); // "Buddy is 3 years old"
console.log(dog2.getInfo()); // "Max is 2 years old"
// 引用类型不再共享
dog1.colors.push('yellow');
console.log(dog1.colors); // ['red', 'blue', 'green', 'yellow']
console.log(dog2.colors); // ['red', 'blue', 'green']
// 但无法继承父类原型方法
try {
dog1.speak(); // TypeError: dog1.speak is not a function
} catch (e) {
console.log("无法访问父类原型方法:", e.message);
}
构造函数继承的优缺点:
3. 组合继承(Combination Inheritance):
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
this.colors = ['red', 'blue', 'green'];
}
Animal.prototype.speak = function() {
console.log(this.name + " makes a sound");
};
Animal.prototype.getAge = function() {
return this.age;
};
// 子类使用组合继承
function Dog(name, age, breed) {
// 构造函数继承:继承实例属性
Animal.call(this, name, age);
this.breed = breed;
}
// 原型链继承:继承原型方法
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
// 子类特有方法
Dog.prototype.bark = function() {
console.log(this.name + " barks");
};
Dog.prototype.getBreed = function() {
return this.breed;
};
// 使用
var dog1 = new Dog("Buddy", 3, "Golden Retriever");
var dog2 = new Dog("Max", 2, "Labrador");
console.log(dog1.name); // "Buddy"
console.log(dog1.getAge()); // 3
dog1.speak(); // "Buddy makes a sound"
dog1.bark(); // "Buddy barks"
// 引用类型不共享
dog1.colors.push('yellow');
console.log(dog1.colors); // ['red', 'blue', 'green', 'yellow']
console.log(dog2.colors); // ['red', 'blue', 'green']
// 组合继承的问题:父类构造函数被调用两次
console.log("构造函数调用次数问题:");
function ParentWithLog(name) {
console.log("Parent constructor called with:", name);
this.name = name;
}
function ChildWithLog(name, age) {
ParentWithLog.call(this, name); // 第一次调用
this.age = age;
}
ChildWithLog.prototype = new ParentWithLog(); // 第二次调用
ChildWithLog.prototype.constructor = ChildWithLog;
var child = new ChildWithLog("Test", 10);
// 输出:
// Parent constructor called with: undefined
// Parent constructor called with: Test
4. 原型式继承(Prototypal Inheritance):
// Object.create 的实现原理
function createObject(proto) {
function F() {}
F.prototype = proto;
return new F();
}
// 父对象
var animal = {
name: "Animal",
colors: ['red', 'blue', 'green'],
speak: function() {
console.log(this.name + " makes a sound");
},
getColors: function() {
return this.colors;
}
};
// 原型式继承
var dog = createObject(animal);
dog.name = "Dog";
dog.bark = function() {
console.log(this.name + " barks");
};
var cat = createObject(animal);
cat.name = "Cat";
cat.meow = function() {
console.log(this.name + " meows");
};
// 使用
dog.speak(); // "Dog makes a sound"
dog.bark(); // "Dog barks"
cat.speak(); // "Cat makes a sound"
cat.meow(); // "Cat meows"
// 引用类型仍然共享
dog.colors.push('yellow');
console.log(cat.colors); // ['red', 'blue', 'green', 'yellow']
// ES5 Object.create 方法
if (Object.create) {
var modernDog = Object.create(animal);
modernDog.name = "Modern Dog";
modernDog.speak(); // "Modern Dog makes a sound"
// 使用属性描述符
var advancedDog = Object.create(animal, {
name: {
value: "Advanced Dog",
writable: true,
enumerable: true,
configurable: true
},
breed: {
value: "Golden Retriever",
writable: false,
enumerable: true,
configurable: false
}
});
console.log(advancedDog.name); // "Advanced Dog"
console.log(advancedDog.breed); // "Golden Retriever"
}
5. 寄生式继承(Parasitic Inheritance):
function createDog(original) {
var clone = Object.create(original); // 创建对象副本
// 增强对象
clone.bark = function() {
console.log(this.name + " barks loudly");
};
clone.wagTail = function() {
console.log(this.name + " wags tail");
};
// 重写父类方法
var originalSpeak = clone.speak;
clone.speak = function() {
originalSpeak.call(this);
console.log("...and it's a dog!");
};
return clone;
}
// 父对象
var animal = {
name: "Animal",
speak: function() {
console.log(this.name + " makes a sound");
}
};
// 使用寄生式继承
var myDog = createDog(animal);
myDog.name = "Buddy";
myDog.speak(); // "Buddy makes a sound" + "...and it's a dog!"
myDog.bark(); // "Buddy barks loudly"
myDog.wagTail(); // "Buddy wags tail"
// 工厂模式的寄生式继承
function createAnimalFactory(type, name) {
var animal = Object.create({
speak: function() {
console.log(this.name + " makes a sound");
},
getName: function() {
return this.name;
}
});
animal.name = name;
animal.type = type;
// 根据类型添加特定方法
if (type === 'dog') {
animal.bark = function() {
console.log(this.name + " barks");
};
} else if (type === 'cat') {
animal.meow = function() {
console.log(this.name + " meows");
};
}
return animal;
}
var dog = createAnimalFactory('dog', 'Rex');
var cat = createAnimalFactory('cat', 'Whiskers');
dog.bark(); // "Rex barks"
cat.meow(); // "Whiskers meows"
6. 寄生组合式继承(Parasitic Combination Inheritance):
// 寄生组合式继承 - 最理想的继承方式
function inheritPrototype(child, parent) {
var prototype = Object.create(parent.prototype); // 创建父类原型副本
prototype.constructor = child; // 增强对象
child.prototype = prototype; // 指定对象
}
// 父类
function Animal(name, age) {
this.name = name;
this.age = age;
this.colors = ['red', 'blue', 'green'];
}
Animal.prototype.speak = function() {
console.log(this.name + " makes a sound");
};
Animal.prototype.getAge = function() {
return this.age;
};
// 子类
function Dog(name, age, breed) {
Animal.call(this, name, age); // 只调用一次父类构造函数
this.breed = breed;
}
// 实现继承
inheritPrototype(Dog, Animal);
// 添加子类方法
Dog.prototype.bark = function() {
console.log(this.name + " barks");
};
Dog.prototype.getBreed = function() {
return this.breed;
};
// 使用
var dog1 = new Dog("Buddy", 3, "Golden Retriever");
var dog2 = new Dog("Max", 2, "Labrador");
console.log(dog1.name); // "Buddy"
console.log(dog1.getAge()); // 3
console.log(dog1.getBreed()); // "Golden Retriever"
dog1.speak(); // "Buddy makes a sound"
dog1.bark(); // "Buddy barks"
// 引用类型不共享
dog1.colors.push('yellow');
console.log(dog1.colors); // ['red', 'blue', 'green', 'yellow']
console.log(dog2.colors); // ['red', 'blue', 'green']
// 验证原型链
console.log(dog1 instanceof Dog); // true
console.log(dog1 instanceof Animal); // true
console.log(Dog.prototype.isPrototypeOf(dog1)); // true
console.log(Animal.prototype.isPrototypeOf(dog1)); // true
多级继承示例:
// 三级继承示例
function Animal(name) {
this.name = name;
this.kingdom = "Animal Kingdom";
}
Animal.prototype.breathe = function() {
console.log(this.name + " breathes");
};
function Mammal(name, warmBlooded) {
Animal.call(this, name);
this.warmBlooded = warmBlooded;
}
inheritPrototype(Mammal, Animal);
Mammal.prototype.feedMilk = function() {
console.log(this.name + " feeds milk to babies");
};
function Dog(name, breed) {
Mammal.call(this, name, true);
this.breed = breed;
}
inheritPrototype(Dog, Mammal);
Dog.prototype.bark = function() {
console.log(this.name + " barks");
};
// 使用
var myDog = new Dog("Buddy", "Golden Retriever");
console.log(myDog.name); // "Buddy"
console.log(myDog.kingdom); // "Animal Kingdom"
console.log(myDog.warmBlooded); // true
console.log(myDog.breed); // "Golden Retriever"
myDog.breathe(); // "Buddy breathes"
myDog.feedMilk(); // "Buddy feeds milk to babies"
myDog.bark(); // "Buddy barks"
// 验证继承链
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Mammal); // true
console.log(myDog instanceof Animal); // true
Mixin 模式(混入继承):
// Mixin 功能模块
var CanFly = {
fly: function() {
console.log(this.name + " is flying");
},
land: function() {
console.log(this.name + " has landed");
}
};
var CanSwim = {
swim: function() {
console.log(this.name + " is swimming");
},
dive: function() {
console.log(this.name + " is diving");
}
};
var CanWalk = {
walk: function() {
console.log(this.name + " is walking");
},
run: function() {
console.log(this.name + " is running");
}
};
// Mixin 函数
function mixin(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
// 基类
function Animal(name) {
this.name = name;
}
function Bird(name) {
Animal.call(this, name);
}
inheritPrototype(Bird, Animal);
function Duck(name) {
Bird.call(this, name);
}
inheritPrototype(Duck, Bird);
// 给 Duck 添加多种能力
mixin(Duck.prototype, CanFly);
mixin(Duck.prototype, CanSwim);
mixin(Duck.prototype, CanWalk);
var duck = new Duck("Donald");
duck.fly(); // "Donald is flying"
duck.swim(); // "Donald is swimming"
duck.walk(); // "Donald is walking"
// 批量 mixin
function multiMixin(target) {
var sources = Array.prototype.slice.call(arguments, 1);
sources.forEach(function(source) {
mixin(target, source);
});
return target;
}
function Superman(name) {
this.name = name;
}
multiMixin(Superman.prototype, CanFly, CanWalk);
var superman = new Superman("Clark Kent");
superman.fly(); // "Clark Kent is flying"
superman.walk(); // "Clark Kent is walking"
继承的性能比较和最佳实践:
// 性能测试函数
function performanceTest(name, createFn, iterations) {
var start = Date.now();
for (var i = 0; i < iterations; i++) {
var obj = createFn(i);
obj.someMethod && obj.someMethod();
}
var end = Date.now();
console.log(name + " 用时: " + (end - start) + "ms");
}
// 测试不同继承方式的性能
var iterations = 100000;
// 原型链继承
function TestPrototype() {
function Parent(id) { this.id = id; }
Parent.prototype.someMethod = function() { return this.id; };
function Child(id) { this.id = id; }
Child.prototype = new Parent();
Child.prototype.constructor = Child;
return new Child(arguments[0]);
}
// 寄生组合式继承
function TestParasiticCombination() {
function Parent(id) { this.id = id; }
Parent.prototype.someMethod = function() { return this.id; };
function Child(id) { Parent.call(this, id); }
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
return new Child(arguments[0]);
}
// Object.create
function TestObjectCreate() {
var parent = {
someMethod: function() { return this.id; }
};
var child = Object.create(parent);
child.id = arguments[0];
return child;
}
// 执行性能测试
console.log("继承方式性能比较:");
performanceTest("原型链继承", TestPrototype, iterations);
performanceTest("寄生组合式继承", TestParasiticCombination, iterations);
performanceTest("Object.create", TestObjectCreate, iterations);
继承方式总结对比:
| 继承方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 原型链继承 | 简单实现,继承原型方法 | 引用类型共享,无法传参 | 简单继承,不涉及引用类型 |
| 构造函数继承 | 避免引用共享,可传参 | 无法继承原型方法,方法重复创建 | 只需要继承实例属性 |
| 组合继承 | 结合前两者优点 | 父类构造函数调用两次 | 需要完整继承功能 |
| 原型式继承 | 简单,基于对象 | 引用类型共享 | 对象间的简单继承 |
| 寄生式继承 | 可以增强对象 | 方法重复创建 | 需要对继承对象进行增强 |
| 寄生组合式继承 | 最完美的实现 | 实现复杂 | 推荐的标准继承方式 |
最佳实践:
What is a constructor function? What does the 'new' operator do?
What is a constructor function? What does the ‘new’ operator do?
考察点:对象创建过程和构造函数原理的理解。
答案:
构造函数是用来创建对象实例的特殊函数。通过 new 操作符调用构造函数可以创建一个新的对象实例,并将构造函数的 this 绑定到这个新对象上。
构造函数的基本概念:
// 构造函数(按约定首字母大写)
function Person(name, age) {
// this 指向新创建的实例
this.name = name;
this.age = age;
this.sayHello = function() {
console.log("Hello, I'm " + this.name);
};
}
// 在原型上添加方法(更好的做法)
Person.prototype.getAge = function() {
return this.age;
};
Person.prototype.introduce = function() {
console.log("My name is " + this.name + ", I'm " + this.age + " years old");
};
// 使用 new 创建实例
var person1 = new Person("Alice", 25);
var person2 = new Person("Bob", 30);
console.log(person1.name); // "Alice"
console.log(person2.name); // "Bob"
person1.sayHello(); // "Hello, I'm Alice"
person2.introduce(); // "My name is Bob, I'm 30 years old"
// 验证实例关系
console.log(person1 instanceof Person); // true
console.log(person1.constructor === Person); // true
new 操作符的执行步骤:
// new 操作符内部执行的四个步骤:
function myNew(constructor, ...args) {
// 1. 创建一个新的空对象
var obj = {};
// 2. 将新对象的原型链接到构造函数的 prototype
obj.__proto__ = constructor.prototype;
// 或者使用 Object.setPrototypeOf(obj, constructor.prototype);
// 3. 将构造函数的 this 绑定到新对象,并执行构造函数
var result = constructor.apply(obj, args);
// 4. 如果构造函数返回了对象类型,则返回该对象;否则返回新创建的对象
return (typeof result === 'object' && result !== null) ? result : obj;
}
// 测试自定义的 new 实现
function Car(brand, model) {
this.brand = brand;
this.model = model;
}
Car.prototype.getInfo = function() {
return this.brand + " " + this.model;
};
// 使用原生 new
var car1 = new Car("Toyota", "Camry");
console.log(car1.getInfo()); // "Toyota Camry"
// 使用自定义 myNew
var car2 = myNew(Car, "Honda", "Civic");
console.log(car2.getInfo()); // "Honda Civic"
console.log(car2 instanceof Car); // true
构造函数的详细分析:
// 构造函数 vs 普通函数
function Vehicle(type) {
this.type = type;
this.wheels = 4;
}
Vehicle.prototype.move = function() {
console.log(this.type + " is moving");
};
// 作为构造函数调用
var car = new Vehicle("car");
console.log(car.type); // "car"
car.move(); // "car is moving"
// 作为普通函数调用
Vehicle("truck"); // this 指向全局对象
console.log(window.type); // "truck" (浏览器环境)
// 严格模式下的差异
function StrictVehicle(type) {
"use strict";
this.type = type; // 严格模式下,this 为 undefined,会报错
}
try {
StrictVehicle("bike"); // TypeError: Cannot set property 'type' of undefined
} catch (e) {
console.log("严格模式错误:", e.message);
}
// 检测函数是否被 new 调用
function SafeConstructor(name) {
// 方法1:检查 this 是否是当前构造函数的实例
if (!(this instanceof SafeConstructor)) {
return new SafeConstructor(name);
}
this.name = name;
}
var obj1 = new SafeConstructor("test1");
var obj2 = SafeConstructor("test2"); // 自动使用 new
console.log(obj1.name); // "test1"
console.log(obj2.name); // "test2"
console.log(obj1 instanceof SafeConstructor); // true
console.log(obj2 instanceof SafeConstructor); // true
// 方法2:使用 new.target(ES6,但了解概念有助于理解)
function ModernConstructor(name) {
if (!new.target) {
throw new Error("必须使用 new 调用");
}
this.name = name;
}
构造函数返回值的处理:
// 构造函数返回原始值
function ReturnPrimitive() {
this.name = "instance";
return "string"; // 返回原始值,会被忽略
}
var obj1 = new ReturnPrimitive();
console.log(obj1.name); // "instance"
console.log(typeof obj1); // "object"
// 构造函数返回对象
function ReturnObject() {
this.name = "instance";
return {
customProp: "custom object"
}; // 返回对象,会替代默认创建的实例
}
var obj2 = new ReturnObject();
console.log(obj2.name); // undefined
console.log(obj2.customProp); // "custom object"
// 构造函数返回 null
function ReturnNull() {
this.name = "instance";
return null; // null 被忽略,返回默认实例
}
var obj3 = new ReturnNull();
console.log(obj3.name); // "instance"
// 实际应用:单例模式
function Singleton() {
// 如果已存在实例,返回现有实例
if (Singleton.instance) {
return Singleton.instance;
}
// 创建新实例
this.created = new Date();
Singleton.instance = this;
}
var s1 = new Singleton();
var s2 = new Singleton();
console.log(s1 === s2); // true,同一个实例
原型链和构造函数的关系:
function Animal(species) {
this.species = species;
}
Animal.prototype.breathe = function() {
console.log(this.species + " breathes");
};
var dog = new Animal("Canine");
// 原型链关系
console.log(dog.__proto__ === Animal.prototype); // true
console.log(Animal.prototype.constructor === Animal); // true
console.log(dog.constructor === Animal); // true
// 原型链查找
console.log(dog.hasOwnProperty('species')); // true
console.log(dog.hasOwnProperty('breathe')); // false
console.log('breathe' in dog); // true
// 动态修改原型
Animal.prototype.sleep = function() {
console.log(this.species + " sleeps");
};
dog.sleep(); // "Canine sleeps" - 已创建的实例也能访问新方法
// 原型污染防护
function SecureConstructor(name) {
this.name = name;
// 防止原型污染
if (this.constructor !== SecureConstructor) {
throw new Error("Invalid constructor");
}
}
SecureConstructor.prototype.getName = function() {
return this.name;
};
构造函数的高级用法:
// 工厂函数 vs 构造函数
function createPerson(name, age) {
return {
name: name,
age: age,
sayHello: function() {
console.log("Hello, I'm " + this.name);
}
};
}
function PersonConstructor(name, age) {
this.name = name;
this.age = age;
}
PersonConstructor.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name);
};
// 比较两种方式
var person1 = createPerson("Alice", 25);
var person2 = new PersonConstructor("Bob", 30);
console.log(person1.sayHello === person2.sayHello); // false vs true (原型方法)
// 构造函数的静态方法
function MathUtils(value) {
this.value = value;
}
// 静态方法
MathUtils.add = function(a, b) {
return a + b;
};
MathUtils.PI = 3.14159;
// 实例方法
MathUtils.prototype.square = function() {
return this.value * this.value;
};
console.log(MathUtils.add(5, 3)); // 8
console.log(MathUtils.PI); // 3.14159
var math = new MathUtils(5);
console.log(math.square()); // 25
// 构造函数继承静态属性的问题
function Parent() {}
Parent.staticProp = "parent static";
function Child() {}
Child.prototype = Object.create(Parent.prototype);
console.log(Child.staticProp); // undefined - 静态属性不会被继承
// 手动继承静态属性
function inheritStatic(child, parent) {
for (var key in parent) {
if (parent.hasOwnProperty(key)) {
child[key] = parent[key];
}
}
}
inheritStatic(Child, Parent);
console.log(Child.staticProp); // "parent static"
内置构造函数的行为:
// 内置构造函数的特殊行为
console.log(new Array(5)); // [empty × 5] - 特殊行为
console.log(new Array(1, 2, 3)); // [1, 2, 3]
console.log(new Object(null)); // {} - 空对象
console.log(new Object(5)); // Number {5} - 包装对象
console.log(new Object("hello")); // String {"hello"} - 包装对象
// 基本类型的构造函数
var str1 = "hello";
var str2 = new String("hello");
console.log(typeof str1); // "string"
console.log(typeof str2); // "object"
console.log(str1 == str2); // true
console.log(str1 === str2); // false
// 包装对象的陷阱
var bool = new Boolean(false);
if (bool) {
console.log("这会执行"); // 包装对象总是 truthy
}
// 避免使用 new 创建基本类型
var num1 = Number("42"); // 42 (数字)
var num2 = new Number("42"); // Number {42} (对象)
console.log(num1 + 8); // 50
console.log(num2 + 8); // 50 (自动拆箱)
构造函数的性能和内存考虑:
// 错误的做法:在构造函数内定义方法
function BadPerson(name) {
this.name = name;
// 每个实例都有自己的方法副本
this.getName = function() {
return this.name;
};
this.setName = function(newName) {
this.name = newName;
};
}
// 正确的做法:在原型上定义方法
function GoodPerson(name) {
this.name = name;
}
// 所有实例共享同一个方法
GoodPerson.prototype.getName = function() {
return this.name;
};
GoodPerson.prototype.setName = function(newName) {
this.name = newName;
};
// 性能测试
console.time("Bad approach");
for (var i = 0; i < 10000; i++) {
new BadPerson("Test" + i);
}
console.timeEnd("Bad approach");
console.time("Good approach");
for (var i = 0; i < 10000; i++) {
new GoodPerson("Test" + i);
}
console.timeEnd("Good approach");
// 内存使用比较
var badInstances = [];
var goodInstances = [];
for (var i = 0; i < 1000; i++) {
badInstances.push(new BadPerson("Test"));
goodInstances.push(new GoodPerson("Test"));
}
// BadPerson 的每个实例都有独立的方法
console.log(badInstances[0].getName === badInstances[1].getName); // false
// GoodPerson 的所有实例共享原型方法
console.log(goodInstances[0].getName === goodInstances[1].getName); // true
高级构造函数模式:
// 混合构造函数模式
function HybridPerson(name, age) {
// 实例属性
this.name = name;
this.age = age;
this.friends = [];
// 特殊情况:实例方法(需要闭包时)
if (typeof this.getSecretInfo !== 'function') {
var secret = "secret-" + Math.random();
HybridPerson.prototype.getSecretInfo = function() {
return secret;
};
}
}
// 共享方法
HybridPerson.prototype.getName = function() {
return this.name;
};
// 寄生构造函数模式
function ParasiticArray() {
var values = new Array();
// 添加额外的方法
values.toPipedString = function() {
return this.join("|");
};
return values; // 返回其他对象
}
var colors = new ParasiticArray();
colors.push("red", "blue", "green");
console.log(colors.toPipedString()); // "red|blue|green"
// 稳妥构造函数模式(数据安全)
function SafePerson(name) {
var o = new Object();
// 私有变量和函数
var privateName = name;
// 公共方法
o.sayName = function() {
console.log(privateName);
};
// 除了 sayName,没有其他方法能访问 privateName
return o;
}
var safePerson = SafePerson("Alice");
safePerson.sayName(); // "Alice"
console.log(safePerson.name); // undefined - 无法直接访问
构造函数的调试和验证:
// 构造函数验证工具
function validateConstructor(instance, Constructor) {
console.log("=== 构造函数验证 ===");
console.log("实例:", instance);
console.log("构造函数:", Constructor.name);
console.log("instanceof 检查:", instance instanceof Constructor);
console.log("constructor 属性:", instance.constructor === Constructor);
console.log("原型链检查:", Constructor.prototype.isPrototypeOf(instance));
console.log("原型对象:", Object.getPrototypeOf(instance) === Constructor.prototype);
}
function TestConstructor(value) {
this.value = value;
}
TestConstructor.prototype.getValue = function() {
return this.value;
};
var testInstance = new TestConstructor("test");
validateConstructor(testInstance, TestConstructor);
// 构造函数链的跟踪
function Parent(name) {
console.log("Parent constructor:", name);
this.name = name;
}
function Child(name, age) {
console.log("Child constructor:", name, age);
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
console.log("=== 构造函数调用链 ===");
var child = new Child("Tom", 10);
validateConstructor(child, Child);
validateConstructor(child, Parent);
最佳实践总结:
How to implement deep copy?
How to implement deep copy?
考察点:对象复制和递归算法的理解。
答案:
深拷贝是指创建一个对象的完整副本,包括对象内部所有嵌套的对象和数组。与浅拷贝不同,深拷贝后的对象与原对象完全独立,修改其中一个不会影响另一个。
浅拷贝 vs 深拷贝的区别:
// 浅拷贝示例
var original = {
name: "John",
age: 30,
hobbies: ["reading", "swimming"],
address: {
city: "New York",
country: "USA"
}
};
// 浅拷贝
var shallowCopy = {};
for (var key in original) {
if (original.hasOwnProperty(key)) {
shallowCopy[key] = original[key];
}
}
// 修改嵌套对象
shallowCopy.address.city = "Los Angeles";
shallowCopy.hobbies.push("coding");
console.log(original.address.city); // "Los Angeles" - 原对象也被修改了
console.log(original.hobbies); // ["reading", "swimming", "coding"]
// 深拷贝应该避免这种情况
console.log("原对象被浅拷贝影响了");
1. 简单的 JSON 方法(有限制):
function jsonDeepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
// 测试 JSON 方法
var testObj = {
name: "Alice",
age: 25,
hobbies: ["music", "sports"],
address: {
street: "123 Main St",
city: "Boston"
}
};
var jsonCopy = jsonDeepCopy(testObj);
jsonCopy.address.city = "Chicago";
jsonCopy.hobbies.push("reading");
console.log(testObj.address.city); // "Boston" - 原对象未被修改
console.log(testObj.hobbies); // ["music", "sports"]
console.log(jsonCopy.address.city); // "Chicago"
// JSON 方法的限制
var complexObj = {
func: function() { return "function"; },
date: new Date(),
regex: /test/g,
undefined: undefined,
symbol: Symbol("test"),
null: null
};
console.log("原对象:", complexObj);
console.log("JSON 拷贝:", jsonDeepCopy(complexObj));
// 输出:{date: "2023-...", null: null}
// function、undefined、symbol、regex 等会丢失
2. 递归深拷贝实现:
function deepCopy(obj) {
// 处理基本类型和 null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理 Date 对象
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// 处理 Array
if (Array.isArray(obj)) {
var arrCopy = [];
for (var i = 0; i < obj.length; i++) {
arrCopy[i] = deepCopy(obj[i]);
}
return arrCopy;
}
// 处理普通对象
if (typeof obj === 'object') {
var objCopy = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
objCopy[key] = deepCopy(obj[key]);
}
}
return objCopy;
}
}
// 测试递归深拷贝
var testData = {
string: "hello",
number: 42,
boolean: true,
null: null,
undefined: undefined,
array: [1, 2, [3, 4]],
date: new Date(),
object: {
nested: {
deep: "value"
}
}
};
var recursiveCopy = deepCopy(testData);
recursiveCopy.array[2][0] = 999;
recursiveCopy.object.nested.deep = "modified";
console.log("原对象数组:", testData.array[2][0]); // 3
console.log("原对象嵌套:", testData.object.nested.deep); // "value"
console.log("拷贝后数组:", recursiveCopy.array[2][0]); // 999
console.log("拷贝后嵌套:", recursiveCopy.object.nested.deep); // "modified"
3. 完善的深拷贝实现(处理循环引用):
function advancedDeepCopy(obj, hash = new WeakMap()) {
// null 和基本类型直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理 Date 对象
if (obj instanceof Date) {
return new Date(obj);
}
// 处理 RegExp 对象
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理数组
if (Array.isArray(obj)) {
var arrCopy = [];
hash.set(obj, arrCopy); // 提前设置,防止循环引用
for (var i = 0; i < obj.length; i++) {
arrCopy[i] = advancedDeepCopy(obj[i], hash);
}
return arrCopy;
}
// 处理普通对象
var objCopy = {};
hash.set(obj, objCopy); // 提前设置,防止循环引用
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
objCopy[key] = advancedDeepCopy(obj[key], hash);
}
}
return objCopy;
}
// 测试循环引用
var circularObj = {
name: "circular"
};
circularObj.self = circularObj; // 创建循环引用
var circularArray = [1, 2];
circularArray[2] = circularArray; // 数组循环引用
try {
var copy1 = advancedDeepCopy(circularObj);
console.log(copy1.name); // "circular"
console.log(copy1.self === copy1); // true - 保持了循环引用结构
var copy2 = advancedDeepCopy(circularArray);
console.log(copy2[0]); // 1
console.log(copy2[2] === copy2); // true
} catch (e) {
console.log("处理循环引用成功");
}
4. 考虑更多数据类型的深拷贝:
function comprehensiveDeepCopy(obj, hash = new WeakMap()) {
// null 和基本类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 循环引用检查
if (hash.has(obj)) {
return hash.get(obj);
}
// 获取对象的确切类型
var type = Object.prototype.toString.call(obj);
var cloneObj;
switch (type) {
case '[object Date]':
cloneObj = new Date(obj);
break;
case '[object RegExp]':
cloneObj = new RegExp(obj);
break;
case '[object Array]':
cloneObj = [];
hash.set(obj, cloneObj);
for (var i = 0; i < obj.length; i++) {
cloneObj[i] = comprehensiveDeepCopy(obj[i], hash);
}
break;
case '[object Object]':
// 保持原型链
cloneObj = Object.create(Object.getPrototypeOf(obj));
hash.set(obj, cloneObj);
// 拷贝可枚举属性
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = comprehensiveDeepCopy(obj[key], hash);
}
}
// 拷贝不可枚举属性
var keys = Object.getOwnPropertyNames(obj);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!obj.propertyIsEnumerable(key) && key !== 'constructor') {
var descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor && typeof descriptor.value !== 'function') {
cloneObj[key] = comprehensiveDeepCopy(descriptor.value, hash);
}
}
}
break;
case '[object Function]':
// 函数的拷贝(简单实现)
cloneObj = new Function('return ' + obj.toString())();
break;
case '[object Map]':
cloneObj = new Map();
hash.set(obj, cloneObj);
obj.forEach(function(value, key) {
cloneObj.set(
comprehensiveDeepCopy(key, hash),
comprehensiveDeepCopy(value, hash)
);
});
break;
case '[object Set]':
cloneObj = new Set();
hash.set(obj, cloneObj);
obj.forEach(function(value) {
cloneObj.add(comprehensiveDeepCopy(value, hash));
});
break;
default:
// 其他类型,尝试使用构造函数创建
try {
cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = comprehensiveDeepCopy(obj[key], hash);
}
}
} catch (e) {
// 如果无法创建,返回原对象
cloneObj = obj;
}
}
return cloneObj;
}
// 测试各种数据类型
var complexData = {
string: "test",
number: 123,
boolean: true,
null: null,
undefined: undefined,
date: new Date(),
regexp: /test/gi,
array: [1, 2, {nested: "array"}],
map: new Map([['key1', 'value1'], ['key2', {nested: 'map'}]]),
set: new Set([1, 2, {nested: 'set'}]),
func: function() { return "function"; },
object: {
deep: {
deeper: "value"
}
}
};
var comprehensive = comprehensiveDeepCopy(complexData);
console.log("完整深拷贝测试:");
console.log("Date 类型:", comprehensive.date instanceof Date);
console.log("RegExp 类型:", comprehensive.regexp instanceof RegExp);
console.log("Map 类型:", comprehensive.map instanceof Map);
console.log("Set 类型:", comprehensive.set instanceof Set);
5. 基于递归的性能优化版本:
function optimizedDeepCopy(obj) {
// 使用栈模拟递归,避免栈溢出
var stack = [{source: obj, target: {}}];
var copyObj = stack[0].target;
var visited = new WeakMap();
visited.set(obj, copyObj);
while (stack.length > 0) {
var current = stack.pop();
var source = current.source;
var target = current.target;
for (var key in source) {
if (source.hasOwnProperty(key)) {
var value = source[key];
if (typeof value !== 'object' || value === null) {
target[key] = value;
} else if (visited.has(value)) {
target[key] = visited.get(value);
} else {
if (Array.isArray(value)) {
target[key] = [];
visited.set(value, target[key]);
stack.push({source: value, target: target[key]});
} else if (value instanceof Date) {
target[key] = new Date(value);
} else if (value instanceof RegExp) {
target[key] = new RegExp(value);
} else {
target[key] = {};
visited.set(value, target[key]);
stack.push({source: value, target: target[key]});
}
}
}
}
}
return copyObj;
}
// 性能测试
function performanceTest() {
// 创建大型测试对象
var largeObj = {
level1: {}
};
var current = largeObj.level1;
for (var i = 0; i < 1000; i++) {
current.next = {
value: i,
array: [1, 2, 3],
data: "data" + i
};
current = current.next;
}
console.time("递归深拷贝");
var copy1 = advancedDeepCopy(largeObj);
console.timeEnd("递归深拷贝");
console.time("栈优化深拷贝");
var copy2 = optimizedDeepCopy(largeObj);
console.timeEnd("栈优化深拷贝");
console.time("JSON 方法");
var copy3 = JSON.parse(JSON.stringify(largeObj));
console.timeEnd("JSON 方法");
}
performanceTest();
6. 实际应用中的深拷贝工具函数:
// 生产环境使用的深拷贝函数
function deepClone(source, target) {
target = target || {};
for (var key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null) {
// 区分数组和对象
target[key] = Array.isArray(source[key]) ? [] : {};
deepClone(source[key], target[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
// 带类型检查的深拷贝
function typedDeepCopy(obj) {
if (obj === null) return null;
if (typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(typedDeepCopy);
if (typeof obj === 'object') {
var copy = {};
Object.keys(obj).forEach(function(key) {
copy[key] = typedDeepCopy(obj[key]);
});
return copy;
}
}
// 基于 Object.assign 的浅拷贝对比
function shallowCopy(obj) {
if (Array.isArray(obj)) {
return obj.slice();
}
return Object.assign({}, obj);
}
// 测试不同拷贝方法
var testObject = {
name: "test",
details: {
age: 25,
hobbies: ["reading", "coding"]
}
};
console.log("=== 拷贝方法对比 ===");
var shallow = shallowCopy(testObject);
var deep = typedDeepCopy(testObject);
// 修改嵌套属性
shallow.details.age = 30;
console.log("浅拷贝后原对象年龄:", testObject.details.age); // 30
deep.details.hobbies.push("swimming");
console.log("深拷贝后原对象爱好:", testObject.details.hobbies); // ["reading", "coding"]
深拷贝的边界情况和注意事项:
// 处理特殊情况的深拷贝
function robustDeepCopy(obj, seen = new WeakSet()) {
// 防止循环引用导致的无限递归
if (seen.has(obj)) {
return {}; // 或者抛出错误
}
if (obj === null || typeof obj !== 'object') {
return obj;
}
// DOM 节点不应该被深拷贝
if (obj.nodeType && typeof obj.cloneNode === 'function') {
return obj.cloneNode(true);
}
// 函数直接返回(通常不需要拷贝)
if (typeof obj === 'function') {
return obj;
}
seen.add(obj);
try {
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Array) {
return obj.map(function(item) {
return robustDeepCopy(item, seen);
});
}
var copy = {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = robustDeepCopy(obj[key], seen);
}
}
return copy;
} finally {
seen.delete(obj);
}
}
// 测试边界情况
var edgeCase = {
window: typeof window !== 'undefined' ? window : null,
document: typeof document !== 'undefined' ? document : null,
func: function() { return "test"; },
regexp: /test/g,
date: new Date()
};
// 移除可能有问题的属性后测试
var safeEdgeCase = {
func: function() { return "test"; },
regexp: /test/g,
date: new Date(),
nested: {
value: "nested"
}
};
var edgeCopy = robustDeepCopy(safeEdgeCase);
console.log("边界情况测试:");
console.log("函数:", typeof edgeCopy.func);
console.log("正则:", edgeCopy.regexp instanceof RegExp);
console.log("日期:", edgeCopy.date instanceof Date);
深拷贝方法的选择指南:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| JSON.parse/stringify | 简单快速 | 不支持函数、undefined、Symbol等 | 简单数据结构 |
| 递归实现 | 功能完整、可控制 | 可能栈溢出 | 中等复杂度对象 |
| 迭代实现 | 避免栈溢出 | 实现复杂 | 大型深层对象 |
| 第三方库(lodash.cloneDeep) | 功能完善、性能优化 | 增加依赖 | 生产环境推荐 |
最佳实践:
What is functional programming? How to implement it in JavaScript?
What is functional programming? How to implement it in JavaScript?
考察点:编程范式和高阶函数的理解。
答案:
函数式编程是一种编程范式,它将计算视为数学函数的求值,强调函数的使用,避免改变状态和可变数据。在 JavaScript 中,函数是一等公民,这使得函数式编程成为可能。
函数式编程的核心概念:
// 1. 纯函数 - 相同输入总是产生相同输出,无副作用
function pureAdd(a, b) {
return a + b; // 纯函数:不修改外部状态,结果只依赖参数
}
console.log(pureAdd(2, 3)); // 5
console.log(pureAdd(2, 3)); // 5 - 相同输入,相同输出
// 非纯函数示例
var counter = 0;
function impureAdd(a, b) {
counter++; // 副作用:修改外部状态
return a + b + counter;
}
console.log(impureAdd(2, 3)); // 6
console.log(impureAdd(2, 3)); // 7 - 相同输入,不同输出
// 纯函数改造
function pureCounter(currentCount, a, b) {
return {
result: a + b + currentCount + 1,
newCount: currentCount + 1
};
}
var state = {count: 0};
var result1 = pureCounter(state.count, 2, 3);
console.log(result1); // {result: 6, newCount: 1}
2. 高阶函数 - 接受函数作为参数或返回函数的函数:
// 接受函数作为参数
function operate(a, b, operation) {
return operation(a, b);
}
function add(x, y) { return x + y; }
function multiply(x, y) { return x * y; }
console.log(operate(5, 3, add)); // 8
console.log(operate(5, 3, multiply)); // 15
// 返回函数
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
var double = createMultiplier(2);
var triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(4)); // 12
// 函数组合
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
function addOne(x) { return x + 1; }
function square(x) { return x * x; }
var addOneThenSquare = compose(square, addOne);
console.log(addOneThenSquare(3)); // (3+1)^2 = 16
3. 不可变性 - 避免修改现有数据:
// 错误的可变方式
var numbers = [1, 2, 3, 4, 5];
function addElementMutable(arr, element) {
arr.push(element); // 修改原数组
return arr;
}
var result = addElementMutable(numbers, 6);
console.log(numbers); // [1, 2, 3, 4, 5, 6] - 原数组被修改
// 正确的不可变方式
var numbers2 = [1, 2, 3, 4, 5];
function addElementImmutable(arr, element) {
return arr.concat([element]); // 返回新数组,不修改原数组
}
var result2 = addElementImmutable(numbers2, 6);
console.log(numbers2); // [1, 2, 3, 4, 5] - 原数组未修改
console.log(result2); // [1, 2, 3, 4, 5, 6] - 新数组
// 对象的不可变操作
function updatePersonImmutable(person, updates) {
// 使用 Object.assign 创建新对象
return Object.assign({}, person, updates);
}
// 或者手动拷贝
function updatePersonManual(person, updates) {
var newPerson = {};
for (var key in person) {
if (person.hasOwnProperty(key)) {
newPerson[key] = person[key];
}
}
for (var key in updates) {
if (updates.hasOwnProperty(key)) {
newPerson[key] = updates[key];
}
}
return newPerson;
}
var person = {name: "Alice", age: 25, city: "New York"};
var updatedPerson = updatePersonImmutable(person, {age: 26, city: "Boston"});
console.log(person); // {name: "Alice", age: 25, city: "New York"}
console.log(updatedPerson); // {name: "Alice", age: 26, city: "Boston"}
4. 常用的函数式编程方法:
// map - 转换数组中的每个元素
var numbers = [1, 2, 3, 4, 5];
var doubled = numbers.map(function(x) {
return x * 2;
});
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - 过滤数组元素
var evenNumbers = numbers.filter(function(x) {
return x % 2 === 0;
});
console.log(evenNumbers); // [2, 4]
// reduce - 将数组归约为单个值
var sum = numbers.reduce(function(acc, current) {
return acc + current;
}, 0);
console.log(sum); // 15
var product = numbers.reduce(function(acc, current) {
return acc * current;
}, 1);
console.log(product); // 120
// 函数式编程链式调用
var result = numbers
.filter(function(x) { return x % 2 === 0; }) // [2, 4]
.map(function(x) { return x * x; }) // [4, 16]
.reduce(function(acc, x) { return acc + x; }, 0); // 20
console.log(result); // 20
5. 实现自定义的函数式工具:
// 自定义 map 实现
function myMap(array, fn) {
var result = [];
for (var i = 0; i < array.length; i++) {
result.push(fn(array[i], i, array));
}
return result;
}
// 自定义 filter 实现
function myFilter(array, predicate) {
var result = [];
for (var i = 0; i < array.length; i++) {
if (predicate(array[i], i, array)) {
result.push(array[i]);
}
}
return result;
}
// 自定义 reduce 实现
function myReduce(array, fn, initialValue) {
var acc = initialValue;
var startIndex = 0;
if (acc === undefined) {
acc = array[0];
startIndex = 1;
}
for (var i = startIndex; i < array.length; i++) {
acc = fn(acc, array[i], i, array);
}
return acc;
}
// 测试自定义实现
var testArray = [1, 2, 3, 4, 5];
console.log(myMap(testArray, function(x) { return x * 2; })); // [2, 4, 6, 8, 10]
console.log(myFilter(testArray, function(x) { return x > 3; })); // [4, 5]
console.log(myReduce(testArray, function(acc, x) { return acc + x; }, 0)); // 15
6. 函数组合和管道:
// 函数组合 - 从右到左执行
function compose() {
var functions = Array.prototype.slice.call(arguments);
return function(x) {
return functions.reduceRight(function(acc, fn) {
return fn(acc);
}, x);
};
}
// 管道 - 从左到右执行
function pipe() {
var functions = Array.prototype.slice.call(arguments);
return function(x) {
return functions.reduce(function(acc, fn) {
return fn(acc);
}, x);
};
}
// 基础函数
function addOne(x) { return x + 1; }
function double(x) { return x * 2; }
function square(x) { return x * x; }
// 使用 compose (从右到左)
var composedFn = compose(square, double, addOne);
console.log(composedFn(3)); // ((3+1)*2)^2 = 64
// 使用 pipe (从左到右)
var pipedFn = pipe(addOne, double, square);
console.log(pipedFn(3)); // ((3+1)*2)^2 = 64
// 更复杂的函数组合示例
function trim(str) { return str.trim(); }
function toUpperCase(str) { return str.toUpperCase(); }
function addExclamation(str) { return str + '!'; }
var processString = pipe(trim, toUpperCase, addExclamation);
console.log(processString(" hello world ")); // "HELLO WORLD!"
// 数组处理的管道
var processNumbers = pipe(
function(arr) { return arr.filter(function(x) { return x > 0; }); },
function(arr) { return arr.map(function(x) { return x * 2; }); },
function(arr) { return arr.reduce(function(acc, x) { return acc + x; }, 0); }
);
console.log(processNumbers([-1, 2, -3, 4, 5])); // (2+4+5)*2 = 22
7. 记忆化 (Memoization):
// 记忆化装饰器
function memoize(fn) {
var cache = {};
return function() {
var key = JSON.stringify(arguments);
if (cache[key]) {
console.log('从缓存返回:', key);
return cache[key];
}
var result = fn.apply(this, arguments);
cache[key] = result;
console.log('计算并缓存:', key);
return result;
};
}
// 斐波那契数列(递归版本)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 记忆化版本
var memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(10)); // 计算并缓存多个值
console.log(memoizedFibonacci(10)); // 从缓存返回
// 阶乘函数的记忆化
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
var memoizedFactorial = memoize(factorial);
console.log(memoizedFactorial(5)); // 120
console.log(memoizedFactorial(5)); // 从缓存返回
// 更复杂的记忆化,支持对象参数
function advancedMemoize(fn, keyGenerator) {
var cache = new Map();
return function() {
var key = keyGenerator ? keyGenerator.apply(this, arguments) : JSON.stringify(arguments);
if (cache.has(key)) {
return cache.get(key);
}
var result = fn.apply(this, arguments);
cache.set(key, result);
return result;
};
}
8. 函数式编程的实际应用:
// 数据处理管道
var students = [
{name: 'Alice', score: 85, subject: 'Math'},
{name: 'Bob', score: 92, subject: 'Physics'},
{name: 'Charlie', score: 78, subject: 'Math'},
{name: 'David', score: 96, subject: 'Physics'},
{name: 'Eve', score: 88, subject: 'Math'}
];
// 函数式方法处理数据
var mathStudentsAverage = students
.filter(function(student) {
return student.subject === 'Math';
})
.map(function(student) {
return student.score;
})
.reduce(function(sum, score, index, array) {
return index === array.length - 1 ?
(sum + score) / array.length :
sum + score;
}, 0);
console.log('数学平均分:', mathStudentsAverage); // 83.67
// 函数式表单验证
function createValidator(rules) {
return function(data) {
return rules.reduce(function(errors, rule) {
var fieldErrors = rule(data);
return errors.concat(fieldErrors);
}, []);
};
}
function required(fieldName) {
return function(data) {
return data[fieldName] ? [] : [fieldName + ' is required'];
};
}
function minLength(fieldName, min) {
return function(data) {
return data[fieldName] && data[fieldName].length >= min ?
[] : [fieldName + ' must be at least ' + min + ' characters'];
};
}
function email(fieldName) {
return function(data) {
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return data[fieldName] && emailRegex.test(data[fieldName]) ?
[] : [fieldName + ' must be a valid email'];
};
}
var userValidator = createValidator([
required('name'),
required('email'),
minLength('name', 2),
email('email')
]);
var userData = {name: 'A', email: 'invalid'};
var errors = userValidator(userData);
console.log('验证错误:', errors);
// 状态管理的函数式方法
function createStore(reducer, initialState) {
var state = initialState;
var listeners = [];
return {
getState: function() {
return state;
},
dispatch: function(action) {
state = reducer(state, action);
listeners.forEach(function(listener) {
listener(state);
});
},
subscribe: function(listener) {
listeners.push(listener);
return function() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
};
}
function counterReducer(state, action) {
state = state || {count: 0};
switch (action.type) {
case 'INCREMENT':
return Object.assign({}, state, {count: state.count + 1});
case 'DECREMENT':
return Object.assign({}, state, {count: state.count - 1});
default:
return state;
}
}
var store = createStore(counterReducer, {count: 0});
store.subscribe(function(state) {
console.log('状态更新:', state);
});
store.dispatch({type: 'INCREMENT'}); // 状态更新: {count: 1}
store.dispatch({type: 'INCREMENT'}); // 状态更新: {count: 2}
store.dispatch({type: 'DECREMENT'}); // 状态更新: {count: 1}
函数式编程的优势与注意事项:
优势:
注意事项:
最佳实践:
What is currying? How to implement it?
What is currying? How to implement it?
考察点:函数式编程技巧和闭包应用。
答案:
柯里化(Currying)是函数式编程中的一种技术,它将接受多个参数的函数转换为一系列只接受一个参数的函数。柯里化的名称来源于数学家 Haskell Curry。
柯里化的基本概念:
// 普通的多参数函数
function add(a, b, c) {
return a + b + c;
}
console.log(add(1, 2, 3)); // 6
// 手动柯里化版本
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(curriedAdd(1)(2)(3)); // 6
// 或者使用箭头函数的简化写法(概念展示)
var curriedAddArrow = function(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
};
通用柯里化函数的实现:
// 基础柯里化实现
function curry(fn) {
return function curried() {
var args = Array.prototype.slice.call(arguments);
// 如果参数足够,直接执行原函数
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
// 参数不够,返回新函数等待更多参数
return function() {
var nextArgs = Array.prototype.slice.call(arguments);
return curried.apply(this, args.concat(nextArgs));
};
}
};
}
// 测试基础柯里化
function multiply(a, b, c) {
return a * b * c;
}
var curriedMultiply = curry(multiply);
console.log(curriedMultiply(2)(3)(4)); // 24
console.log(curriedMultiply(2, 3)(4)); // 24
console.log(curriedMultiply(2)(3, 4)); // 24
console.log(curriedMultiply(2, 3, 4)); // 24
// 部分应用
var double = curriedMultiply(2);
var triple = curriedMultiply(3);
console.log(double(5)(6)); // 60 (2 * 5 * 6)
console.log(triple(4)(5)); // 60 (3 * 4 * 5)
增强版柯里化实现:
// 支持占位符的柯里化
function advancedCurry(fn, arity) {
arity = arity || fn.length;
return function curried() {
var args = Array.prototype.slice.call(arguments);
if (args.length >= arity) {
return fn.apply(this, args);
}
return function() {
var nextArgs = Array.prototype.slice.call(arguments);
return curried.apply(this, args.concat(nextArgs));
};
};
}
// 带占位符的柯里化(使用 Symbol 作为占位符)
var _ = {}; // 占位符对象
function curryWithPlaceholder(fn) {
return function curried() {
var args = Array.prototype.slice.call(arguments);
// 如果没有占位符且参数足够,执行函数
if (args.length >= fn.length && args.indexOf(_) === -1) {
return fn.apply(this, args);
}
return function() {
var nextArgs = Array.prototype.slice.call(arguments);
var newArgs = [];
var nextIndex = 0;
// 替换占位符
for (var i = 0; i < args.length; i++) {
if (args[i] === _ && nextIndex < nextArgs.length) {
newArgs.push(nextArgs[nextIndex++]);
} else {
newArgs.push(args[i]);
}
}
// 添加剩余参数
while (nextIndex < nextArgs.length) {
newArgs.push(nextArgs[nextIndex++]);
}
return curried.apply(this, newArgs);
};
};
}
// 测试占位符柯里化
function subtract(a, b, c) {
return a - b - c;
}
var curriedSubtract = curryWithPlaceholder(subtract);
console.log(curriedSubtract(10, _, 2)(5)); // 3 (10 - 5 - 2)
console.log(curriedSubtract(_, 3, _)(10, 2)); // 5 (10 - 3 - 2)
柯里化的实际应用:
// 1. 配置函数
function createLogger(level) {
return function(message) {
return function(timestamp) {
return '[' + timestamp + '] [' + level + '] ' + message;
};
};
}
var errorLogger = createLogger('ERROR');
var warnLogger = createLogger('WARN');
var infoLogger = createLogger('INFO');
console.log(errorLogger('System crashed')('2023-12-01 10:30:00'));
// [2023-12-01 10:30:00] [ERROR] System crashed
// 使用柯里化自动化
var curriedLogger = curry(function(level, message, timestamp) {
return '[' + timestamp + '] [' + level + '] ' + message;
});
var logError = curriedLogger('ERROR');
var logWarn = curriedLogger('WARN');
console.log(logError('Database connection failed')('2023-12-01 10:31:00'));
// 2. 数据验证
function validate(rule, errorMessage, value) {
return rule(value) ? null : errorMessage;
}
var curriedValidate = curry(validate);
// 创建特定验证器
var validateRequired = curriedValidate(function(val) {
return val != null && val !== '';
}, 'Field is required');
var validateEmail = curriedValidate(function(val) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
}, 'Invalid email format');
var validateMinLength = function(min) {
return curriedValidate(function(val) {
return val && val.length >= min;
}, 'Minimum length is ' + min);
};
// 使用验证器
console.log(validateRequired('[email protected]')); // null (有效)
console.log(validateRequired('')); // 'Field is required'
console.log(validateEmail('invalid-email')); // 'Invalid email format'
console.log(validateMinLength(8)('short')); // 'Minimum length is 8'
// 3. 数学运算
function mathOperation(operation, a, b) {
switch (operation) {
case 'add': return a + b;
case 'subtract': return a - b;
case 'multiply': return a * b;
case 'divide': return a / b;
default: return NaN;
}
}
var curriedMath = curry(mathOperation);
var add = curriedMath('add');
var subtract = curriedMath('subtract');
var multiply = curriedMath('multiply');
var divide = curriedMath('divide');
console.log(add(5)(3)); // 8
console.log(multiply(4)(6)); // 24
// 批量操作
var numbers = [1, 2, 3, 4, 5];
var doubledNumbers = numbers.map(multiply(2));
var addTen = add(10);
var incrementedNumbers = numbers.map(addTen);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
console.log(incrementedNumbers); // [11, 12, 13, 14, 15]
柯里化与数组操作:
// 柯里化 map 函数
var curriedMap = curry(function(fn, array) {
return array.map(fn);
});
// 柯里化 filter 函数
var curriedFilter = curry(function(predicate, array) {
return array.filter(predicate);
});
// 柯里化 reduce 函数
var curriedReduce = curry(function(fn, initialValue, array) {
return array.reduce(fn, initialValue);
});
// 创建专用函数
var mapDouble = curriedMap(function(x) { return x * 2; });
var filterEven = curriedFilter(function(x) { return x % 2 === 0; });
var sum = curriedReduce(function(acc, x) { return acc + x; }, 0);
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(mapDouble(numbers)); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
console.log(filterEven(numbers)); // [2, 4, 6, 8, 10]
console.log(sum(numbers)); // 55
// 函数组合
function pipe() {
var functions = Array.prototype.slice.call(arguments);
return function(value) {
return functions.reduce(function(acc, fn) {
return fn(acc);
}, value);
};
}
var processNumbers = pipe(
filterEven,
mapDouble,
sum
);
console.log(processNumbers(numbers)); // 60 ((2+4+6+8+10) * 2)
柯里化在事件处理中的应用:
// DOM 事件处理的柯里化
function handleEvent(eventType, callback, element) {
element.addEventListener(eventType, callback);
}
var curriedHandleEvent = curry(handleEvent);
// 创建专用事件处理器
var onClick = curriedHandleEvent('click');
var onMouseOver = curriedHandleEvent('mouseover');
// 专用回调
var showAlert = function(message) {
return function() {
alert(message);
};
};
var logClick = function(elementName) {
return function(event) {
console.log(elementName + ' clicked', event);
};
};
// 模拟 DOM 元素
var button = {
addEventListener: function(type, callback) {
console.log('Added ' + type + ' listener to button');
// 模拟触发
setTimeout(callback, 100);
}
};
var link = {
addEventListener: function(type, callback) {
console.log('Added ' + type + ' listener to link');
}
};
// 使用柯里化的事件处理
onClick(showAlert('Button clicked!'))(button);
onClick(logClick('Navigation Link'))(link);
// HTTP 请求的柯里化
function makeRequest(method, url, data) {
return new Promise(function(resolve) {
// 模拟 HTTP 请求
setTimeout(function() {
resolve({
method: method,
url: url,
data: data,
status: 200
});
}, 1000);
});
}
var curriedRequest = curry(makeRequest);
var get = curriedRequest('GET');
var post = curriedRequest('POST');
var put = curriedRequest('PUT');
// 创建专用 API 调用
var getUser = get('/api/users/');
var postUser = post('/api/users');
getUser(null).then(function(response) {
console.log('GET response:', response);
});
postUser({name: 'John', email: '[email protected]'}).then(function(response) {
console.log('POST response:', response);
});
柯里化与函数组合的高级应用:
// 创建一个数据处理管道
var data = [
{name: 'Alice', age: 25, department: 'Engineering'},
{name: 'Bob', age: 30, department: 'Marketing'},
{name: 'Charlie', age: 35, department: 'Engineering'},
{name: 'Diana', age: 28, department: 'Sales'},
{name: 'Eve', age: 32, department: 'Engineering'}
];
// 柯里化的辅助函数
var prop = curry(function(property, obj) {
return obj[property];
});
var equals = curry(function(expected, actual) {
return expected === actual;
});
var gt = curry(function(threshold, value) {
return value > threshold;
});
var where = curry(function(predicate, array) {
return array.filter(predicate);
});
var pluck = curry(function(property, array) {
return array.map(prop(property));
});
// 构建复杂查询
var getEngineers = where(function(person) {
return equals('Engineering')(prop('department')(person));
});
var getSeniorEmployees = where(function(person) {
return gt(30)(prop('age')(person));
});
var getName = prop('name');
// 组合查询
var seniorEngineers = pipe(
getEngineers,
getSeniorEmployees,
pluck('name')
);
console.log(seniorEngineers(data)); // ['Charlie', 'Eve']
// 动态构建查询
function createQuery(department, minAge) {
return pipe(
where(function(person) { return person.department === department; }),
where(function(person) { return person.age >= minAge; }),
pluck('name')
);
}
var seniorMarketers = createQuery('Marketing', 25);
var seniorSales = createQuery('Sales', 25);
console.log(seniorMarketers(data)); // ['Bob']
console.log(seniorSales(data)); // ['Diana']
柯里化的性能考虑:
// 性能测试
function normalAdd(a, b, c, d) {
return a + b + c + d;
}
var curriedAdd4 = curry(normalAdd);
// 性能比较
function performanceTest() {
var iterations = 1000000;
console.time('Normal function');
for (var i = 0; i < iterations; i++) {
normalAdd(1, 2, 3, 4);
}
console.timeEnd('Normal function');
console.time('Curried function (all at once)');
for (var i = 0; i < iterations; i++) {
curriedAdd4(1, 2, 3, 4);
}
console.timeEnd('Curried function (all at once)');
console.time('Curried function (one by one)');
for (var i = 0; i < iterations; i++) {
curriedAdd4(1)(2)(3)(4);
}
console.timeEnd('Curried function (one by one)');
}
performanceTest();
// 优化的柯里化实现
function optimizedCurry(fn, arity) {
arity = arity || fn.length;
function curryWrapper(args) {
return function() {
var newArgs = args.concat(Array.prototype.slice.call(arguments));
if (newArgs.length >= arity) {
return fn.apply(this, newArgs);
}
return curryWrapper(newArgs);
};
}
return curryWrapper([]);
}
var optimizedCurriedAdd = optimizedCurry(normalAdd);
console.log(optimizedCurriedAdd(1)(2)(3)(4)); // 10
柯里化的优缺点:
优点:
缺点:
最佳实践:
What are debounce and throttle? How to implement them?
What are debounce and throttle? How to implement them?
考察点:性能优化和事件处理的理解。
答案:
防抖(Debounce)和节流(Throttle)是两种重要的性能优化技术,用于控制函数的执行频率,特别是在处理高频事件时。
防抖 (Debounce) 的概念和实现:
防抖是指在事件被触发后,延迟一段时间再执行回调函数。如果在延迟期间又有事件触发,则重新计时。简单来说就是"等你不触发了,我再执行"。
// 基础防抖实现
function debounce(func, delay) {
var timeoutId;
return function() {
var context = this;
var args = arguments;
// 清除之前的定时器
clearTimeout(timeoutId);
// 设置新的定时器
timeoutId = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}
// 使用示例
function searchAPI(query) {
console.log('搜索: ' + query);
}
var debouncedSearch = debounce(searchAPI, 300);
// 模拟用户输入
debouncedSearch('a'); // 不会立即执行
debouncedSearch('ab'); // 取消上一次,重新计时
debouncedSearch('abc'); // 取消上一次,重新计时
// 300ms 后才会执行 searchAPI('abc')
立即执行版防抖:
// 支持立即执行的防抖
function debounceImmediate(func, delay, immediate) {
var timeoutId;
return function() {
var context = this;
var args = arguments;
var callNow = immediate && !timeoutId;
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
timeoutId = null;
if (!immediate) {
func.apply(context, args);
}
}, delay);
if (callNow) {
func.apply(context, args);
}
};
}
// 测试立即执行防抖
function handleClick() {
console.log('按钮被点击了', new Date().toLocaleTimeString());
}
var debouncedClick = debounceImmediate(handleClick, 1000, true);
// 模拟多次快速点击
debouncedClick(); // 立即执行
debouncedClick(); // 不执行
debouncedClick(); // 不执行
// 1秒后可以再次触发
可取消的防抖:
// 带取消功能的防抖
function debounceWithCancel(func, delay) {
var timeoutId;
function debounced() {
var context = this;
var args = arguments;
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
func.apply(context, args);
}, delay);
}
debounced.cancel = function() {
clearTimeout(timeoutId);
timeoutId = null;
};
return debounced;
}
// 使用可取消的防抖
var debouncedSave = debounceWithCancel(function(data) {
console.log('保存数据:', data);
}, 2000);
debouncedSave('test data');
// 可以手动取消
// debouncedSave.cancel();
节流 (Throttle) 的概念和实现:
节流是指限制函数在一定时间内只能执行一次。无论触发多少次,都按照固定的频率执行。简单来说就是"我有自己的节奏,不管你触发多频繁"。
// 基础节流实现(时间戳版)
function throttleTimestamp(func, delay) {
var lastExecTime = 0;
return function() {
var context = this;
var args = arguments;
var currentTime = Date.now();
if (currentTime - lastExecTime >= delay) {
func.apply(context, args);
lastExecTime = currentTime;
}
};
}
// 基础节流实现(定时器版)
function throttleTimer(func, delay) {
var timeoutId;
return function() {
var context = this;
var args = arguments;
if (!timeoutId) {
timeoutId = setTimeout(function() {
func.apply(context, args);
timeoutId = null;
}, delay);
}
};
}
// 使用示例
function handleScroll() {
console.log('滚动事件触发', new Date().toLocaleTimeString());
}
var throttledScroll = throttleTimestamp(handleScroll, 100);
// 模拟快速滚动
for (var i = 0; i < 10; i++) {
setTimeout(function() {
throttledScroll();
}, i * 10);
}
完整版节流实现(支持首尾执行控制):
// 完整版节流实现
function throttle(func, delay, options) {
options = options || {};
var timeoutId;
var lastExecTime = 0;
var leading = options.leading !== false; // 首次是否立即执行
var trailing = options.trailing !== false; // 结束后是否再执行一次
return function() {
var context = this;
var args = arguments;
var currentTime = Date.now();
// 如果是第一次调用且不允许立即执行
if (!lastExecTime && !leading) {
lastExecTime = currentTime;
}
var remaining = delay - (currentTime - lastExecTime);
if (remaining <= 0 || remaining > delay) {
// 可以执行
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
lastExecTime = currentTime;
func.apply(context, args);
} else if (!timeoutId && trailing) {
// 设置定时器,在剩余时间后执行
timeoutId = setTimeout(function() {
lastExecTime = leading === false ? 0 : Date.now();
timeoutId = null;
func.apply(context, args);
}, remaining);
}
};
}
// 测试不同配置
function logMessage(msg) {
console.log(msg, new Date().toLocaleTimeString());
}
var throttledLeading = throttle(function() {
logMessage('立即执行,不在结尾执行');
}, 1000, { leading: true, trailing: false });
var throttledTrailing = throttle(function() {
logMessage('不立即执行,在结尾执行');
}, 1000, { leading: false, trailing: true });
var throttledBoth = throttle(function() {
logMessage('立即执行,也在结尾执行');
}, 1000, { leading: true, trailing: true });
可取消的节流:
// 带取消功能的节流
function throttleWithCancel(func, delay) {
var timeoutId;
var lastExecTime = 0;
function throttled() {
var context = this;
var args = arguments;
var currentTime = Date.now();
if (currentTime - lastExecTime >= delay) {
func.apply(context, args);
lastExecTime = currentTime;
} else if (!timeoutId) {
timeoutId = setTimeout(function() {
func.apply(context, args);
lastExecTime = Date.now();
timeoutId = null;
}, delay - (currentTime - lastExecTime));
}
}
throttled.cancel = function() {
clearTimeout(timeoutId);
timeoutId = null;
lastExecTime = 0;
};
return throttled;
}
// 使用可取消的节流
var throttledResize = throttleWithCancel(function() {
console.log('窗口大小改变');
}, 200);
// 可以手动取消
// throttledResize.cancel();
实际应用场景:
// 1. 搜索框输入防抖
function setupSearchBox() {
var searchInput = document.getElementById('search');
var debouncedSearch = debounce(function(event) {
var query = event.target.value;
if (query.length >= 2) {
// 发起搜索请求
fetchSearchResults(query);
}
}, 300);
searchInput.addEventListener('input', debouncedSearch);
}
function fetchSearchResults(query) {
console.log('搜索:', query);
// 实际的 API 调用
// fetch('/api/search?q=' + encodeURIComponent(query))
}
// 2. 按钮防重复点击
function setupSubmitButton() {
var submitBtn = document.getElementById('submit');
var debouncedSubmit = debounce(function() {
submitForm();
}, 1000, true); // 立即执行防抖
submitBtn.addEventListener('click', debouncedSubmit);
}
function submitForm() {
console.log('提交表单');
// 实际的表单提交逻辑
}
// 3. 滚动事件节流
function setupScrollHandler() {
var throttledScroll = throttle(function() {
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 更新导航栏状态
updateNavbar(scrollTop);
// 懒加载图片
lazyLoadImages(scrollTop);
// 滚动到顶部按钮显示/隐藏
toggleBackToTop(scrollTop);
}, 16); // 大约 60fps
window.addEventListener('scroll', throttledScroll);
}
function updateNavbar(scrollTop) {
console.log('更新导航栏', scrollTop);
}
function lazyLoadImages(scrollTop) {
console.log('懒加载图片', scrollTop);
}
function toggleBackToTop(scrollTop) {
console.log('切换返回顶部按钮', scrollTop > 300 ? '显示' : '隐藏');
}
// 4. 窗口大小改变节流
function setupResizeHandler() {
var throttledResize = throttle(function() {
var width = window.innerWidth;
var height = window.innerHeight;
// 重新计算布局
recalculateLayout(width, height);
// 更新图表大小
resizeCharts(width, height);
}, 100);
window.addEventListener('resize', throttledResize);
}
function recalculateLayout(width, height) {
console.log('重新计算布局', width, 'x', height);
}
function resizeCharts(width, height) {
console.log('调整图表大小', width, 'x', height);
}
// 5. API 请求频率控制
function createAPIThrottle() {
var apiCache = {};
return function throttledAPI(endpoint, params) {
var cacheKey = endpoint + JSON.stringify(params);
var now = Date.now();
// 检查缓存
if (apiCache[cacheKey] && now - apiCache[cacheKey].timestamp < 5000) {
console.log('返回缓存结果');
return Promise.resolve(apiCache[cacheKey].data);
}
// 发起新请求
return fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
// 更新缓存
apiCache[cacheKey] = {
data: data,
timestamp: now
};
return data;
});
};
}
var throttledAPI = createAPIThrottle();
防抖和节流的性能测试:
// 性能测试函数
function performanceTest() {
var callCount = 0;
var testFunction = function() {
callCount++;
};
// 原始函数测试
console.time('原始函数');
for (var i = 0; i < 100000; i++) {
testFunction();
}
console.timeEnd('原始函数');
console.log('原始函数调用次数:', callCount);
// 防抖函数测试
callCount = 0;
var debouncedFunc = debounce(testFunction, 10);
console.time('防抖函数');
for (var i = 0; i < 100000; i++) {
debouncedFunc();
}
setTimeout(function() {
console.timeEnd('防抖函数');
console.log('防抖函数调用次数:', callCount);
// 节流函数测试
callCount = 0;
var throttledFunc = throttle(testFunction, 10);
console.time('节流函数');
for (var i = 0; i < 1000; i++) {
setTimeout(function() {
throttledFunc();
}, i);
}
setTimeout(function() {
console.timeEnd('节流函数');
console.log('节流函数调用次数:', callCount);
}, 2000);
}, 100);
}
// performanceTest();
高级应用:组合防抖和节流:
// 组合防抖和节流的高级用法
function createSmartHandler(func, options) {
options = options || {};
var debounceDelay = options.debounceDelay || 0;
var throttleDelay = options.throttleDelay || 0;
var handler = func;
// 先应用节流
if (throttleDelay > 0) {
handler = throttle(handler, throttleDelay);
}
// 再应用防抖
if (debounceDelay > 0) {
handler = debounce(handler, debounceDelay);
}
return handler;
}
// 智能搜索:节流 + 防抖
var smartSearch = createSmartHandler(function(query) {
console.log('执行搜索:', query);
}, {
throttleDelay: 100, // 最多100ms执行一次
debounceDelay: 300 // 停止输入300ms后执行
});
// 智能滚动处理
var smartScroll = createSmartHandler(function() {
console.log('处理滚动事件');
}, {
throttleDelay: 16 // 只使用节流,60fps
});
防抖和节流的区别总结:
| 特性 | 防抖 (Debounce) | 节流 (Throttle) |
|---|---|---|
| 执行时机 | 事件停止触发后延迟执行 | 按固定频率执行 |
| 触发频率 | 可能不执行或只执行一次 | 固定间隔必定执行 |
| 适用场景 | 搜索框输入、按钮防重复点击、窗口resize | 滚动事件、鼠标移动、页面滚动 |
| 性能影响 | 可能完全不执行,节省性能 | 保证最小执行频率 |
| 用户体验 | 避免不必要的请求 | 保证功能响应性 |
最佳实践:
选择合适的延迟时间:
考虑用户体验:
内存管理:
错误处理:
What is the garbage collection mechanism in JavaScript?
What is the garbage collection mechanism in JavaScript?
考察点:内存管理和性能优化的理解。
答案:
JavaScript 的垃圾回收(Garbage Collection, GC)是自动内存管理机制,负责自动释放不再使用的内存空间,防止内存泄漏。
垃圾回收的基本概念:
// 内存分配和回收的基本示例
function createObjects() {
var obj1 = { name: 'Alice', age: 25 }; // 分配内存
var obj2 = { name: 'Bob', age: 30 }; // 分配内存
obj1.friend = obj2; // 建立引用关系
obj2.friend = obj1; // 相互引用
return obj1;
// 函数执行完毕后,如果返回的 obj1 没有被外部引用
// 那么 obj1 和 obj2 都可能被垃圾回收
}
var result = createObjects(); // result 持有对象的引用
result = null; // 释放引用,对象变得可回收
主要的垃圾回收算法:
1. 引用计数 (Reference Counting):
// 引用计数的基本原理
function referenceCountingExample() {
var obj = { data: 'some data' }; // 引用计数: 1
var ref1 = obj; // 引用计数: 2
var ref2 = obj; // 引用计数: 3
ref1 = null; // 引用计数: 2
ref2 = null; // 引用计数: 1
obj = null; // 引用计数: 0,可以被回收
}
// 引用计数的问题:循环引用
function circularReferenceIssue() {
var objA = {};
var objB = {};
objA.ref = objB; // objB 引用计数: 1
objB.ref = objA; // objA 引用计数: 1
// 即使没有其他引用,引用计数永远不会为0
// 在纯引用计数算法中,这些对象永远不会被回收
// 解决方法:手动断开引用
objA.ref = null;
objB.ref = null;
}
// IE 早期版本的 DOM 内存泄漏示例
function domMemoryLeak() {
var element = document.getElementById('myElement');
var data = { element: element };
element.onclick = function() {
// 这个函数持有 data 的引用
console.log(data);
};
// DOM 元素和 JavaScript 对象相互引用
// 在老版本 IE 中可能导致内存泄漏
// 修复方法
element.onclick = null; // 断开事件处理器
data.element = null; // 断开对 DOM 的引用
}
2. 标记清除 (Mark and Sweep) - 现代主流算法:
// 标记清除算法的工作原理演示
function markAndSweepDemo() {
// 阶段1:标记阶段 - 从根对象开始标记所有可达对象
var global = window; // 根对象
var reachableObj = {
name: 'reachable',
data: [1, 2, 3]
};
var unreachableObj = {
name: 'unreachable',
data: [4, 5, 6]
};
global.myGlobal = reachableObj; // 可从根对象到达
var temp = unreachableObj;
temp = null; // unreachableObj 变得不可达
// 在垃圾回收时:
// 1. 标记阶段:从 global 开始,标记 reachableObj 及其属性
// 2. 清除阶段:回收所有未被标记的对象(如 unreachableObj)
global.myGlobal = null; // 使 reachableObj 也变得不可达
}
// 复杂对象引用的标记清除
function complexReferenceDemo() {
function createComplexStructure() {
var parent = {
name: 'parent',
children: []
};
var child1 = {
name: 'child1',
parent: parent
};
var child2 = {
name: 'child2',
parent: parent,
sibling: child1
};
parent.children.push(child1, child2);
child1.sibling = child2;
return parent;
}
var structure = createComplexStructure();
// 所有对象都可以从 structure 到达,不会被回收
structure = null;
// 现在整个结构都变得不可达,可以被回收
// 尽管内部有循环引用,标记清除算法仍能正确处理
}
3. 分代垃圾回收 (Generational GC):
// 分代垃圾回收的概念演示
function generationalGCDemo() {
// 新生代对象(年轻对象)
function createYoungObjects() {
var tempArray = [];
for (var i = 0; i < 1000; i++) {
tempArray.push({
id: i,
data: new Array(100).fill(i)
});
}
// 这些对象很快就会变得不可达
// 在新生代中频繁回收
return tempArray.slice(0, 10); // 只返回少量对象
}
// 老生代对象(长期存活对象)
var longLivedData = {
cache: new Map(),
config: {
apiUrl: 'https://api.example.com',
timeout: 5000
},
stats: {
requests: 0,
errors: 0
}
};
// 这个对象存活时间长,会被移到老生代
// 老生代回收频率较低,但更彻底
for (var i = 0; i < 100; i++) {
var youngObjs = createYoungObjects();
longLivedData.stats.requests += youngObjs.length;
// youngObjs 在循环结束后变得不可达
// 会在新生代中快速回收
}
return longLivedData;
}
4. 增量垃圾回收 (Incremental GC):
// 增量垃圾回收的影响演示
function incrementalGCDemo() {
var largeDataSets = [];
// 创建大量数据,可能触发垃圾回收
function createLargeDataSet() {
var data = [];
for (var i = 0; i < 10000; i++) {
data.push({
id: i,
payload: new Array(1000).fill('x'.repeat(100))
});
}
return data;
}
// 模拟增量垃圾回收的工作方式
function simulateIncrementalWork() {
var start = Date.now();
for (var i = 0; i < 5; i++) {
// 分批创建数据,避免长时间阻塞
largeDataSets.push(createLargeDataSet());
// 模拟其他工作
var current = Date.now();
console.log('第', i + 1, '批数据创建完成,耗时:', current - start, 'ms');
// 现代 JavaScript 引擎会在适当时机进行增量垃圾回收
// 避免长时间暂停用户界面
}
// 清理数据
largeDataSets = [];
}
simulateIncrementalWork();
}
内存泄漏的常见原因和解决方案:
// 1. 全局变量导致的内存泄漏
function globalVariableLeak() {
// 问题代码
function problematicFunction() {
// 意外创建全局变量
leakedVar = 'This becomes global'; // 忘记使用 var
this.property = 'Another leak'; // 在非严格模式下
}
problematicFunction();
// 解决方案
function fixedFunction() {
'use strict'; // 使用严格模式
var localVar = 'This stays local';
// 或者明确使用 var 声明
var anotherLocal = 'Also local';
}
fixedFunction();
}
// 2. 定时器导致的内存泄漏
function timerLeak() {
var largeData = new Array(1000000).fill('large data');
// 问题代码
var intervalId = setInterval(function() {
// 这个函数持有对 largeData 的引用
if (largeData.length > 0) {
console.log('Processing data...');
}
}, 1000);
// 忘记清除定时器导致 largeData 无法被回收
// 解决方案
setTimeout(function() {
clearInterval(intervalId);
largeData = null; // 显式清除引用
}, 10000);
}
// 3. 事件监听器导致的内存泄漏
function eventListenerLeak() {
function setupEventHandlers() {
var largeObject = {
data: new Array(100000).fill('data')
};
var button = document.getElementById('myButton');
// 问题代码
button.addEventListener('click', function() {
// 持有对 largeObject 的引用
console.log('Object size:', largeObject.data.length);
});
// 当页面或组件销毁时,如果没有移除监听器
// largeObject 将无法被回收
// 解决方案
function handleClick() {
console.log('Button clicked');
}
button.addEventListener('click', handleClick);
// 在适当的时候移除监听器
return function cleanup() {
button.removeEventListener('click', handleClick);
largeObject = null;
};
}
var cleanup = setupEventHandlers();
// 在组件销毁时调用清理函数
// cleanup();
}
// 4. 闭包导致的内存泄漏
function closureLeak() {
function createHandler() {
var largeData = new Array(1000000).fill('large');
var smallData = 'small';
// 问题代码:闭包持有整个作用域
return function(type) {
if (type === 'small') {
return smallData; // 只需要 smallData
}
// 但是 largeData 也被持有,无法回收
};
}
// 解决方案:分离关注点
function createOptimizedHandler() {
var smallData = 'small';
// 将大数据分离到另一个作用域
(function() {
var largeData = new Array(1000000).fill('large');
// 处理大数据的逻辑
processLargeData(largeData);
// 函数执行完毕后 largeData 可以被回收
})();
return function(type) {
if (type === 'small') {
return smallData;
}
};
}
function processLargeData(data) {
console.log('Processing', data.length, 'items');
}
var handler = createOptimizedHandler();
}
// 5. DOM 引用导致的内存泄漏
function domReferenceLeak() {
var elements = [];
function addElement() {
var div = document.createElement('div');
div.innerHTML = 'Content';
document.body.appendChild(div);
// 问题:即使元素从 DOM 中移除,仍被 elements 数组引用
elements.push(div);
}
function removeElement() {
var div = elements.pop();
if (div && div.parentNode) {
div.parentNode.removeChild(div);
}
// div 仍然被局部变量引用,需要显式清除
div = null;
}
// 更好的解决方案:使用 WeakMap 或及时清理引用
var elementWeakMap = new WeakMap();
function addElementOptimized() {
var div = document.createElement('div');
div.innerHTML = 'Content';
document.body.appendChild(div);
// 使用 WeakMap 存储关联数据
elementWeakMap.set(div, { created: Date.now() });
return div;
}
}
垃圾回收监控和优化:
// 1. 内存使用监控
function memoryMonitoring() {
// 检查内存使用情况(现代浏览器)
if (performance.memory) {
console.log('已使用内存:', performance.memory.usedJSHeapSize);
console.log('总分配内存:', performance.memory.totalJSHeapSize);
console.log('内存限制:', performance.memory.jsHeapSizeLimit);
}
// 定期监控内存使用
function startMemoryMonitoring() {
setInterval(function() {
if (performance.memory) {
var used = (performance.memory.usedJSHeapSize / 1048576).toFixed(2);
var total = (performance.memory.totalJSHeapSize / 1048576).toFixed(2);
console.log('内存使用: ' + used + 'MB / ' + total + 'MB');
// 内存使用过高时的警告
if (performance.memory.usedJSHeapSize > performance.memory.jsHeapSizeLimit * 0.9) {
console.warn('内存使用过高,建议清理');
}
}
}, 5000);
}
// startMemoryMonitoring();
}
// 2. 手动触发垃圾回收(仅用于测试)
function manualGC() {
// 注意:这些方法仅在特定环境下可用(如 Node.js 或开启特定标志的浏览器)
// Node.js 环境
if (typeof global !== 'undefined' && global.gc) {
console.log('手动触发垃圾回收');
global.gc();
}
// Chrome DevTools 中可用
if (typeof window !== 'undefined' && window.gc) {
console.log('手动触发垃圾回收');
window.gc();
}
}
// 3. 内存泄漏检测工具
function memoryLeakDetection() {
var objectRegistry = new Set();
function trackObject(obj, name) {
objectRegistry.add({ object: obj, name: name, created: Date.now() });
}
function checkLeaks() {
var leaks = [];
objectRegistry.forEach(function(entry) {
if (Date.now() - entry.created > 30000) { // 30秒
leaks.push(entry.name);
}
});
if (leaks.length > 0) {
console.warn('可能的内存泄漏:', leaks);
}
}
// 使用示例
var myObject = { data: 'test' };
trackObject(myObject, 'myObject');
setTimeout(checkLeaks, 35000);
}
// 4. 对象池模式减少垃圾回收压力
function objectPoolPattern() {
function ObjectPool(createFn, resetFn, initialSize) {
this.createFn = createFn;
this.resetFn = resetFn;
this.pool = [];
// 预创建对象
for (var i = 0; i < (initialSize || 10); i++) {
this.pool.push(this.createFn());
}
}
ObjectPool.prototype.get = function() {
if (this.pool.length > 0) {
return this.pool.pop();
} else {
return this.createFn();
}
};
ObjectPool.prototype.release = function(obj) {
this.resetFn(obj);
this.pool.push(obj);
};
// 使用示例:点对象池
var pointPool = new ObjectPool(
function() { return { x: 0, y: 0 }; }, // 创建函数
function(point) { point.x = 0; point.y = 0; }, // 重置函数
100 // 初始大小
);
function usePoints() {
var points = [];
// 获取点对象
for (var i = 0; i < 1000; i++) {
var point = pointPool.get();
point.x = Math.random() * 100;
point.y = Math.random() * 100;
points.push(point);
}
// 使用完毕后归还到池中
points.forEach(function(point) {
pointPool.release(point);
});
}
usePoints();
}
垃圾回收最佳实践:
// 最佳实践总结
function bestPractices() {
// 1. 及时清除引用
function properCleanup() {
var data = { large: new Array(100000) };
// 使用完毕后清除引用
processData(data);
data = null; // 明确设置为 null
}
// 2. 避免创建不必要的闭包
function avoidUnnecessaryClosures() {
var config = { apiUrl: 'https://api.com' };
// 不好的做法
function badHandler() {
return function(data) {
// 即使不需要 config,也会持有对它的引用
return processApiData(data);
};
}
// 好的做法
function goodHandler() {
// 如果需要 config,明确传递
return processApiData;
}
}
// 3. 使用事件委托减少监听器数量
function eventDelegation() {
// 不好的做法:为每个按钮添加监听器
function badEventHandling() {
var buttons = document.querySelectorAll('.button');
buttons.forEach(function(button) {
button.addEventListener('click', handleClick);
});
}
// 好的做法:使用事件委托
function goodEventHandling() {
document.addEventListener('click', function(event) {
if (event.target.matches('.button')) {
handleClick(event);
}
});
}
}
// 4. 合理使用缓存
function properCaching() {
// 使用 WeakMap 进行缓存,自动清理
var cache = new WeakMap();
function getCachedData(obj) {
if (!cache.has(obj)) {
cache.set(obj, computeExpensiveData(obj));
}
return cache.get(obj);
}
// 当 obj 被回收时,cache 中的条目也会自动清理
}
// 5. 定期清理长期运行的应用
function periodicCleanup() {
var caches = [];
var timers = [];
function addToCache(key, value) {
caches.push({ key: key, value: value, time: Date.now() });
}
function cleanupCaches() {
var now = Date.now();
var maxAge = 5 * 60 * 1000; // 5分钟
caches = caches.filter(function(item) {
return now - item.time < maxAge;
});
}
// 定期清理
var cleanupTimer = setInterval(cleanupCaches, 60000); // 每分钟清理
timers.push(cleanupTimer);
// 应用关闭时的清理
function shutdown() {
timers.forEach(clearInterval);
caches.length = 0;
}
}
function processData(data) {
console.log('Processing data...');
}
function processApiData(data) {
return data;
}
function handleClick(event) {
console.log('Click handled');
}
function computeExpensiveData(obj) {
return { computed: 'expensive result' };
}
}
垃圾回收机制总结:
开发者需要注意的要点:
What is execution context?
What is execution context?
考察点:代码执行机制的深入理解。
答案:
执行上下文(Execution Context)是 JavaScript 代码执行时的环境,它定义了变量或函数有权访问的数据,以及它们的行为。每当 JavaScript 代码执行时,都会在相应的执行上下文中运行。
执行上下文的类型:
// 1. 全局执行上下文 (Global Execution Context)
var globalVar = 'I am global';
function globalFunction() {
console.log('Global function');
}
// 2. 函数执行上下文 (Function Execution Context)
function createFunctionContext() {
var localVar = 'I am local';
function innerFunction() {
var innerVar = 'I am inner';
console.log(localVar, innerVar);
}
innerFunction(); // 创建新的函数执行上下文
}
createFunctionContext(); // 创建函数执行上下文
// 3. Eval 执行上下文 (Eval Execution Context) - 不推荐使用
eval('var evalVar = "I am eval"'); // 创建 eval 执行上下文
执行上下文的组成部分:
1. 变量对象 (Variable Object, VO) / 活动对象 (Activation Object, AO):
// 执行上下文创建过程演示
function demonstrateExecutionContext(param1, param2) {
console.log(arguments); // Arguments 对象
var localVar = 'local';
function innerFunction() {
return 'inner';
}
var functionExpression = function() {
return 'expression';
};
console.log(localVar);
console.log(innerFunction());
}
// 当调用 demonstrateExecutionContext('arg1', 'arg2') 时
// 执行上下文的变量对象包含:
// {
// arguments: { 0: 'arg1', 1: 'arg2', length: 2 },
// param1: 'arg1',
// param2: 'arg2',
// localVar: undefined (创建阶段) -> 'local' (执行阶段),
// innerFunction: function innerFunction() { ... },
// functionExpression: undefined (创建阶段) -> function() { ... } (执行阶段)
// }
demonstrateExecutionContext('arg1', 'arg2');
2. 作用域链 (Scope Chain):
// 作用域链的形成
var globalVar = 'global';
function outerFunction() {
var outerVar = 'outer';
function middleFunction() {
var middleVar = 'middle';
function innerFunction() {
var innerVar = 'inner';
// 作用域链:
// [innerFunction AO] -> [middleFunction AO] -> [outerFunction AO] -> [Global VO]
console.log(innerVar); // 在 innerFunction AO 中找到
console.log(middleVar); // 在 middleFunction AO 中找到
console.log(outerVar); // 在 outerFunction AO 中找到
console.log(globalVar); // 在 Global VO 中找到
}
return innerFunction;
}
return middleFunction();
}
var inner = outerFunction();
inner();
// 作用域链的动态性
function createCounter() {
var count = 0;
return function() {
// 这个函数的作用域链包含了对 createCounter 执行上下文的引用
// 即使 createCounter 执行完毕,count 变量仍然可以访问
return ++count;
};
}
var counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
3. this 的值:
// this 在不同执行上下文中的值
var globalThis = this; // 全局上下文中的 this
function regularFunction() {
// 函数执行上下文中的 this
console.log('Regular function this:', this);
return this;
}
var obj = {
method: function() {
// 方法执行上下文中的 this
console.log('Method this:', this);
function nestedFunction() {
// 嵌套函数中的 this
console.log('Nested function this:', this);
}
nestedFunction();
}
};
regularFunction(); // this 指向全局对象(浏览器中是 window)
obj.method(); // method 中的 this 指向 obj,nestedFunction 中的 this 指向全局对象
// 使用 call/apply/bind 改变 this
var anotherObj = { name: 'another' };
function showThis() {
console.log('This is:', this);
}
showThis.call(anotherObj); // this 指向 anotherObj
showThis.apply(anotherObj); // this 指向 anotherObj
var boundFunction = showThis.bind(anotherObj);
boundFunction(); // this 指向 anotherObj
执行上下文的创建过程:
1. 创建阶段 (Creation Phase):
// 创建阶段的行为演示
function creationPhaseDemo() {
console.log(typeof functionDeclaration); // 'function'
console.log(typeof variableDeclaration); // 'undefined'
console.log(typeof functionExpression); // 'undefined'
// 函数声明提升
function functionDeclaration() {
return 'I am hoisted';
}
// 变量声明提升,但不赋值
var variableDeclaration = 'I am assigned later';
// 函数表达式不会提升
var functionExpression = function() {
return 'I am not hoisted';
};
}
creationPhaseDemo();
// 创建阶段的详细过程
function detailedCreationProcess(param) {
// 在这个函数被调用时,执行上下文的创建阶段会:
// 1. 创建变量对象/活动对象
// 2. 建立作用域链
// 3. 确定 this 的值
// 变量对象在创建阶段的状态:
// ActivationObject = {
// arguments: { 0: param, length: 1 },
// param: param,
// localVar: undefined,
// hoistedFunction: function hoistedFunction() { ... }
// };
console.log(hoistedFunction); // 函数已经存在
console.log(localVar); // undefined
function hoistedFunction() {
return 'hoisted';
}
var localVar = 'initialized';
}
detailedCreationProcess('test');
2. 执行阶段 (Execution Phase):
// 执行阶段的行为演示
function executionPhaseDemo() {
// 执行阶段:代码按顺序执行,变量赋值
console.log('Before assignment:', localVar); // undefined
var localVar = 'now assigned';
console.log('After assignment:', localVar); // 'now assigned'
// 动态创建属性
this.dynamicProperty = 'created at runtime';
// 函数调用创建新的执行上下文
nestedFunction();
function nestedFunction() {
console.log('Nested function executed');
}
}
executionPhaseDemo();
执行上下文栈 (Execution Context Stack):
// 执行上下文栈的工作原理
function demonstrateContextStack() {
console.log('Global context');
function first() {
console.log('First function context');
function second() {
console.log('Second function context');
function third() {
console.log('Third function context');
// 栈状态: [Global, first, second, third]
}
third();
// third 执行完毕,从栈中弹出
// 栈状态: [Global, first, second]
}
second();
// second 执行完毕,从栈中弹出
// 栈状态: [Global, first]
}
first();
// first 执行完毕,从栈中弹出
// 栈状态: [Global]
}
demonstrateContextStack();
// 递归调用中的执行上下文栈
function factorial(n) {
console.log('Factorial called with:', n);
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
// 每次递归调用都会创建新的执行上下文并压入栈中
}
console.log('Result:', factorial(5));
// 调用栈会达到最大深度 5,然后逐个弹出
闭包与执行上下文:
// 闭包中的执行上下文保持
function createClosures() {
var functions = [];
for (var i = 0; i < 3; i++) {
// 这里有一个经典的闭包陷阱
functions[i] = function() {
console.log('Wrong way:', i); // 所有函数都会输出 3
};
}
// 正确的做法:为每个函数创建独立的执行上下文
var correctFunctions = [];
for (var j = 0; j < 3; j++) {
correctFunctions[j] = (function(index) {
// 立即执行函数创建独立的执行上下文
return function() {
console.log('Correct way:', index);
};
})(j);
}
return {
wrong: functions,
correct: correctFunctions
};
}
var closures = createClosures();
console.log('Wrong closures:');
closures.wrong.forEach(function(fn) { fn(); }); // 都输出 3
console.log('Correct closures:');
closures.correct.forEach(function(fn) { fn(); }); // 输出 0, 1, 2
// 使用 let 解决闭包问题(ES6,但理解执行上下文有帮助)
function modernClosureExample() {
var functions = [];
// 使用 IIFE 模拟块级作用域
for (var i = 0; i < 3; i++) {
(function() {
var localI = i;
functions[localI] = function() {
console.log('IIFE way:', localI);
};
})();
}
return functions;
}
var modernClosures = modernClosureExample();
modernClosures.forEach(function(fn) { fn(); });
执行上下文与异常处理:
// 执行上下文在异常处理中的行为
function exceptionHandlingContext() {
var outerVar = 'outer';
try {
function riskyFunction() {
var riskyVar = 'risky';
function deepFunction() {
var deepVar = 'deep';
// 抛出异常会导致当前执行上下文被销毁
throw new Error('Something went wrong');
// 这行代码不会执行
console.log('This will not execute');
}
deepFunction();
// deepFunction 抛出异常后,这行也不会执行
console.log('This will not execute either');
}
riskyFunction();
} catch (error) {
// 异常被捕获,执行上下文栈回到这里
console.log('Caught error:', error.message);
console.log('Outer var still accessible:', outerVar);
}
console.log('Execution continues normally');
}
exceptionHandlingContext();
// finally 块中的执行上下文
function finallyContextDemo() {
function testFinally() {
try {
console.log('Try block');
return 'try return';
} catch (error) {
console.log('Catch block');
return 'catch return';
} finally {
// finally 块总是执行,有自己的执行上下文
console.log('Finally block');
// 注意:finally 中的 return 会覆盖 try/catch 中的 return
}
}
var result = testFinally();
console.log('Result:', result); // 'try return'
}
finallyContextDemo();
执行上下文的内存管理:
// 执行上下文的内存影响
function memoryManagementContext() {
var largeData = new Array(1000000).fill('data');
function createPersistentClosure() {
// 这个闭包会保持对外部执行上下文的引用
return function() {
console.log('Data length:', largeData.length);
};
}
function createOptimizedClosure() {
var dataLength = largeData.length; // 只保存需要的数据
return function() {
console.log('Data length:', dataLength);
};
}
// 第一个闭包会保持对整个 largeData 的引用
var persistentClosure = createPersistentClosure();
// 第二个闭包只保存需要的值
var optimizedClosure = createOptimizedClosure();
// 清理大数据
largeData = null;
// persistentClosure 仍然持有对原始大数据的引用(内存泄漏)
// optimizedClosure 只持有一个数字(内存友好)
return {
persistent: persistentClosure,
optimized: optimizedClosure
};
}
var closures = memoryManagementContext();
执行上下文的调试技巧:
// 调试执行上下文的技巧
function debuggingExecutionContext() {
var debugVar = 'debug value';
function debugFunction(param) {
var localDebugVar = 'local debug';
// 1. 使用 arguments.callee 查看当前函数(严格模式下不可用)
// console.log('Current function:', arguments.callee);
// 2. 使用 console.trace() 查看调用栈
console.trace('Execution trace');
// 3. 检查作用域中的变量
console.log('Arguments:', arguments);
console.log('Local variables accessible:',
typeof debugVar, // 'string' - 来自外部作用域
typeof localDebugVar, // 'string' - 当前作用域
typeof param); // 'string' - 参数
// 4. 使用 debugger 语句(在浏览器开发者工具中有效)
debugger; // 这里会暂停执行,可以检查作用域
function innerDebugFunction() {
console.trace('Inner trace');
// 检查作用域链
console.log('Inner scope can access:',
typeof debugVar, // 外部作用域
typeof localDebugVar, // 父函数作用域
typeof param); // 父函数参数
}
innerDebugFunction();
}
debugFunction('debug param');
}
// debuggingExecutionContext();
// 动态执行上下文分析
function analyzeExecutionContext() {
function getContextInfo() {
var contextInfo = {
functionName: arguments.callee.name || 'anonymous',
argumentsLength: arguments.length,
arguments: Array.prototype.slice.call(arguments),
hasThis: this !== undefined && this !== null,
thisType: typeof this
};
return contextInfo;
}
// 在不同上下文中调用
console.log('Global context:', getContextInfo('global'));
var obj = {
method: function() {
return getContextInfo('method');
}
};
console.log('Object method context:', obj.method());
function Constructor(name) {
this.name = name;
return getContextInfo('constructor');
}
console.log('Constructor context:', new Constructor('test'));
}
analyzeExecutionContext();
执行上下文总结:
核心概念:
重要特性:
性能考虑:
What are Variable Object (VO) and Activation Object (AO)?
What are Variable Object (VO) and Activation Object (AO)?
考察点:执行上下文内部机制的理解。
答案:
变量对象(Variable Object, VO)和活动对象(Activation Object, AO)是 JavaScript 执行上下文中用于存储变量、函数声明和函数参数的内部对象。它们是理解 JavaScript 作用域、变量提升和闭包机制的关键概念。
变量对象 (Variable Object, VO):
变量对象是执行上下文中用于存储变量和函数声明的抽象对象。在全局执行上下文中,变量对象就是全局对象(浏览器中是 window,Node.js 中是 global)。
// 全局执行上下文中的变量对象
var globalVar = 'I am global';
function globalFunction() {
return 'global function';
}
// 在全局上下文中,VO 就是 global object
// 在浏览器中:
// window.globalVar === 'I am global'
// window.globalFunction === function globalFunction() { ... }
console.log(this === window); // true (在浏览器全局上下文中)
console.log(window.globalVar); // 'I am global'
console.log(typeof window.globalFunction); // 'function'
// 全局变量对象的特殊性
this.newGlobalVar = 'added to VO';
console.log(newGlobalVar); // 'added to VO' - 可以直接访问
// 使用 var 声明的全局变量成为 VO 的属性
var declaredVar = 'declared';
console.log(window.declaredVar); // 'declared'
// 但是有一个重要区别:用 var 声明的变量不能删除
delete window.declaredVar; // false (严格模式下抛出错误)
delete window.newGlobalVar; // true
console.log(window.declaredVar); // 'declared' - 仍然存在
console.log(window.newGlobalVar); // undefined - 已被删除
活动对象 (Activation Object, AO):
在函数执行上下文中,变量对象被称为活动对象。活动对象包含函数的参数、内部变量声明和函数声明。
// 函数执行上下文中的活动对象
function demonstrateAO(param1, param2) {
// 在函数执行之前,AO 会包含:
// AO = {
// arguments: { 0: param1, 1: param2, length: 2, callee: demonstrateAO },
// param1: 传入的值或 undefined,
// param2: 传入的值或 undefined,
// localVar: undefined (变量声明提升),
// innerFunction: function innerFunction() { ... } (函数声明提升)
// };
console.log('参数对象:', arguments);
console.log('param1:', param1);
console.log('param2:', param2);
console.log('localVar 在声明前:', typeof localVar); // 'undefined'
console.log('innerFunction 在声明前:', typeof innerFunction); // 'function'
var localVar = 'local value';
function innerFunction() {
return 'inner function';
}
var functionExpression = function() {
return 'function expression';
};
console.log('localVar 在声明后:', localVar); // 'local value'
return {
param1: param1,
param2: param2,
localVar: localVar,
innerFunction: innerFunction,
functionExpression: functionExpression
};
}
var result = demonstrateAO('arg1', 'arg2');
console.log(result);
创建阶段的详细过程:
// 执行上下文创建阶段的 VO/AO 构建过程
function detailedCreationProcess(a, b) {
// 创建阶段(按顺序执行):
// 1. 创建 Arguments 对象
console.log('Arguments object:', arguments);
// 2. 扫描函数声明,添加到 AO
console.log('Function declaration hoisted:', typeof hoistedFunction);
// 3. 扫描变量声明,添加到 AO(值为 undefined)
console.log('Variable declaration hoisted:', typeof hoistedVar);
console.log('hoistedVar value:', hoistedVar); // undefined
// 4. 函数参数成为 AO 的属性
console.log('Parameter a:', a);
console.log('Parameter b:', b);
// 执行阶段:代码从上到下执行
var hoistedVar = 'now assigned';
console.log('hoistedVar after assignment:', hoistedVar);
function hoistedFunction() {
return 'I was hoisted';
}
// 函数表达式在执行阶段才赋值
var functionExpression = function() {
return 'function expression';
};
console.log('Function expression after assignment:', typeof functionExpression);
}
detailedCreationProcess('valueA', 'valueB');
VO/AO 与作用域链的关系:
// VO/AO 如何形成作用域链
var globalScope = 'global';
function outerFunction(outerParam) {
var outerVar = 'outer';
function middleFunction(middleParam) {
var middleVar = 'middle';
function innerFunction(innerParam) {
var innerVar = 'inner';
// 当前函数的作用域链:
// [innerFunction AO] -> [middleFunction AO] -> [outerFunction AO] -> [Global VO]
// innerFunction AO = {
// arguments: { 0: innerParam, ... },
// innerParam: 传入值,
// innerVar: 'inner'
// };
// 变量查找过程:
console.log(innerVar); // 在 innerFunction AO 中找到
console.log(middleVar); // 在 middleFunction AO 中找到
console.log(outerVar); // 在 outerFunction AO 中找到
console.log(globalScope); // 在 Global VO 中找到
// 如果变量不存在,会沿着作用域链查找
try {
console.log(nonExistentVar);
} catch (e) {
console.log('Variable not found in any VO/AO:', e.message);
}
}
return innerFunction;
}
return middleFunction;
}
var inner = outerFunction('outer')('middle');
inner('inner');
Arguments 对象详解:
// Arguments 对象是 AO 的重要组成部分
function argumentsObjectDemo(a, b, c) {
// Arguments 对象的特性
console.log('Arguments object:', arguments);
console.log('Arguments length:', arguments.length);
console.log('Parameters length:', argumentsObjectDemo.length);
// Arguments 对象与参数的关系
console.log('参数 a:', a);
console.log('arguments[0]:', arguments[0]);
// 在非严格模式下,arguments 和参数是同步的
arguments[0] = 'changed via arguments';
console.log('参数 a after change:', a); // 'changed via arguments'
a = 'changed via parameter';
console.log('arguments[0] after parameter change:', arguments[0]); // 'changed via parameter'
// 访问超出定义的参数
console.log('Extra argument:', arguments[3]);
// Arguments 对象的 callee 属性(指向当前函数)
console.log('arguments.callee === argumentsObjectDemo:', arguments.callee === argumentsObjectDemo);
// 将 arguments 转换为真正的数组
var argsArray = Array.prototype.slice.call(arguments);
console.log('Arguments as array:', argsArray);
// 或者使用更现代的方法(ES5)
var argsArray2 = Array.prototype.slice.apply(arguments);
console.log('Arguments as array (method 2):', argsArray2);
}
argumentsObjectDemo('arg1', 'arg2', 'arg3', 'extra');
// 严格模式下的 arguments 对象
function strictArgumentsDemo(a, b) {
'use strict';
console.log('Initial a:', a);
console.log('Initial arguments[0]:', arguments[0]);
// 在严格模式下,arguments 和参数不同步
arguments[0] = 'changed arguments';
console.log('Parameter a after arguments change:', a); // 不变
a = 'changed parameter';
console.log('arguments[0] after parameter change:', arguments[0]); // 不变
// 严格模式下 arguments.callee 不可用
try {
console.log(arguments.callee);
} catch (e) {
console.log('arguments.callee error in strict mode:', e.message);
}
}
strictArgumentsDemo('initial');
变量提升与 VO/AO 的关系:
// 变量提升的本质:VO/AO 的创建过程
function hoistingDemo() {
// 以下代码的执行顺序和 AO 的变化:
console.log('=== 创建阶段 ===');
console.log('var declaration hoisted:', typeof varDeclaration); // 'undefined'
console.log('function declaration hoisted:', typeof functionDeclaration); // 'function'
console.log('let would cause error:', typeof letDeclaration); // ReferenceError in ES6
// AO 在创建阶段的状态:
// AO = {
// varDeclaration: undefined,
// functionDeclaration: function functionDeclaration() { ... }
// };
console.log('=== 执行阶段 ===');
var varDeclaration = 'var assigned';
console.log('var after assignment:', varDeclaration);
function functionDeclaration() {
return 'function declaration';
}
// 函数表达式和箭头函数不会提升
var functionExpression = function() {
return 'function expression';
};
console.log('Function expression:', typeof functionExpression);
}
hoistingDemo();
// 同名声明的处理
function nameConflictDemo() {
console.log('同名变量和函数:', typeof sameNameEntity); // 'function'
var sameNameEntity = 'variable';
function sameNameEntity() {
return 'function';
}
console.log('赋值后:', sameNameEntity); // 'variable'
// 解释:
// 1. 创建阶段:函数声明先于变量声明处理
// 2. AO.sameNameEntity = function sameNameEntity() { ... }
// 3. AO.sameNameEntity 已存在,var 声明被忽略(不会覆盖)
// 4. 执行阶段:变量赋值覆盖函数引用
}
nameConflictDemo();
闭包中的 VO/AO 保持:
// 闭包如何保持对外部 AO 的引用
function createClosures() {
var outerVar = 'outer value';
var counter = 0;
function createCounter() {
// 这个函数的 AO 包含对外部函数 AO 的引用
return function() {
counter++;
return outerVar + ' - ' + counter;
};
}
// 即使 createClosures 执行完毕,它的 AO 仍然被内部函数引用
var counter1 = createCounter();
var counter2 = createCounter();
return {
counter1: counter1,
counter2: counter2,
// 提供访问外部 AO 的方法
getOuterVar: function() {
return outerVar;
},
setOuterVar: function(value) {
outerVar = value;
}
};
}
var closures = createClosures();
console.log(closures.counter1()); // 'outer value - 1'
console.log(closures.counter2()); // 'outer value - 2' (共享同一个 AO)
console.log(closures.getOuterVar()); // 'outer value'
closures.setOuterVar('modified');
console.log(closures.counter1()); // 'modified - 3'
// 每个函数调用都创建新的 AO
function independentClosures() {
var functions = [];
for (var i = 0; i < 3; i++) {
functions.push((function(index) {
// 每次 IIFE 调用都创建新的 AO
// AO = { index: i 的当前值 }
return function() {
return 'Closure ' + index;
};
})(i));
}
return functions;
}
var independentFuncs = independentClosures();
independentFuncs.forEach(function(func, index) {
console.log('Function', index, ':', func());
});
VO/AO 与 this 绑定:
// VO/AO 中的 this 值确定
function thisBindingDemo() {
var globalThis = this;
function regularFunction() {
// 函数的 AO 不包含 this,this 值在调用时确定
console.log('Regular function this === globalThis:', this === globalThis);
var nestedFunction = function() {
console.log('Nested function this === globalThis:', this === globalThis);
};
nestedFunction();
}
var obj = {
method: function() {
console.log('Method this === obj:', this === obj);
var that = this; // 保存 this 引用到 AO 中
var innerFunction = function() {
console.log('Inner function this === obj:', this === obj);
console.log('Inner function that === obj:', that === obj);
};
innerFunction();
}
};
regularFunction();
obj.method();
// 使用 call/apply 改变 this,但不改变 AO
var anotherObj = { name: 'another' };
obj.method.call(anotherObj);
}
thisBindingDemo();
VO/AO 的实际应用和调试:
// 理解 VO/AO 有助于调试和优化
function debuggingWithVO() {
// 1. 变量查找性能
var localVar = 'local';
function deepNesting() {
function level1() {
function level2() {
function level3() {
// 访问 localVar 需要遍历作用域链
// [level3 AO] -> [level2 AO] -> [level1 AO] -> [deepNesting AO]
return localVar; // 在第4层找到
};
return level3();
}
return level2();
}
return level1();
}
// 优化:将常用变量缓存到当前 AO
function optimizedNesting() {
var cachedVar = localVar; // 缓存到当前 AO
function level1() {
function level2() {
function level3() {
return cachedVar; // 直接在 optimizedNesting AO 中找到
}
return level3();
}
return level2();
}
return level1();
}
console.log('Deep nesting result:', deepNesting());
console.log('Optimized nesting result:', optimizedNesting());
}
debuggingWithVO();
// 2. 内存使用优化
function memoryOptimization() {
var largeData = new Array(100000).fill('data');
function createBadClosure() {
// 这个闭包会保持对整个 AO 的引用,包括 largeData
return function(id) {
return 'Item ' + id;
};
}
function createGoodClosure() {
// 只保存需要的数据
var itemPrefix = 'Item ';
return function(id) {
return itemPrefix + id;
};
}
var badClosure = createBadClosure();
var goodClosure = createGoodClosure();
// 清理大数据
largeData = null;
// badClosure 仍然持有对包含 largeData 的 AO 的引用
// goodClosure 只持有 itemPrefix
return {
bad: badClosure,
good: goodClosure
};
}
var closures = memoryOptimization();
// 3. 变量提升调试
function hoistingDebugging() {
console.log('Debugging variable hoisting:');
try {
console.log('undeclaredVar:', undeclaredVar);
} catch (e) {
console.log('undeclaredVar error:', e.message);
}
console.log('declaredLaterVar:', typeof declaredLaterVar); // undefined
var declaredLaterVar = 'assigned';
console.log('declaredLaterVar after assignment:', declaredLaterVar);
// 函数声明完全提升
console.log('hoistedFunction result:', hoistedFunction());
function hoistedFunction() {
return 'I am hoisted completely';
}
}
hoistingDebugging();
ES5 vs 现代 JavaScript 中的变化:
// ES5 中的 VO/AO 概念在现代 JavaScript 中的演进
function es5VsModern() {
console.log('=== ES5 中的 var 声明 ===');
function es5VarExample() {
console.log('var before declaration:', typeof varVariable); // undefined
var varVariable = 'var value';
console.log('var after declaration:', varVariable);
}
es5VarExample();
console.log('=== ES6+ 中的 let/const 声明 ===');
function modernLetExample() {
// let/const 有暂时性死区,不会在 VO 创建阶段初始化
try {
console.log('let before declaration:', letVariable);
} catch (e) {
console.log('let error:', e.message);
}
let letVariable = 'let value';
console.log('let after declaration:', letVariable);
}
modernLetExample();
// 块级作用域改变了变量对象的概念
function blockScopeExample() {
var varInFunction = 'function scoped';
if (true) {
var varInBlock = 'still function scoped';
let letInBlock = 'block scoped';
// varInBlock 添加到函数的 AO
// letInBlock 创建新的词法环境(类似新的 VO)
}
console.log('varInFunction:', varInFunction);
console.log('varInBlock:', varInBlock); // 可以访问
try {
console.log('letInBlock:', letInBlock);
} catch (e) {
console.log('letInBlock error:', e.message); // 不能访问
}
}
blockScopeExample();
}
es5VsModern();
VO/AO 总结:
核心概念:
组成要素:
重要特性:
实践意义:
What is strict mode? What is its purpose?
What is strict mode? What is its purpose?
考察点:代码质量和语言特性的理解。
答案:
严格模式(Strict Mode)是 ES5 引入的一种 JavaScript 运行模式,它提供了更严格的错误检查机制,禁止使用一些不安全或容易出错的语法特性,使 JavaScript 代码更加安全和健壮。
启用严格模式的方法:
// 1. 全局严格模式
'use strict';
var globalVar = 'This is in strict mode';
function globalStrictFunction() {
// 这个函数也在严格模式下运行
console.log('Global strict function');
}
// 2. 函数级别严格模式
function strictFunction() {
'use strict';
// 只有这个函数内部是严格模式
var localVar = 'Function strict mode';
}
function nonStrictFunction() {
// 这个函数不在严格模式下
implicitGlobal = 'This creates global variable'; // 在非严格模式下可以
}
// 3. 模块级别严格模式(ES6 模块默认严格模式)
// export function moduleFunction() {
// // ES6 模块默认在严格模式下
// }
// 4. 立即执行函数的严格模式
(function() {
'use strict';
// 这个 IIFE 内部是严格模式
var iifeVar = 'IIFE strict mode';
})();
严格模式的主要限制和特性:
1. 禁止隐式全局变量:
// 非严格模式
function nonStrictExample() {
undeclaredVar = 'This becomes global'; // 创建全局变量
console.log(window.undeclaredVar); // 'This becomes global'
}
nonStrictExample();
// 严格模式
function strictExample() {
'use strict';
try {
undeclaredVar2 = 'This will throw error';
} catch (e) {
console.log('Strict mode error:', e.message); // ReferenceError
}
}
strictExample();
// 正确的做法
function correctExample() {
'use strict';
var declaredVar = 'This is correct';
console.log(declaredVar);
}
correctExample();
2. 禁止删除不可删除的属性:
function deletionExample() {
'use strict';
var obj = { prop: 'value' };
// 可以删除对象属性
delete obj.prop; // true
console.log(obj.prop); // undefined
// 不能删除变量
var localVar = 'test';
try {
delete localVar; // SyntaxError in strict mode
} catch (e) {
console.log('Cannot delete variable:', e.message);
}
// 不能删除函数
function localFunction() {}
try {
delete localFunction; // SyntaxError in strict mode
} catch (e) {
console.log('Cannot delete function:', e.message);
}
// 不能删除内置对象的属性
try {
delete Object.prototype; // TypeError in strict mode
} catch (e) {
console.log('Cannot delete Object.prototype:', e.message);
}
}
deletionExample();
3. 禁止重复的参数名:
// 非严格模式下允许重复参数
function nonStrictDuplicateParams(a, b, a) {
console.log('First a:', arguments[0]);
console.log('b:', b);
console.log('Second a:', arguments[2]);
console.log('Parameter a refers to:', a); // 最后一个 a
}
nonStrictDuplicateParams(1, 2, 3);
// 严格模式下禁止重复参数
function strictNoDuplicates() {
'use strict';
// 这样的函数声明在严格模式下会导致语法错误
// function invalidFunction(a, b, a) { } // SyntaxError
// 正确的做法
function validFunction(a, b, c) {
console.log('Parameters:', a, b, c);
}
validFunction(1, 2, 3);
}
strictNoDuplicates();
4. 八进制字面量限制:
function octalLiteralsExample() {
console.log('=== 非严格模式 ===');
// 非严格模式下允许八进制字面量
var octalNumber = 010; // 8 in decimal
console.log('Octal 010:', octalNumber);
console.log('=== 严格模式 ===');
(function() {
'use strict';
try {
// 严格模式下八进制字面量会导致语法错误
// var invalidOctal = 010; // SyntaxError
// 正确的做法:使用十进制或显式八进制
var decimalNumber = 8;
var explicitOctal = parseInt('10', 8);
console.log('Decimal:', decimalNumber);
console.log('Explicit octal:', explicitOctal);
} catch (e) {
console.log('Octal error:', e.message);
}
})();
}
octalLiteralsExample();
5. this 绑定的变化:
// this 在严格模式和非严格模式下的差异
function thisBindingExample() {
console.log('=== 非严格模式下的 this ===');
function nonStrictThis() {
console.log('Non-strict this:', this === window); // true (浏览器环境)
}
nonStrictThis();
console.log('=== 严格模式下的 this ===');
function strictThis() {
'use strict';
console.log('Strict this:', this); // undefined
console.log('Strict this === undefined:', this === undefined); // true
}
strictThis();
// 通过 call/apply 调用
console.log('=== call/apply 调用 ===');
function testCallApply() {
'use strict';
console.log('Called with null:', this);
}
testCallApply.call(null); // null (严格模式),window (非严格模式)
testCallApply.call(undefined); // undefined (严格模式),window (非严格模式)
testCallApply.call(42); // 42 (严格模式),Number(42) (非严格模式)
}
thisBindingExample();
// 构造函数中的 this
function constructorThisExample() {
function StrictConstructor() {
'use strict';
this.property = 'value';
}
// 正确使用 new
var instance = new StrictConstructor();
console.log('Constructor instance:', instance.property);
// 忘记使用 new(严格模式下会抛出错误)
try {
var failed = StrictConstructor(); // TypeError: Cannot set property 'property' of undefined
} catch (e) {
console.log('Constructor error:', e.message);
}
}
constructorThisExample();
6. arguments 对象的变化:
// arguments 对象在严格模式和非严格模式下的差异
function argumentsExample() {
console.log('=== 非严格模式 arguments ===');
function nonStrictArguments(a, b) {
console.log('Initial a:', a);
console.log('Initial arguments[0]:', arguments[0]);
// 修改参数会影响 arguments
a = 'modified parameter';
console.log('After modifying a:', arguments[0]); // 'modified parameter'
// 修改 arguments 会影响参数
arguments[0] = 'modified arguments';
console.log('After modifying arguments[0]:', a); // 'modified arguments'
}
nonStrictArguments('original');
console.log('=== 严格模式 arguments ===');
function strictArguments(a, b) {
'use strict';
console.log('Initial a:', a);
console.log('Initial arguments[0]:', arguments[0]);
// 修改参数不会影响 arguments
a = 'modified parameter';
console.log('After modifying a, arguments[0]:', arguments[0]); // 'original'
// 修改 arguments 不会影响参数
arguments[0] = 'modified arguments';
console.log('After modifying arguments[0], a:', a); // 'modified parameter'
// arguments.callee 在严格模式下不可用
try {
console.log(arguments.callee);
} catch (e) {
console.log('arguments.callee error:', e.message);
}
// arguments.caller 在严格模式下不可用
try {
console.log(arguments.caller);
} catch (e) {
console.log('arguments.caller error:', e.message);
}
}
strictArguments('original');
}
argumentsExample();
7. with 语句被禁用:
function withStatementExample() {
var obj = {
prop1: 'value1',
prop2: 'value2'
};
console.log('=== 非严格模式 with ===');
// 非严格模式下允许 with(但不推荐)
with (obj) {
console.log('prop1:', prop1); // 'value1'
console.log('prop2:', prop2); // 'value2'
}
console.log('=== 严格模式 with ===');
(function() {
'use strict';
// with 语句在严格模式下会导致语法错误
// with (obj) { } // SyntaxError
// 正确的做法:直接访问对象属性
console.log('obj.prop1:', obj.prop1);
console.log('obj.prop2:', obj.prop2);
// 或者使用解构赋值(ES6)
// const { prop1, prop2 } = obj;
})();
}
withStatementExample();
8. eval 的安全性增强:
function evalSecurityExample() {
console.log('=== 非严格模式 eval ===');
function nonStrictEval() {
eval('var evalVar = "created by eval"');
console.log('evalVar:', typeof evalVar); // 'string' - eval 创建的变量污染了当前作用域
}
// nonStrictEval();
console.log('=== 严格模式 eval ===');
function strictEval() {
'use strict';
eval('var evalVar = "created by eval"');
try {
console.log('evalVar:', evalVar);
} catch (e) {
console.log('evalVar not accessible:', e.message); // ReferenceError
}
// eval 在严格模式下有自己的作用域
var result = eval('var localEvalVar = "local to eval"; localEvalVar');
console.log('eval result:', result); // 'local to eval'
try {
console.log('localEvalVar:', localEvalVar);
} catch (e) {
console.log('localEvalVar not accessible:', e.message);
}
}
strictEval();
// 间接 eval 调用
console.log('=== 间接 eval 调用 ===');
(function() {
'use strict';
var globalEval = eval;
// 直接 eval:在当前作用域(但严格模式下仍有自己的作用域)
eval('var directEval = "direct"');
// 间接 eval:在全局作用域
globalEval('var indirectEval = "indirect"');
try {
console.log('directEval:', directEval);
} catch (e) {
console.log('directEval error:', e.message);
}
console.log('indirectEval in global:', typeof window.indirectEval);
})();
}
evalSecurityExample();
严格模式的实际应用:
// 1. 防止常见错误
function preventCommonMistakes() {
'use strict';
// 防止意外的全局变量
function processData(data) {
var result = [];
for (var i = 0; i < data.length; i++) {
// 如果写成 rsult = ...(拼写错误),严格模式会抛出错误
result.push(data[i] * 2);
}
return result;
}
console.log('Processed data:', processData([1, 2, 3, 4]));
// 防止删除重要属性
var config = {
apiUrl: 'https://api.example.com',
timeout: 5000
};
Object.defineProperty(config, 'version', {
value: '1.0.0',
writable: false,
configurable: false
});
try {
delete config.version; // TypeError in strict mode
} catch (e) {
console.log('Cannot delete version:', e.message);
}
}
preventCommonMistakes();
// 2. 更安全的对象操作
function saferObjectOperations() {
'use strict';
var obj = {};
// 严格模式下对只读属性赋值会抛出错误
Object.defineProperty(obj, 'readOnly', {
value: 'cannot change',
writable: false
});
try {
obj.readOnly = 'trying to change'; // TypeError
} catch (e) {
console.log('Read-only error:', e.message);
}
// 严格模式下向不可扩展对象添加属性会抛出错误
var frozenObj = Object.freeze({});
try {
frozenObj.newProperty = 'value'; // TypeError
} catch (e) {
console.log('Frozen object error:', e.message);
}
}
saferObjectOperations();
// 3. 库和框架中的严格模式
function libraryStrictMode() {
'use strict';
// 创建一个简单的库
var MyLibrary = (function() {
function MyLibrary(config) {
if (!(this instanceof MyLibrary)) {
throw new TypeError('MyLibrary must be called with new');
}
this.config = config || {};
}
MyLibrary.prototype.method = function() {
// 库方法总是在严格模式下运行
return 'Library method called';
};
return MyLibrary;
})();
// 正确使用
var lib = new MyLibrary({ option: 'value' });
console.log(lib.method());
// 错误使用
try {
var failed = MyLibrary(); // TypeError
} catch (e) {
console.log('Library error:', e.message);
}
}
libraryStrictMode();
// 4. 性能优化
function performanceOptimization() {
'use strict';
// 严格模式可能有轻微的性能提升
function heavyComputation(n) {
var result = 0;
for (var i = 0; i < n; i++) {
result += Math.sqrt(i);
}
return result;
}
console.time('Strict mode computation');
var result = heavyComputation(1000000);
console.timeEnd('Strict mode computation');
console.log('Result:', result);
}
performanceOptimization();
严格模式的兼容性和最佳实践:
// 兼容性处理
function compatibilityHandling() {
// 检测是否支持严格模式
var supportsStrictMode = (function() {
'use strict';
try {
// 在严格模式下,arguments.callee 会抛出错误
arguments.callee;
return false;
} catch (e) {
return true;
}
})();
console.log('Supports strict mode:', supportsStrictMode);
// 渐进式采用严格模式
if (supportsStrictMode) {
(function() {
'use strict';
// 严格模式下的代码
console.log('Running in strict mode');
})();
} else {
// 非严格模式下的兼容代码
console.log('Running in non-strict mode');
}
}
compatibilityHandling();
// 最佳实践
function bestPractices() {
'use strict';
// 1. 在文件或函数顶部声明
console.log('Best practice: declare at top');
// 2. 使用工具检查
// 可以使用 JSLint, JSHint, ESLint 等工具
// 3. 团队开发中的一致性
var team = {
coding: function() {
'use strict';
// 确保所有团队成员都使用严格模式
}
};
// 4. 测试严格模式和非严格模式
function testBothModes() {
// 非严格模式测试
(function() {
var nonStrictResult = testFunction();
console.log('Non-strict result:', nonStrictResult);
})();
// 严格模式测试
(function() {
'use strict';
var strictResult = testFunction();
console.log('Strict result:', strictResult);
})();
function testFunction() {
return typeof this;
}
}
testBothModes();
}
bestPractices();
严格模式总结:
优点:
使用场景:
注意事项:
How to detect if an object property exists? What methods are available?
How to detect if an object property exists? What methods are available?
考察点:对象属性操作和原型链的理解。
答案:
在 JavaScript 中有多种方法来检测对象属性是否存在,每种方法都有其特定的使用场景和行为差异。了解这些方法的区别对于正确处理对象属性至关重要。
1. in 操作符 - 检测属性是否存在于对象或其原型链中:
// in 操作符的基本使用
function inOperatorDemo() {
var obj = {
name: 'John',
age: 30,
active: true,
count: 0,
value: null,
data: undefined
};
// 检测自有属性
console.log('name' in obj); // true
console.log('age' in obj); // true
console.log('count' in obj); // true (即使值为 0)
console.log('value' in obj); // true (即使值为 null)
console.log('data' in obj); // true (即使值为 undefined)
console.log('nonExistent' in obj); // false
// 检测继承的属性
console.log('toString' in obj); // true (继承自 Object.prototype)
console.log('valueOf' in obj); // true (继承自 Object.prototype)
console.log('hasOwnProperty' in obj); // true (继承自 Object.prototype)
return obj;
}
var testObj = inOperatorDemo();
// 动态属性名检测
function dynamicPropertyCheck(obj, propName) {
return propName in obj;
}
console.log('动态检测 name:', dynamicPropertyCheck(testObj, 'name')); // true
console.log('动态检测 salary:', dynamicPropertyCheck(testObj, 'salary')); // false
2. hasOwnProperty() - 检测对象自有属性(不包括继承属性):
// hasOwnProperty 的使用
function hasOwnPropertyDemo() {
var parent = {
parentProp: 'parent value'
};
var child = Object.create(parent);
child.childProp = 'child value';
child.nullProp = null;
child.undefinedProp = undefined;
child.zeroProp = 0;
child.falseProp = false;
console.log('=== hasOwnProperty 检测 ===');
// 检测自有属性
console.log('childProp:', child.hasOwnProperty('childProp')); // true
console.log('nullProp:', child.hasOwnProperty('nullProp')); // true
console.log('undefinedProp:', child.hasOwnProperty('undefinedProp')); // true
console.log('zeroProp:', child.hasOwnProperty('zeroProp')); // true
console.log('falseProp:', child.hasOwnProperty('falseProp')); // true
// 不检测继承属性
console.log('parentProp:', child.hasOwnProperty('parentProp')); // false
console.log('toString:', child.hasOwnProperty('toString')); // false
console.log('=== in 操作符对比 ===');
// in 操作符会检测继承属性
console.log('parentProp in child:', 'parentProp' in child); // true
console.log('toString in child:', 'toString' in child); // true
return child;
}
var childObj = hasOwnPropertyDemo();
// 安全的 hasOwnProperty 调用
function safeHasOwnProperty(obj, prop) {
// 防止对象重写了 hasOwnProperty 方法
return Object.prototype.hasOwnProperty.call(obj, prop);
}
// 测试安全调用
var objWithOverriddenMethod = {
name: 'test',
hasOwnProperty: function() {
return false; // 被重写的方法
}
};
console.log('不安全调用:', objWithOverriddenMethod.hasOwnProperty('name')); // false (错误)
console.log('安全调用:', safeHasOwnProperty(objWithOverriddenMethod, 'name')); // true (正确)
3. 直接属性访问 - 检测属性值是否为 undefined:
// 直接属性访问的问题和解决方案
function directAccessDemo() {
var obj = {
name: 'John',
age: 0,
active: false,
data: null,
info: undefined,
// 注意:这里故意将一个属性设置为 undefined
};
console.log('=== 直接访问的问题 ===');
// 问题1:无法区分属性不存在和属性值为 undefined
console.log('obj.info:', obj.info); // undefined (属性存在但值为 undefined)
console.log('obj.missing:', obj.missing); // undefined (属性不存在)
// 问题2:falsy 值可能被误判
console.log('obj.age 检测:', obj.age ? '存在' : '不存在'); // '不存在' (错误,age = 0)
console.log('obj.active 检测:', obj.active ? '存在' : '不存在'); // '不存在' (错误,active = false)
console.log('=== 正确的检测方法 ===');
// 使用 typeof 检测(但仍有限制)
console.log('typeof obj.info:', typeof obj.info); // 'undefined'
console.log('typeof obj.missing:', typeof obj.missing); // 'undefined'
// 使用 !== undefined 检测(推荐用于值检测)
console.log('obj.age !== undefined:', obj.age !== undefined); // true
console.log('obj.active !== undefined:', obj.active !== undefined); // true
console.log('obj.info !== undefined:', obj.info !== undefined); // false
console.log('obj.missing !== undefined:', obj.missing !== undefined); // false
return obj;
}
directAccessDemo();
// 结合多种方法的完整检测
function comprehensivePropertyCheck(obj, prop) {
var exists = prop in obj;
var isOwnProperty = safeHasOwnProperty(obj, prop);
var value = obj[prop];
var hasValue = value !== undefined;
return {
exists: exists,
isOwnProperty: isOwnProperty,
isInherited: exists && !isOwnProperty,
value: value,
hasValue: hasValue
};
}
// 测试综合检测
var testObject = Object.create({inheritedProp: 'inherited'});
testObject.ownProp = 'own';
testObject.nullProp = null;
testObject.undefinedProp = undefined;
console.log('=== 综合属性检测 ===');
console.log('ownProp:', comprehensivePropertyCheck(testObject, 'ownProp'));
console.log('inheritedProp:', comprehensivePropertyCheck(testObject, 'inheritedProp'));
console.log('nullProp:', comprehensivePropertyCheck(testObject, 'nullProp'));
console.log('undefinedProp:', comprehensivePropertyCheck(testObject, 'undefinedProp'));
console.log('nonExistent:', comprehensivePropertyCheck(testObject, 'nonExistent'));
4. Object.keys() 和 Object.getOwnPropertyNames() - 获取属性列表:
// 使用属性枚举方法进行检测
function propertyEnumerationDemo() {
var obj = {
enumerable1: 'value1',
enumerable2: 'value2'
};
// 添加不可枚举属性
Object.defineProperty(obj, 'nonEnumerable', {
value: 'hidden value',
enumerable: false,
writable: true,
configurable: true
});
console.log('=== Object.keys() ===');
var keys = Object.keys(obj);
console.log('Enumerable own properties:', keys); // ['enumerable1', 'enumerable2']
function checkWithKeys(obj, prop) {
return Object.keys(obj).indexOf(prop) !== -1;
}
console.log('enumerable1 via keys:', checkWithKeys(obj, 'enumerable1')); // true
console.log('nonEnumerable via keys:', checkWithKeys(obj, 'nonEnumerable')); // false
console.log('=== Object.getOwnPropertyNames() ===');
var allProperties = Object.getOwnPropertyNames(obj);
console.log('All own properties:', allProperties); // ['enumerable1', 'enumerable2', 'nonEnumerable']
function checkWithPropertyNames(obj, prop) {
return Object.getOwnPropertyNames(obj).indexOf(prop) !== -1;
}
console.log('enumerable1 via names:', checkWithPropertyNames(obj, 'enumerable1')); // true
console.log('nonEnumerable via names:', checkWithPropertyNames(obj, 'nonEnumerable')); // true
return obj;
}
propertyEnumerationDemo();
// 性能比较
function performanceComparison() {
var obj = {};
// 创建大量属性
for (var i = 0; i < 1000; i++) {
obj['prop' + i] = i;
}
var testProp = 'prop500';
var iterations = 100000;
// 测试 in 操作符
console.time('in operator');
for (var i = 0; i < iterations; i++) {
var result = testProp in obj;
}
console.timeEnd('in operator');
// 测试 hasOwnProperty
console.time('hasOwnProperty');
for (var i = 0; i < iterations; i++) {
var result = obj.hasOwnProperty(testProp);
}
console.timeEnd('hasOwnProperty');
// 测试 Object.keys().indexOf()
console.time('Object.keys().indexOf()');
for (var i = 0; i < iterations; i++) {
var result = Object.keys(obj).indexOf(testProp) !== -1;
}
console.timeEnd('Object.keys().indexOf()');
}
// performanceComparison();
5. 属性描述符检测:
// 使用属性描述符进行高级检测
function propertyDescriptorDemo() {
var obj = {};
// 定义不同类型的属性
Object.defineProperty(obj, 'readOnly', {
value: 'cannot change',
writable: false,
enumerable: true,
configurable: true
});
Object.defineProperty(obj, 'hidden', {
value: 'hidden property',
writable: true,
enumerable: false,
configurable: true
});
Object.defineProperty(obj, 'permanent', {
value: 'cannot delete',
writable: true,
enumerable: true,
configurable: false
});
// 检测属性描述符
function getPropertyInfo(obj, prop) {
var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (!descriptor) {
return {
exists: false,
message: 'Property does not exist'
};
}
return {
exists: true,
value: descriptor.value,
writable: descriptor.writable,
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
isAccessor: 'get' in descriptor || 'set' in descriptor
};
}
console.log('=== 属性描述符检测 ===');
console.log('readOnly:', getPropertyInfo(obj, 'readOnly'));
console.log('hidden:', getPropertyInfo(obj, 'hidden'));
console.log('permanent:', getPropertyInfo(obj, 'permanent'));
console.log('nonExistent:', getPropertyInfo(obj, 'nonExistent'));
// 检测访问器属性
Object.defineProperty(obj, 'accessor', {
get: function() {
return this._accessor;
},
set: function(value) {
this._accessor = value;
},
enumerable: true,
configurable: true
});
console.log('accessor:', getPropertyInfo(obj, 'accessor'));
return obj;
}
propertyDescriptorDemo();
6. 实际应用场景和最佳实践:
// 实际应用中的属性检测
function realWorldApplications() {
// 1. 安全的配置对象合并
function mergeConfig(defaultConfig, userConfig) {
var merged = {};
// 复制默认配置
for (var key in defaultConfig) {
if (defaultConfig.hasOwnProperty(key)) {
merged[key] = defaultConfig[key];
}
}
// 覆盖用户配置(只覆盖已存在的配置项)
for (var key in userConfig) {
if (userConfig.hasOwnProperty(key) && key in defaultConfig) {
merged[key] = userConfig[key];
}
}
return merged;
}
var defaultConfig = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
var userConfig = {
timeout: 10000,
debug: true // 这个不会被合并,因为不在 defaultConfig 中
};
console.log('Merged config:', mergeConfig(defaultConfig, userConfig));
// 2. 对象属性验证
function validateObject(obj, requiredProps, optionalProps) {
var errors = [];
var warnings = [];
// 检查必需属性
requiredProps = requiredProps || [];
for (var i = 0; i < requiredProps.length; i++) {
var prop = requiredProps[i];
if (!(prop in obj) || obj[prop] === undefined || obj[prop] === null) {
errors.push('Missing required property: ' + prop);
}
}
// 检查未知属性
optionalProps = optionalProps || [];
var allowedProps = requiredProps.concat(optionalProps);
for (var key in obj) {
if (obj.hasOwnProperty(key) && allowedProps.indexOf(key) === -1) {
warnings.push('Unknown property: ' + key);
}
}
return {
valid: errors.length === 0,
errors: errors,
warnings: warnings
};
}
var userData = {
name: 'John',
email: '[email protected]',
age: null, // 错误:必需属性为 null
extra: 'value' // 警告:未知属性
};
var validation = validateObject(userData, ['name', 'email', 'age'], ['phone', 'address']);
console.log('Validation result:', validation);
// 3. 动态属性访问器
function createPropertyAccessor(obj) {
return {
get: function(path) {
var keys = path.split('.');
var current = obj;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (current && typeof current === 'object' && key in current) {
current = current[key];
} else {
return undefined;
}
}
return current;
},
set: function(path, value) {
var keys = path.split('.');
var current = obj;
for (var i = 0; i < keys.length - 1; i++) {
var key = keys[i];
if (!(key in current) || typeof current[key] !== 'object') {
current[key] = {};
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
},
has: function(path) {
var keys = path.split('.');
var current = obj;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!current || typeof current !== 'object' || !(key in current)) {
return false;
}
current = current[key];
}
return true;
}
};
}
var data = {
user: {
profile: {
name: 'Alice'
}
}
};
var accessor = createPropertyAccessor(data);
console.log('Deep property exists:', accessor.has('user.profile.name')); // true
console.log('Deep property value:', accessor.get('user.profile.name')); // 'Alice'
console.log('Non-existent path:', accessor.has('user.settings.theme')); // false
accessor.set('user.settings.theme', 'dark');
console.log('After setting:', accessor.get('user.settings.theme')); // 'dark'
}
realWorldApplications();
// 跨浏览器兼容的属性检测
function crossBrowserPropertyDetection() {
// 特性检测而非浏览器检测
function hasProperty(obj, prop) {
// 优先使用原生方法
if (typeof Object.hasOwnProperty === 'function') {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
// 降级到 in 操作符
if (typeof prop === 'string') {
return prop in obj;
}
// 最后降级到直接访问
return obj[prop] !== undefined;
}
// 检测对象是否支持某个方法
function hasMethod(obj, methodName) {
return hasProperty(obj, methodName) &&
typeof obj[methodName] === 'function';
}
// 检测 API 支持
function detectFeatureSupport() {
var support = {};
// 检测 Object 方法支持
support.objectKeys = hasMethod(Object, 'keys');
support.objectCreate = hasMethod(Object, 'create');
support.objectDefineProperty = hasMethod(Object, 'defineProperty');
// 检测 Array 方法支持
support.arrayIndexOf = hasMethod(Array.prototype, 'indexOf');
support.arrayForEach = hasMethod(Array.prototype, 'forEach');
support.arrayMap = hasMethod(Array.prototype, 'map');
// 检测 Function 方法支持
support.functionBind = hasMethod(Function.prototype, 'bind');
return support;
}
var features = detectFeatureSupport();
console.log('Feature support:', features);
// 基于特性检测的 polyfill 加载
function loadPolyfillsIfNeeded() {
if (!features.arrayIndexOf) {
// 加载 indexOf polyfill
Array.prototype.indexOf = Array.prototype.indexOf || function(searchElement, fromIndex) {
fromIndex = fromIndex || 0;
for (var i = fromIndex; i < this.length; i++) {
if (this[i] === searchElement) {
return i;
}
}
return -1;
};
}
if (!features.objectKeys) {
// 加载 Object.keys polyfill
Object.keys = Object.keys || function(obj) {
var keys = [];
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
keys.push(key);
}
}
return keys;
};
}
}
loadPolyfillsIfNeeded();
}
crossBrowserPropertyDetection();
属性检测方法总结:
| 方法 | 检测范围 | 检测继承属性 | 性能 | 适用场景 |
|---|---|---|---|---|
in 操作符 |
所有属性 | 是 | 高 | 一般属性存在性检测 |
hasOwnProperty() |
自有属性 | 否 | 高 | 避免继承属性干扰 |
直接访问 !== undefined |
有值属性 | 是 | 最高 | 值存在性检测 |
Object.keys().indexOf() |
可枚举自有属性 | 否 | 低 | 需要属性列表时 |
Object.getOwnPropertyNames() |
所有自有属性 | 否 | 低 | 包括不可枚举属性 |
Object.getOwnPropertyDescriptor() |
详细属性信息 | 否 | 中 | 需要属性元信息时 |
最佳实践建议:
hasOwnProperty() 检测自有属性in 操作符检测所有属性!== undefined 检测属性值存在性Object.prototype.hasOwnProperty.call() 避免方法被重写in 和 hasOwnProperty()Please explain the JavaScript Event Loop mechanism.
考察点:对 JavaScript 异步执行模型的宏观理解。
答案:
事件循环(Event Loop)是 JavaScript 异步编程的核心机制,它使得单线程的 JavaScript 能够处理异步操作。理解事件循环对于掌握 JavaScript 的异步行为至关重要。
JavaScript 单线程模型:
// JavaScript 是单线程的,但可以处理异步操作
console.log('开始执行'); // 1. 同步执行
setTimeout(function() {
console.log('异步回调'); // 3. 异步执行
}, 0);
console.log('继续执行'); // 2. 同步执行
// 输出顺序:
// 开始执行
// 继续执行
// 异步回调
事件循环的核心组件:
1. 调用栈 (Call Stack):
// 调用栈的工作原理
function first() {
console.log('First function start');
second();
console.log('First function end');
}
function second() {
console.log('Second function start');
third();
console.log('Second function end');
}
function third() {
console.log('Third function');
}
// 调用栈执行过程:
// 1. first() 入栈
// 2. second() 入栈 (在 first() 之上)
// 3. third() 入栈 (在 second() 之上)
// 4. third() 执行完毕出栈
// 5. second() 执行完毕出栈
// 6. first() 执行完毕出栈
first();
// 输出:
// First function start
// Second function start
// Third function
// Second function end
// First function end
2. 任务队列 (Task Queue / Callback Queue):
// 任务队列的工作机制
function demonstrateTaskQueue() {
console.log('1. 同步代码开始');
// 宏任务 - setTimeout
setTimeout(function() {
console.log('4. setTimeout 回调');
}, 0);
// 宏任务 - setInterval
var intervalId = setInterval(function() {
console.log('5. setInterval 回调');
clearInterval(intervalId); // 只执行一次
}, 0);
// 模拟异步 I/O(在浏览器中)
if (typeof XMLHttpRequest !== 'undefined') {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
console.log('6. AJAX 回调');
}
};
// xhr.open('GET', '/test', true);
// xhr.send(); // 实际项目中取消注释
}
console.log('2. 同步代码继续');
// 立即执行的代码
(function() {
console.log('3. 立即执行函数');
})();
}
demonstrateTaskQueue();
3. 微任务队列 (Microtask Queue):
// 微任务的执行优先级
function demonstrateMicrotasks() {
console.log('1. 同步开始');
// 宏任务
setTimeout(function() {
console.log('5. setTimeout 宏任务');
}, 0);
// 微任务 - Promise (ES6,但概念在 ES5 中已存在)
// 在 ES5 中可以用其他方式模拟微任务
// 模拟微任务的 ES5 实现
function scheduleImmediate(callback) {
// 在支持的环境中使用 setImmediate
if (typeof setImmediate !== 'undefined') {
setImmediate(callback);
} else {
// 降级到 setTimeout
setTimeout(callback, 0);
}
}
// 使用 MessageChannel 模拟微任务(更准确)
function createMicrotask(callback) {
if (typeof MessageChannel !== 'undefined') {
var channel = new MessageChannel();
channel.port2.onmessage = function() {
callback();
};
channel.port1.postMessage(null);
} else {
scheduleImmediate(callback);
}
}
createMicrotask(function() {
console.log('3. 模拟微任务 1');
});
createMicrotask(function() {
console.log('4. 模拟微任务 2');
});
console.log('2. 同步结束');
// 预期输出(在支持微任务的环境中):
// 1. 同步开始
// 2. 同步结束
// 3. 模拟微任务 1
// 4. 模拟微任务 2
// 5. setTimeout 宏任务
}
demonstrateMicrotasks();
事件循环的执行流程:
// 事件循环的详细执行步骤演示
function eventLoopDemo() {
console.log('=== 事件循环执行步骤演示 ===');
// 步骤 1:执行全局同步代码
console.log('1. 全局同步代码');
// 步骤 2:注册异步任务
setTimeout(function taskA() {
console.log('4. 宏任务 A');
// 在宏任务中注册新的微任务
createMicrotask(function() {
console.log('5. 宏任务A中的微任务');
});
// 在宏任务中注册新的宏任务
setTimeout(function() {
console.log('7. 宏任务A中的宏任务');
}, 0);
}, 0);
setTimeout(function taskB() {
console.log('6. 宏任务 B');
}, 0);
// 注册微任务
createMicrotask(function() {
console.log('3. 微任务');
});
// 步骤 3:继续执行同步代码
console.log('2. 继续全局同步代码');
// 事件循环执行顺序:
// 1. 执行所有同步代码
// 2. 执行所有微任务
// 3. 执行一个宏任务
// 4. 执行所有新产生的微任务
// 5. 重复步骤 3-4
}
eventLoopDemo();
浏览器环境 vs Node.js 环境的事件循环:
// 浏览器环境的事件循环
function browserEventLoop() {
console.log('=== 浏览器事件循环 ===');
// 浏览器中的常见异步任务源
// 1. Timer APIs
setTimeout(function() {
console.log('setTimeout');
}, 0);
setInterval(function() {
console.log('setInterval');
clearInterval(this); // 只执行一次
}, 0);
// 2. I/O 操作
function simulateIO() {
if (typeof fetch !== 'undefined') {
// 现代浏览器
fetch('/api/data')
.then(function() {
console.log('Fetch 完成');
})
.catch(function() {
console.log('Fetch 错误(模拟)');
});
} else if (typeof XMLHttpRequest !== 'undefined') {
// 传统 AJAX
var xhr = new XMLHttpRequest();
xhr.onload = function() {
console.log('AJAX 完成');
};
xhr.onerror = function() {
console.log('AJAX 错误');
};
// xhr.open('GET', '/api/data');
// xhr.send();
}
}
// 3. UI 事件
function simulateUIEvents() {
// 模拟点击事件
if (typeof document !== 'undefined') {
document.addEventListener('click', function() {
console.log('点击事件处理');
});
// 程序化触发事件
setTimeout(function() {
var event = new MouseEvent('click');
document.dispatchEvent(event);
}, 100);
}
}
simulateIO();
simulateUIEvents();
}
// Node.js 环境的事件循环(概念演示)
function nodeEventLoop() {
console.log('=== Node.js 事件循环概念 ===');
// Node.js 中的事件循环阶段:
// 1. Timer 阶段: setTimeout, setInterval
// 2. Pending 阶段: 处理一些系统操作的回调
// 3. Poll 阶段: 获取新的 I/O 事件
// 4. Check 阶段: setImmediate 回调
// 5. Close 阶段: socket.on('close', ...)
console.log('开始');
// setImmediate(Node.js 特有)
if (typeof setImmediate !== 'undefined') {
setImmediate(function() {
console.log('setImmediate');
});
}
setTimeout(function() {
console.log('setTimeout');
}, 0);
// process.nextTick(Node.js 特有,类似微任务)
if (typeof process !== 'undefined' && process.nextTick) {
process.nextTick(function() {
console.log('nextTick');
});
}
console.log('结束');
}
browserEventLoop();
// nodeEventLoop(); // 仅在 Node.js 环境中运行
异步任务的优先级:
// 异步任务优先级演示
function asyncPriorityDemo() {
console.log('=== 异步任务优先级演示 ===');
// 创建多层嵌套的异步任务
setTimeout(function() {
console.log('宏任务 1');
createMicrotask(function() {
console.log('宏任务1中的微任务1');
createMicrotask(function() {
console.log('微任务中的微任务');
});
});
createMicrotask(function() {
console.log('宏任务1中的微任务2');
});
setTimeout(function() {
console.log('宏任务1中的宏任务');
}, 0);
}, 0);
setTimeout(function() {
console.log('宏任务 2');
}, 0);
createMicrotask(function() {
console.log('全局微任务1');
setTimeout(function() {
console.log('微任务中的宏任务');
}, 0);
});
createMicrotask(function() {
console.log('全局微任务2');
});
console.log('同步代码');
// 执行顺序解析:
// 1. 同步代码
// 2. 全局微任务1
// 3. 全局微任务2
// 4. 宏任务 1
// 5. 宏任务1中的微任务1
// 6. 微任务中的微任务
// 7. 宏任务1中的微任务2
// 8. 宏任务 2
// 9. 宏任务1中的宏任务
// 10. 微任务中的宏任务
}
asyncPriorityDemo();
实际应用中的事件循环:
// 1. 动画渲染中的事件循环
function animationEventLoop() {
var element = { style: { left: '0px' } }; // 模拟 DOM 元素
var position = 0;
function animate() {
position += 1;
element.style.left = position + 'px';
console.log('动画帧:', position);
if (position < 100) {
// 使用 setTimeout 模拟 requestAnimationFrame
setTimeout(animate, 16); // 大约 60fps
}
}
// 启动动画
animate();
}
// 2. 数据处理中的事件循环
function dataProcessingEventLoop() {
var largeDataSet = [];
for (var i = 0; i < 1000; i++) {
largeDataSet.push(Math.random());
}
var batchSize = 100;
var currentIndex = 0;
var results = [];
function processBatch() {
var endIndex = Math.min(currentIndex + batchSize, largeDataSet.length);
// 处理当前批次
for (var i = currentIndex; i < endIndex; i++) {
results.push(largeDataSet[i] * 2); // 简单的数据处理
}
currentIndex = endIndex;
console.log('处理进度:', (currentIndex / largeDataSet.length * 100).toFixed(1) + '%');
// 如果还有数据需要处理,异步处理下一批
if (currentIndex < largeDataSet.length) {
setTimeout(processBatch, 0); // 让出控制权给其他任务
} else {
console.log('数据处理完成,结果数量:', results.length);
}
}
// 开始处理
processBatch();
}
// 3. 用户交互响应中的事件循环
function userInteractionEventLoop() {
var clickCount = 0;
var debounceTimer = null;
function handleClick() {
clickCount++;
// 清除之前的防抖定时器
clearTimeout(debounceTimer);
// 设置新的防抖定时器
debounceTimer = setTimeout(function() {
console.log('处理点击事件,总点击次数:', clickCount);
// 重置计数器
clickCount = 0;
}, 300); // 300ms 防抖
}
// 模拟多次快速点击
handleClick();
setTimeout(handleClick, 50);
setTimeout(handleClick, 100);
setTimeout(handleClick, 150);
// 500ms 后再次点击
setTimeout(handleClick, 500);
}
// 演示实际应用
console.log('=== 动画事件循环演示 ===');
// animationEventLoop(); // 取消注释以查看动画效果
console.log('=== 数据处理事件循环演示 ===');
dataProcessingEventLoop();
console.log('=== 用户交互事件循环演示 ===');
userInteractionEventLoop();
事件循环的性能优化:
// 事件循环性能优化技巧
function eventLoopOptimization() {
// 1. 避免阻塞主线程
function badLongRunningTask() {
var start = Date.now();
// 不好的做法:长时间阻塞主线程
for (var i = 0; i < 10000000; i++) {
Math.random(); // 模拟耗时操作
}
console.log('阻塞任务完成,耗时:', Date.now() - start, 'ms');
}
function goodLongRunningTask(callback) {
var iterations = 10000000;
var batchSize = 100000;
var currentIteration = 0;
var start = Date.now();
function processBatch() {
var endIteration = Math.min(currentIteration + batchSize, iterations);
// 处理当前批次
for (var i = currentIteration; i < endIteration; i++) {
Math.random(); // 模拟耗时操作
}
currentIteration = endIteration;
if (currentIteration < iterations) {
// 异步处理下一批,不阻塞主线程
setTimeout(processBatch, 0);
} else {
console.log('非阻塞任务完成,耗时:', Date.now() - start, 'ms');
if (callback) callback();
}
}
processBatch();
}
// 2. 合理使用 setTimeout 的延迟时间
function optimizeTimerUsage() {
// 避免过度使用 0 延迟
var counter = 0;
function rapidFire() {
console.log('Rapid fire:', ++counter);
if (counter < 5) {
setTimeout(rapidFire, 0); // 可能导致性能问题
}
}
function controlled() {
console.log('Controlled:', ++counter);
if (counter < 10) {
setTimeout(controlled, 4); // 最小有效延迟通常是 4ms
}
}
rapidFire();
setTimeout(controlled, 100);
}
// 3. 监控事件循环性能
function monitorEventLoop() {
var lastTime = Date.now();
var lagThreshold = 50; // 50ms 延迟阈值
function checkEventLoopLag() {
var currentTime = Date.now();
var lag = currentTime - lastTime - 10; // 减去预期的 10ms 间隔
if (lag > lagThreshold) {
console.warn('事件循环延迟检测:', lag + 'ms');
}
lastTime = currentTime;
setTimeout(checkEventLoopLag, 10);
}
checkEventLoopLag();
// 模拟阻塞操作
setTimeout(function() {
var start = Date.now();
while (Date.now() - start < 100) {
// 阻塞 100ms
}
}, 1000);
}
console.log('=== 性能优化演示 ===');
// 比较阻塞 vs 非阻塞任务
console.log('开始阻塞任务...');
badLongRunningTask();
console.log('开始非阻塞任务...');
goodLongRunningTask(function() {
console.log('非阻塞任务回调执行');
});
optimizeTimerUsage();
monitorEventLoop();
}
eventLoopOptimization();
调试事件循环的技巧:
// 事件循环调试技巧
function debugEventLoop() {
// 1. 可视化调用栈
function visualizeCallStack() {
function a() {
console.trace('调用栈 A');
b();
}
function b() {
console.trace('调用栈 B');
c();
}
function c() {
console.trace('调用栈 C');
}
a();
}
// 2. 追踪异步任务执行
function traceAsyncExecution() {
var taskId = 0;
function createTrackedTimeout(callback, delay, name) {
var id = ++taskId;
name = name || 'Anonymous';
console.log('注册任务 #' + id + ':', name);
return setTimeout(function() {
console.log('执行任务 #' + id + ':', name);
callback();
}, delay);
}
createTrackedTimeout(function() {
console.log('任务 A 完成');
createTrackedTimeout(function() {
console.log('任务 A.1 完成');
}, 0, 'Task A.1');
}, 100, 'Task A');
createTrackedTimeout(function() {
console.log('任务 B 完成');
}, 50, 'Task B');
}
// 3. 测量异步操作的实际延迟
function measureAsyncDelay() {
var startTime = Date.now();
setTimeout(function() {
var actualDelay = Date.now() - startTime;
console.log('预期延迟: 0ms, 实际延迟:', actualDelay + 'ms');
}, 0);
var startTime2 = Date.now();
setTimeout(function() {
var actualDelay = Date.now() - startTime2;
console.log('预期延迟: 100ms, 实际延迟:', actualDelay + 'ms');
}, 100);
}
console.log('=== 调试技巧演示 ===');
visualizeCallStack();
traceAsyncExecution();
measureAsyncDelay();
}
debugEventLoop();
// 辅助函数:创建微任务(用于上面的演示)
function createMicrotask(callback) {
if (typeof Promise !== 'undefined') {
Promise.resolve().then(callback);
} else if (typeof MutationObserver !== 'undefined') {
var observer = new MutationObserver(callback);
var node = document.createTextNode('');
observer.observe(node, { characterData: true });
node.textContent = 'trigger';
} else if (typeof MessageChannel !== 'undefined') {
var channel = new MessageChannel();
channel.port2.onmessage = callback;
channel.port1.postMessage(null);
} else {
setTimeout(callback, 0);
}
}
事件循环总结:
核心概念:
执行顺序:
性能优化要点:
调试技巧:
console.trace() 追踪调用栈How do you handle asynchronous operations in ES5 (for example, multiple dependent Ajax requests)?
How do you handle asynchronous operations in ES5 (for example, multiple dependent Ajax requests)?
考察点:对传统异步解决方案(回调)及其问题的理解。
答案:
在 ES5 时代,处理异步操作主要依靠回调函数(Callback)模式。虽然这种模式有其局限性,但通过合适的设计模式和工具函数,仍然可以有效地管理复杂的异步操作。
基本的回调模式:
// 基础的异步操作 - XMLHttpRequest
function createXHR() {
if (typeof XMLHttpRequest !== 'undefined') {
return new XMLHttpRequest();
} else if (typeof ActiveXObject !== 'undefined') {
// IE6+ 兼容性
return new ActiveXObject('Microsoft.XMLHTTP');
} else {
throw new Error('XMLHttpRequest not supported');
}
}
// 简单的 AJAX 封装
function ajax(options, callback) {
var xhr = createXHR();
var method = options.method || 'GET';
var url = options.url;
var data = options.data || null;
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
var responseData;
try {
responseData = JSON.parse(xhr.responseText);
} catch (e) {
responseData = xhr.responseText;
}
callback(null, responseData); // 成功:错误为 null
} else {
callback(new Error('HTTP Error: ' + xhr.status), null); // 失败
}
}
};
xhr.onerror = function() {
callback(new Error('Network Error'), null);
};
xhr.open(method, url, true);
if (method === 'POST' && data) {
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(data));
} else {
xhr.send();
}
}
// 使用示例
ajax({ url: '/api/user/1' }, function(error, user) {
if (error) {
console.error('获取用户失败:', error.message);
return;
}
console.log('用户信息:', user);
});
处理多个依赖的异步请求:
// 场景:获取用户信息 -> 获取用户文章 -> 获取文章评论
function sequentialAsyncOperations() {
console.log('=== 串行异步操作 ===');
// 方法 1:嵌套回调(容易导致回调地狱)
function fetchUserDataNested(userId, callback) {
// 第一步:获取用户信息
ajax({ url: '/api/users/' + userId }, function(error, user) {
if (error) {
return callback(error);
}
// 第二步:获取用户的文章列表
ajax({ url: '/api/users/' + userId + '/posts' }, function(error, posts) {
if (error) {
return callback(error);
}
// 第三步:获取第一篇文章的评论
if (posts.length > 0) {
var firstPostId = posts[0].id;
ajax({ url: '/api/posts/' + firstPostId + '/comments' }, function(error, comments) {
if (error) {
return callback(error);
}
// 组装最终结果
callback(null, {
user: user,
posts: posts,
firstPostComments: comments
});
});
} else {
callback(null, {
user: user,
posts: posts,
firstPostComments: []
});
}
});
});
}
// 使用嵌套回调
fetchUserDataNested('123', function(error, result) {
if (error) {
console.error('嵌套回调失败:', error.message);
} else {
console.log('嵌套回调成功:', result);
}
});
}
// 改进:使用命名函数减少嵌套
function improvedAsyncHandling() {
console.log('=== 改进的异步处理 ===');
function fetchUserData(userId, callback) {
var result = {};
function onUserFetched(error, user) {
if (error) return callback(error);
result.user = user;
ajax({ url: '/api/users/' + userId + '/posts' }, onPostsFetched);
}
function onPostsFetched(error, posts) {
if (error) return callback(error);
result.posts = posts;
if (posts.length > 0) {
var firstPostId = posts[0].id;
ajax({ url: '/api/posts/' + firstPostId + '/comments' }, onCommentsFetched);
} else {
result.firstPostComments = [];
callback(null, result);
}
}
function onCommentsFetched(error, comments) {
if (error) return callback(error);
result.firstPostComments = comments;
callback(null, result);
}
// 开始执行
ajax({ url: '/api/users/' + userId }, onUserFetched);
}
fetchUserData('123', function(error, result) {
if (error) {
console.error('改进版本失败:', error.message);
} else {
console.log('改进版本成功:', result);
}
});
}
并行异步操作处理:
// 并行执行多个异步操作
function parallelAsyncOperations() {
console.log('=== 并行异步操作 ===');
// 实现一个简单的并行执行工具
function parallel(tasks, callback) {
var results = [];
var completedCount = 0;
var hasError = false;
if (tasks.length === 0) {
return callback(null, []);
}
tasks.forEach(function(task, index) {
task(function(error, result) {
if (hasError) return; // 已经有错误,忽略后续回调
if (error) {
hasError = true;
return callback(error);
}
results[index] = result;
completedCount++;
if (completedCount === tasks.length) {
callback(null, results);
}
});
});
}
// 使用示例:并行获取多个用户的信息
function fetchMultipleUsers(userIds, callback) {
var tasks = userIds.map(function(userId) {
return function(taskCallback) {
ajax({ url: '/api/users/' + userId }, taskCallback);
};
});
parallel(tasks, callback);
}
fetchMultipleUsers(['1', '2', '3'], function(error, users) {
if (error) {
console.error('并行获取用户失败:', error.message);
} else {
console.log('并行获取用户成功:', users);
}
});
// 更复杂的并行操作:获取用户信息和设置信息
function fetchUserAndSettings(userId, callback) {
var tasks = [
function(taskCallback) {
ajax({ url: '/api/users/' + userId }, taskCallback);
},
function(taskCallback) {
ajax({ url: '/api/users/' + userId + '/settings' }, taskCallback);
},
function(taskCallback) {
ajax({ url: '/api/users/' + userId + '/preferences' }, taskCallback);
}
];
parallel(tasks, function(error, results) {
if (error) {
return callback(error);
}
callback(null, {
user: results[0],
settings: results[1],
preferences: results[2]
});
});
}
fetchUserAndSettings('123', function(error, result) {
if (error) {
console.error('获取用户完整信息失败:', error.message);
} else {
console.log('获取用户完整信息成功:', result);
}
});
}
串行和并行结合的复杂场景:
// 复杂的异步操作组合
function complexAsyncOperations() {
console.log('=== 复杂异步操作组合 ===');
// 实现串行执行工具
function series(tasks, callback) {
var results = [];
var currentIndex = 0;
function runNext() {
if (currentIndex >= tasks.length) {
return callback(null, results);
}
var currentTask = tasks[currentIndex];
currentTask(function(error, result) {
if (error) {
return callback(error);
}
results.push(result);
currentIndex++;
runNext();
});
}
runNext();
}
// 实现瀑布流工具(每个任务的结果传递给下一个任务)
function waterfall(tasks, callback) {
var currentIndex = 0;
function runNext() {
if (currentIndex >= tasks.length) {
return callback(null);
}
var currentTask = tasks[currentIndex];
var args = Array.prototype.slice.call(arguments, 1);
args.push(function(error) {
if (error) {
return callback(error);
}
currentIndex++;
runNext.apply(null, arguments);
});
currentTask.apply(null, args);
}
runNext();
}
// 场景:用户登录 -> 获取权限 -> 获取菜单 -> 初始化界面
function userLoginFlow(username, password, callback) {
waterfall([
// 第一步:用户登录
function(next) {
ajax({
method: 'POST',
url: '/api/auth/login',
data: { username: username, password: password }
}, next);
},
// 第二步:获取用户权限
function(loginResult, next) {
var token = loginResult.token;
ajax({
url: '/api/auth/permissions',
headers: { 'Authorization': 'Bearer ' + token }
}, function(error, permissions) {
next(error, loginResult, permissions);
});
},
// 第三步:根据权限获取菜单
function(loginResult, permissions, next) {
var allowedMenus = permissions.menus || [];
if (allowedMenus.length > 0) {
ajax({
url: '/api/menus?ids=' + allowedMenus.join(',')
}, function(error, menus) {
next(error, loginResult, permissions, menus);
});
} else {
next(null, loginResult, permissions, []);
}
},
// 第四步:初始化用户界面数据
function(loginResult, permissions, menus, next) {
var initTasks = [
function(taskCallback) {
ajax({ url: '/api/user/profile' }, taskCallback);
},
function(taskCallback) {
ajax({ url: '/api/user/notifications' }, taskCallback);
}
];
parallel(initTasks, function(error, initResults) {
if (error) {
return next(error);
}
next(null, {
login: loginResult,
permissions: permissions,
menus: menus,
profile: initResults[0],
notifications: initResults[1]
});
});
}
], callback);
}
// 使用登录流程
userLoginFlow('testuser', 'password123', function(error, result) {
if (error) {
console.error('登录流程失败:', error.message);
} else {
console.log('登录流程成功:', result);
}
});
}
错误处理和重试机制:
// 异步操作的错误处理和重试
function errorHandlingAndRetry() {
console.log('=== 错误处理和重试机制 ===');
// 带重试的异步操作
function retryableAjax(options, maxRetries, callback) {
var attempt = 0;
function attemptRequest() {
attempt++;
ajax(options, function(error, result) {
if (error) {
console.warn('请求失败 (尝试 ' + attempt + '/' + (maxRetries + 1) + '):', error.message);
if (attempt <= maxRetries) {
// 指数退避重试
var delay = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s, 8s...
console.log('将在 ' + delay + 'ms 后重试...');
setTimeout(attemptRequest, delay);
} else {
callback(new Error('请求失败,已达到最大重试次数'), null);
}
} else {
callback(null, result);
}
});
}
attemptRequest();
}
// 使用重试机制
retryableAjax(
{ url: '/api/unreliable-endpoint' },
3, // 最多重试3次
function(error, result) {
if (error) {
console.error('最终失败:', error.message);
} else {
console.log('重试成功:', result);
}
}
);
// 超时控制
function ajaxWithTimeout(options, timeout, callback) {
var completed = false;
// 设置超时
var timeoutId = setTimeout(function() {
if (!completed) {
completed = true;
callback(new Error('请求超时'), null);
}
}, timeout);
// 执行请求
ajax(options, function(error, result) {
if (!completed) {
completed = true;
clearTimeout(timeoutId);
callback(error, result);
}
});
}
// 使用超时控制
ajaxWithTimeout(
{ url: '/api/slow-endpoint' },
5000, // 5秒超时
function(error, result) {
if (error) {
console.error('请求失败或超时:', error.message);
} else {
console.log('请求成功:', result);
}
}
);
}
创建一个完整的异步库:
// ES5 异步操作库
function createAsyncLibrary() {
var AsyncLib = {};
// 并行执行
AsyncLib.parallel = function(tasks, callback) {
if (!Array.isArray(tasks) || tasks.length === 0) {
return callback(null, []);
}
var results = new Array(tasks.length);
var completedCount = 0;
var hasError = false;
tasks.forEach(function(task, index) {
task(function(error, result) {
if (hasError) return;
if (error) {
hasError = true;
return callback(error);
}
results[index] = result;
completedCount++;
if (completedCount === tasks.length) {
callback(null, results);
}
});
});
};
// 串行执行
AsyncLib.series = function(tasks, callback) {
var results = [];
var currentIndex = 0;
function runNext() {
if (currentIndex >= tasks.length) {
return callback(null, results);
}
tasks[currentIndex](function(error, result) {
if (error) {
return callback(error);
}
results.push(result);
currentIndex++;
runNext();
});
}
runNext();
};
// 瀑布流
AsyncLib.waterfall = function(tasks, callback) {
var currentIndex = 0;
function runNext() {
if (currentIndex >= tasks.length) {
return callback(null);
}
var args = Array.prototype.slice.call(arguments, 1);
var currentTask = tasks[currentIndex];
args.push(function(error) {
if (error) {
return callback(error);
}
currentIndex++;
runNext.apply(null, arguments);
});
currentTask.apply(null, args);
}
runNext();
};
// 映射(并行处理数组)
AsyncLib.map = function(array, iterator, callback) {
var tasks = array.map(function(item, index) {
return function(taskCallback) {
iterator(item, index, taskCallback);
};
});
AsyncLib.parallel(tasks, callback);
};
// 过滤(并行处理数组并过滤结果)
AsyncLib.filter = function(array, test, callback) {
var tasks = array.map(function(item, index) {
return function(taskCallback) {
test(item, function(error, passed) {
taskCallback(error, { item: item, index: index, passed: passed });
});
};
});
AsyncLib.parallel(tasks, function(error, results) {
if (error) {
return callback(error);
}
var filtered = results
.filter(function(result) { return result.passed; })
.map(function(result) { return result.item; });
callback(null, filtered);
});
};
// 限制并发数的并行执行
AsyncLib.parallelLimit = function(tasks, limit, callback) {
var results = new Array(tasks.length);
var running = 0;
var completed = 0;
var index = 0;
var hasError = false;
function runTask(taskIndex) {
running++;
tasks[taskIndex](function(error, result) {
running--;
if (hasError) return;
if (error) {
hasError = true;
return callback(error);
}
results[taskIndex] = result;
completed++;
if (completed === tasks.length) {
callback(null, results);
} else {
runNext();
}
});
}
function runNext() {
while (running < limit && index < tasks.length) {
runTask(index++);
}
}
runNext();
};
return AsyncLib;
}
// 使用异步库
function useAsyncLibrary() {
var AsyncLib = createAsyncLibrary();
// 示例:处理用户数据
function processUserData() {
var userIds = ['1', '2', '3', '4', '5'];
// 使用 map 并行获取所有用户
AsyncLib.map(userIds, function(userId, index, callback) {
ajax({ url: '/api/users/' + userId }, callback);
}, function(error, users) {
if (error) {
console.error('获取用户失败:', error.message);
return;
}
console.log('获取到的用户:', users);
// 过滤出活跃用户
AsyncLib.filter(users, function(user, callback) {
// 检查用户是否活跃
ajax({ url: '/api/users/' + user.id + '/activity' }, function(error, activity) {
if (error) {
return callback(error);
}
callback(null, activity.lastLoginDays < 30);
});
}, function(error, activeUsers) {
if (error) {
console.error('过滤用户失败:', error.message);
return;
}
console.log('活跃用户:', activeUsers);
});
});
}
// 示例:限制并发的批量处理
function batchProcess() {
var tasks = [];
for (var i = 0; i < 20; i++) {
(function(taskId) {
tasks.push(function(callback) {
// 模拟耗时任务
setTimeout(function() {
console.log('任务 ' + taskId + ' 完成');
callback(null, '任务 ' + taskId + ' 结果');
}, Math.random() * 1000);
});
})(i);
}
// 限制最多同时运行 3 个任务
AsyncLib.parallelLimit(tasks, 3, function(error, results) {
if (error) {
console.error('批量处理失败:', error.message);
} else {
console.log('批量处理完成,结果数量:', results.length);
}
});
}
console.log('=== 使用异步库 ===');
processUserData();
batchProcess();
}
// 演示所有异步操作处理方法
sequentialAsyncOperations();
improvedAsyncHandling();
parallelAsyncOperations();
complexAsyncOperations();
errorHandlingAndRetry();
useAsyncLibrary();
ES5异步操作处理总结:
主要模式:
callback(error, result) 约定常用工具函数:
parallel() - 并行执行多个任务series() - 串行执行多个任务waterfall() - 瀑布流执行map() - 并行处理数组retry() - 重试机制最佳实践:
局限性:
What is "Callback Hell"? How to solve it?
What is “Callback Hell”? How to solve it?
考察点:同上,但更侧重于问题的识别和解决方案。
答案:
回调地狱(Callback Hell)是指由于多层嵌套的异步回调函数所造成的代码结构问题,使得代码难以阅读、维护和调试。这是 ES5 时代异步编程的主要痛点。
什么是回调地狱:
// 典型的回调地狱示例
function callbackHellExample() {
console.log('=== 回调地狱示例 ===');
// 场景:用户注册流程
// 1. 验证用户名 -> 2. 验证邮箱 -> 3. 创建用户 -> 4. 发送欢迎邮件 -> 5. 记录日志
function registerUser(userData, callback) {
// 第一层:验证用户名
validateUsername(userData.username, function(usernameError, usernameValid) {
if (usernameError || !usernameValid) {
return callback(new Error('用户名验证失败'));
}
// 第二层:验证邮箱
validateEmail(userData.email, function(emailError, emailValid) {
if (emailError || !emailValid) {
return callback(new Error('邮箱验证失败'));
}
// 第三层:检查邮箱是否已存在
checkEmailExists(userData.email, function(checkError, exists) {
if (checkError) {
return callback(checkError);
}
if (exists) {
return callback(new Error('邮箱已存在'));
}
// 第四层:创建用户
createUser(userData, function(createError, user) {
if (createError) {
return callback(createError);
}
// 第五层:发送欢迎邮件
sendWelcomeEmail(user.email, function(emailError) {
if (emailError) {
console.warn('欢迎邮件发送失败:', emailError.message);
}
// 第六层:记录注册日志
logUserRegistration(user.id, function(logError) {
if (logError) {
console.warn('日志记录失败:', logError.message);
}
// 终于完成了!
callback(null, user);
});
});
});
});
});
});
}
// 模拟的异步验证函数
function validateUsername(username, callback) {
setTimeout(function() {
var isValid = username && username.length >= 3 && username.length <= 20;
callback(null, isValid);
}, 100);
}
function validateEmail(email, callback) {
setTimeout(function() {
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
callback(null, emailRegex.test(email));
}, 100);
}
function checkEmailExists(email, callback) {
setTimeout(function() {
// 模拟数据库查询
var existingEmails = ['[email protected]', '[email protected]'];
callback(null, existingEmails.indexOf(email) !== -1);
}, 200);
}
function createUser(userData, callback) {
setTimeout(function() {
var user = {
id: Math.random().toString(36).substr(2, 9),
username: userData.username,
email: userData.email,
createdAt: new Date().toISOString()
};
callback(null, user);
}, 300);
}
function sendWelcomeEmail(email, callback) {
setTimeout(function() {
console.log('发送欢迎邮件到:', email);
callback(null);
}, 150);
}
function logUserRegistration(userId, callback) {
setTimeout(function() {
console.log('记录用户注册日志:', userId);
callback(null);
}, 100);
}
// 使用这个地狱般的函数
registerUser({
username: 'newuser',
email: '[email protected]'
}, function(error, user) {
if (error) {
console.error('注册失败:', error.message);
} else {
console.log('注册成功:', user);
}
});
}
callbackHellExample();
回调地狱的问题:
// 回调地狱的各种问题
function callbackHellProblems() {
console.log('=== 回调地狱的问题 ===');
// 问题1:难以阅读和理解
function readabilityProblem() {
asyncOperation1(function(error1, result1) {
if (error1) return handleError(error1);
asyncOperation2(result1, function(error2, result2) {
if (error2) return handleError(error2);
asyncOperation3(result2, function(error3, result3) {
if (error3) return handleError(error3);
asyncOperation4(result3, function(error4, result4) {
if (error4) return handleError(error4);
// 代码越来越向右缩进,形成"金字塔"
finalCallback(null, result4);
});
});
});
});
}
// 问题2:错误处理重复和复杂
function errorHandlingProblem() {
getData(function(error, data) {
if (error) {
console.error('获取数据失败:', error);
return callback(error);
}
processData(data, function(error, processed) {
if (error) {
console.error('处理数据失败:', error);
return callback(error);
}
saveData(processed, function(error, saved) {
if (error) {
console.error('保存数据失败:', error);
return callback(error);
}
// 每一层都要重复错误处理
callback(null, saved);
});
});
});
}
// 问题3:难以调试
function debuggingProblem() {
// 当发生错误时,堆栈跟踪变得复杂
step1(function(error, result1) {
step2(result1, function(error, result2) {
step3(result2, function(error, result3) {
// 如果这里出错,很难追踪到具体哪一步
if (result3.someProperty.doesNotExist) {
// TypeError: Cannot read property 'doesNotExist' of undefined
// 堆栈信息指向匿名函数,难以定位
throw new Error('意外错误');
}
});
});
});
}
// 问题4:难以测试
function testingProblem() {
// 深度嵌套的函数难以进行单元测试
function complexBusinessLogic(input, callback) {
validateInput(input, function(error, valid) {
if (error || !valid) return callback(new Error('输入无效'));
fetchUserData(input.userId, function(error, user) {
if (error) return callback(error);
checkPermissions(user, input.action, function(error, allowed) {
if (error) return callback(error);
if (!allowed) return callback(new Error('权限不足'));
// 如何单独测试这部分逻辑?
executeAction(input.action, user, callback);
});
});
});
}
}
// 模拟函数
function asyncOperation1(cb) { setTimeout(function() { cb(null, 'result1'); }, 100); }
function asyncOperation2(data, cb) { setTimeout(function() { cb(null, 'result2'); }, 100); }
function asyncOperation3(data, cb) { setTimeout(function() { cb(null, 'result3'); }, 100); }
function asyncOperation4(data, cb) { setTimeout(function() { cb(null, 'result4'); }, 100); }
function handleError(error) { console.error('错误:', error); }
function finalCallback(error, result) { console.log('最终结果:', result); }
function getData(cb) { setTimeout(function() { cb(null, 'data'); }, 50); }
function processData(data, cb) { setTimeout(function() { cb(null, 'processed'); }, 50); }
function saveData(data, cb) { setTimeout(function() { cb(null, 'saved'); }, 50); }
function step1(cb) { setTimeout(function() { cb(null, {}); }, 50); }
function step2(data, cb) { setTimeout(function() { cb(null, {}); }, 50); }
function step3(data, cb) { setTimeout(function() { cb(null, {}); }, 50); }
}
解决方案1:使用命名函数
// 解决方案1:避免匿名函数,使用命名函数
function namedFunctionsSolution() {
console.log('=== 使用命名函数解决回调地狱 ===');
function improvedRegisterUser(userData, callback) {
validateUsername(userData.username, onUsernameValidated);
function onUsernameValidated(error, isValid) {
if (error || !isValid) {
return callback(new Error('用户名验证失败'));
}
validateEmail(userData.email, onEmailValidated);
}
function onEmailValidated(error, isValid) {
if (error || !isValid) {
return callback(new Error('邮箱验证失败'));
}
checkEmailExists(userData.email, onEmailChecked);
}
function onEmailChecked(error, exists) {
if (error) return callback(error);
if (exists) return callback(new Error('邮箱已存在'));
createUser(userData, onUserCreated);
}
function onUserCreated(error, user) {
if (error) return callback(error);
sendWelcomeEmail(user.email, onEmailSent);
function onEmailSent(error) {
if (error) {
console.warn('欢迎邮件发送失败:', error.message);
}
logUserRegistration(user.id, onLogCompleted);
function onLogCompleted(error) {
if (error) {
console.warn('日志记录失败:', error.message);
}
callback(null, user);
}
}
}
}
// 模拟函数(复用之前的实现)
function validateUsername(username, callback) {
setTimeout(function() {
callback(null, username && username.length >= 3);
}, 100);
}
function validateEmail(email, callback) {
setTimeout(function() {
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
callback(null, emailRegex.test(email));
}, 100);
}
function checkEmailExists(email, callback) {
setTimeout(function() {
callback(null, false); // 简化:假设不存在
}, 100);
}
function createUser(userData, callback) {
setTimeout(function() {
var user = {
id: Math.random().toString(36).substr(2, 9),
username: userData.username,
email: userData.email
};
callback(null, user);
}, 200);
}
function sendWelcomeEmail(email, callback) {
setTimeout(function() {
console.log('发送欢迎邮件:', email);
callback(null);
}, 100);
}
function logUserRegistration(userId, callback) {
setTimeout(function() {
console.log('记录注册日志:', userId);
callback(null);
}, 100);
}
improvedRegisterUser({
username: 'testuser',
email: '[email protected]'
}, function(error, user) {
if (error) {
console.error('命名函数方案 - 注册失败:', error.message);
} else {
console.log('命名函数方案 - 注册成功:', user);
}
});
}
namedFunctionsSolution();
解决方案2:模块化和拆分
// 解决方案2:将复杂逻辑拆分为独立的模块
function modularizationSolution() {
console.log('=== 模块化解决方案 ===');
// 验证模块
var Validator = {
username: function(username, callback) {
setTimeout(function() {
var isValid = username &&
username.length >= 3 &&
username.length <= 20 &&
/^[a-zA-Z0-9_]+$/.test(username);
callback(null, isValid);
}, 50);
},
email: function(email, callback) {
setTimeout(function() {
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
callback(null, emailRegex.test(email));
}, 50);
}
};
// 用户服务模块
var UserService = {
checkEmailExists: function(email, callback) {
setTimeout(function() {
// 模拟数据库查询
var existingEmails = ['[email protected]'];
callback(null, existingEmails.indexOf(email) !== -1);
}, 100);
},
create: function(userData, callback) {
setTimeout(function() {
var user = {
id: Math.random().toString(36).substr(2, 9),
username: userData.username,
email: userData.email,
createdAt: new Date().toISOString()
};
callback(null, user);
}, 200);
}
};
// 邮件服务模块
var EmailService = {
sendWelcome: function(email, callback) {
setTimeout(function() {
console.log('发送欢迎邮件到:', email);
callback(null);
}, 100);
}
};
// 日志服务模块
var LogService = {
userRegistration: function(userId, callback) {
setTimeout(function() {
console.log('记录用户注册:', userId);
callback(null);
}, 50);
}
};
// 用户注册流程协调器
var UserRegistration = {
register: function(userData, callback) {
var self = this;
this.validateUserData(userData, function(error) {
if (error) return callback(error);
self.createUserAccount(userData, function(error, user) {
if (error) return callback(error);
self.sendNotifications(user, function(error) {
// 通知发送失败不应阻止注册成功
if (error) {
console.warn('通知发送失败:', error.message);
}
callback(null, user);
});
});
});
},
validateUserData: function(userData, callback) {
var self = this;
Validator.username(userData.username, function(error, usernameValid) {
if (error) return callback(error);
if (!usernameValid) {
return callback(new Error('用户名格式无效'));
}
Validator.email(userData.email, function(error, emailValid) {
if (error) return callback(error);
if (!emailValid) {
return callback(new Error('邮箱格式无效'));
}
UserService.checkEmailExists(userData.email, function(error, exists) {
if (error) return callback(error);
if (exists) {
return callback(new Error('邮箱已被使用'));
}
callback(null);
});
});
});
},
createUserAccount: function(userData, callback) {
UserService.create(userData, callback);
},
sendNotifications: function(user, callback) {
var self = this;
EmailService.sendWelcome(user.email, function(error) {
if (error) return callback(error);
LogService.userRegistration(user.id, callback);
});
}
};
// 使用模块化的注册流程
UserRegistration.register({
username: 'moduleuser',
email: '[email protected]'
}, function(error, user) {
if (error) {
console.error('模块化方案 - 注册失败:', error.message);
} else {
console.log('模块化方案 - 注册成功:', user);
}
});
}
modularizationSolution();
解决方案3:使用控制流库
// 解决方案3:创建控制流工具函数
function controlFlowSolution() {
console.log('=== 控制流解决方案 ===');
// 简化版的控制流库
var Flow = {
// 串行执行,每个函数的结果传递给下一个
series: function(tasks, callback) {
var currentIndex = 0;
var results = [];
function runNext() {
if (currentIndex >= tasks.length) {
return callback(null, results);
}
var currentTask = tasks[currentIndex];
currentTask(function(error, result) {
if (error) return callback(error);
results.push(result);
currentIndex++;
runNext();
});
}
runNext();
},
// 瀑布流:前一个任务的结果传递给下一个任务
waterfall: function(tasks, callback) {
var currentIndex = 0;
function runNext() {
if (currentIndex >= tasks.length) {
return callback(null);
}
var args = Array.prototype.slice.call(arguments, 1);
var currentTask = tasks[currentIndex];
args.push(function(error) {
if (error) return callback(error);
currentIndex++;
runNext.apply(null, arguments);
});
currentTask.apply(null, args);
}
runNext();
},
// 并行执行
parallel: function(tasks, callback) {
if (tasks.length === 0) {
return callback(null, []);
}
var results = [];
var completedCount = 0;
var hasError = false;
tasks.forEach(function(task, index) {
task(function(error, result) {
if (hasError) return;
if (error) {
hasError = true;
return callback(error);
}
results[index] = result;
completedCount++;
if (completedCount === tasks.length) {
callback(null, results);
}
});
});
}
};
// 使用 waterfall 重写注册流程
function registerUserWithWaterfall(userData, callback) {
Flow.waterfall([
// 第一步:验证用户名
function(next) {
setTimeout(function() {
var isValid = userData.username && userData.username.length >= 3;
if (!isValid) {
return next(new Error('用户名无效'));
}
next(null, userData);
}, 100);
},
// 第二步:验证邮箱
function(userData, next) {
setTimeout(function() {
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(userData.email)) {
return next(new Error('邮箱格式无效'));
}
next(null, userData);
}, 100);
},
// 第三步:检查邮箱是否存在
function(userData, next) {
setTimeout(function() {
// 模拟检查
var exists = false;
if (exists) {
return next(new Error('邮箱已存在'));
}
next(null, userData);
}, 100);
},
// 第四步:创建用户
function(userData, next) {
setTimeout(function() {
var user = {
id: Math.random().toString(36).substr(2, 9),
username: userData.username,
email: userData.email,
createdAt: new Date().toISOString()
};
next(null, user);
}, 200);
},
// 第五步:发送欢迎邮件和记录日志(并行执行)
function(user, next) {
Flow.parallel([
function(parallelCallback) {
setTimeout(function() {
console.log('发送欢迎邮件到:', user.email);
parallelCallback(null, 'email-sent');
}, 100);
},
function(parallelCallback) {
setTimeout(function() {
console.log('记录注册日志:', user.id);
parallelCallback(null, 'log-recorded');
}, 50);
}
], function(error, results) {
if (error) {
console.warn('后续操作失败:', error.message);
}
next(null, user); // 即使后续操作失败,用户创建仍然成功
});
}
], callback);
}
// 使用控制流版本
registerUserWithWaterfall({
username: 'flowuser',
email: '[email protected]'
}, function(error, user) {
if (error) {
console.error('控制流方案 - 注册失败:', error.message);
} else {
console.log('控制流方案 - 注册成功:', user);
}
});
}
controlFlowSolution();
解决方案4:使用 try-catch 包装
// 解决方案4:统一的错误处理包装器
function errorHandlingSolution() {
console.log('=== 统一错误处理解决方案 ===');
// 创建一个安全的异步包装器
function safeAsync(asyncFunction) {
return function() {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
if (typeof callback !== 'function') {
throw new Error('最后一个参数必须是回调函数');
}
// 包装回调以统一错误处理
var wrappedCallback = function(error, result) {
if (error) {
console.error('异步操作错误:', {
error: error.message,
stack: error.stack,
function: asyncFunction.name,
timestamp: new Date().toISOString()
});
}
callback(error, result);
};
args.push(wrappedCallback);
try {
asyncFunction.apply(null, args);
} catch (syncError) {
// 捕获同步错误并转换为异步错误
setTimeout(function() {
wrappedCallback(syncError);
}, 0);
}
};
}
// 创建重试包装器
function withRetry(asyncFunction, maxRetries) {
return function() {
var args = Array.prototype.slice.call(arguments);
var originalCallback = args.pop();
var attempt = 0;
function attemptExecution() {
attempt++;
var retryCallback = function(error, result) {
if (error && attempt <= maxRetries) {
console.warn('操作失败,重试第 ' + attempt + ' 次:', error.message);
setTimeout(attemptExecution, Math.pow(2, attempt - 1) * 1000);
} else {
originalCallback(error, result);
}
};
args.push(retryCallback);
asyncFunction.apply(null, args.slice(0, -1).concat([retryCallback]));
}
attemptExecution();
};
}
// 使用包装器的例子
var safeApiCall = safeAsync(function apiCall(url, callback) {
setTimeout(function() {
// 模拟随机失败
if (Math.random() < 0.3) {
callback(new Error('网络错误'));
} else {
callback(null, { data: 'API响应数据', url: url });
}
}, 100);
});
var retryableApiCall = withRetry(safeApiCall, 3);
// 使用示例
retryableApiCall('/api/data', function(error, result) {
if (error) {
console.error('API调用最终失败:', error.message);
} else {
console.log('API调用成功:', result);
}
});
}
errorHandlingSolution();
解决方案5:Promise 模式(ES5实现)
// 解决方案5:在 ES5 中实现简单的 Promise 模式
function promiseLikeSolution() {
console.log('=== Promise-like 解决方案 ===');
// 简化的 Promise 实现
function SimplePromise(executor) {
var self = this;
this.state = 'pending'; // pending, fulfilled, rejected
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
function resolve(value) {
if (self.state === 'pending') {
self.state = 'fulfilled';
self.value = value;
self.onFulfilledCallbacks.forEach(function(callback) {
callback(value);
});
}
}
function reject(reason) {
if (self.state === 'pending') {
self.state = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(function(callback) {
callback(reason);
});
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
SimplePromise.prototype.then = function(onFulfilled, onRejected) {
var self = this;
return new SimplePromise(function(resolve, reject) {
function handleFulfilled() {
try {
if (typeof onFulfilled === 'function') {
var result = onFulfilled(self.value);
resolve(result);
} else {
resolve(self.value);
}
} catch (error) {
reject(error);
}
}
function handleRejected() {
try {
if (typeof onRejected === 'function') {
var result = onRejected(self.reason);
resolve(result);
} else {
reject(self.reason);
}
} catch (error) {
reject(error);
}
}
if (self.state === 'fulfilled') {
setTimeout(handleFulfilled, 0);
} else if (self.state === 'rejected') {
setTimeout(handleRejected, 0);
} else {
self.onFulfilledCallbacks.push(handleFulfilled);
self.onRejectedCallbacks.push(handleRejected);
}
});
};
SimplePromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
};
// 工具函数:将回调函数转换为 Promise
function promisify(asyncFunction) {
return function() {
var args = Array.prototype.slice.call(arguments);
return new SimplePromise(function(resolve, reject) {
args.push(function(error, result) {
if (error) {
reject(error);
} else {
resolve(result);
}
});
asyncFunction.apply(null, args);
});
};
}
// 模拟异步函数
function asyncValidateUser(username, callback) {
setTimeout(function() {
var isValid = username && username.length >= 3;
if (isValid) {
callback(null, { username: username, valid: true });
} else {
callback(new Error('用户名无效'));
}
}, 100);
}
function asyncCreateUser(userData, callback) {
setTimeout(function() {
var user = {
id: Math.random().toString(36).substr(2, 9),
username: userData.username,
createdAt: new Date().toISOString()
};
callback(null, user);
}, 200);
}
function asyncSendEmail(email, callback) {
setTimeout(function() {
console.log('发送邮件到:', email);
callback(null, 'email-sent');
}, 100);
}
// 转换为 Promise 风格
var validateUser = promisify(asyncValidateUser);
var createUser = promisify(asyncCreateUser);
var sendEmail = promisify(asyncSendEmail);
// 使用 Promise 链式调用替代回调地狱
function registerUserWithPromises(username, email) {
return validateUser(username)
.then(function(validationResult) {
console.log('用户验证通过:', validationResult);
return createUser({
username: validationResult.username,
email: email
});
})
.then(function(user) {
console.log('用户创建成功:', user);
return sendEmail(user.email || email);
})
.then(function(emailResult) {
console.log('邮件发送成功:', emailResult);
return 'registration-complete';
})
.catch(function(error) {
console.error('注册过程出错:', error.message);
throw error;
});
}
// 使用 Promise 版本
registerUserWithPromises('promiseuser', '[email protected]')
.then(function(result) {
console.log('Promise方案 - 注册完成:', result);
})
.catch(function(error) {
console.error('Promise方案 - 注册失败:', error.message);
});
}
promiseLikeSolution();
最佳实践总结:
// 回调地狱解决方案的最佳实践
function bestPracticesExample() {
console.log('=== 最佳实践总结 ===');
// 1. 保持代码浅层嵌套
// 2. 使用命名函数
// 3. 模块化设计
// 4. 统一错误处理
// 5. 适当使用工具库
var BestPractices = {
// 统一的错误处理约定
handleError: function(error, context) {
console.error('错误 [' + context + ']:', error.message);
// 可以添加错误上报、日志记录等
},
// 创建标准的异步包装器
wrapAsync: function(name, asyncFn) {
return function() {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop();
console.log('开始执行:', name);
var startTime = Date.now();
args.push(function(error, result) {
var duration = Date.now() - startTime;
console.log('完成执行:', name, '耗时:', duration + 'ms');
callback(error, result);
});
asyncFn.apply(null, args);
};
},
// 组合多个异步操作
compose: function() {
var functions = Array.prototype.slice.call(arguments);
return function(initialData, callback) {
var currentIndex = 0;
var currentData = initialData;
function runNext() {
if (currentIndex >= functions.length) {
return callback(null, currentData);
}
var currentFn = functions[currentIndex];
currentFn(currentData, function(error, result) {
if (error) return callback(error);
currentData = result;
currentIndex++;
runNext();
});
}
runNext();
};
}
};
// 使用最佳实践重构注册流程
var validateStep = BestPractices.wrapAsync('验证用户', function(userData, callback) {
setTimeout(function() {
if (!userData.username || userData.username.length < 3) {
return callback(new Error('用户名无效'));
}
callback(null, userData);
}, 100);
});
var createStep = BestPractices.wrapAsync('创建用户', function(userData, callback) {
setTimeout(function() {
var user = Object.assign({}, userData, {
id: Math.random().toString(36).substr(2, 9),
createdAt: new Date().toISOString()
});
callback(null, user);
}, 200);
});
var notifyStep = BestPractices.wrapAsync('发送通知', function(user, callback) {
setTimeout(function() {
console.log('发送通知给用户:', user.username);
callback(null, user);
}, 100);
});
// 组合所有步骤
var registerProcess = BestPractices.compose(
validateStep,
createStep,
notifyStep
);
// 执行注册流程
registerProcess({
username: 'bestpractice',
email: '[email protected]'
}, function(error, result) {
if (error) {
BestPractices.handleError(error, '用户注册');
} else {
console.log('最佳实践方案 - 注册成功:', result);
}
});
}
bestPracticesExample();
回调地狱解决方案对比:
| 解决方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 命名函数 | 简单、易理解、调试友好 | 仍有嵌套、函数分散 | 简单的异步流程 |
| 模块化 | 代码组织清晰、易测试 | 需要更多设计、复杂度增加 | 复杂业务逻辑 |
| 控制流库 | 代码简洁、流程清晰 | 需要额外库、学习成本 | 标准异步模式 |
| 统一错误处理 | 错误处理一致、易调试 | 包装复杂、性能开销 | 需要严格错误管理 |
| Promise模式 | 链式调用、错误冒泡 | 实现复杂、ES5不原生支持 | 现代异步编程风格 |
总结:
回调地狱是 ES5 异步编程的主要痛点,通过合理的代码组织、工具函数和设计模式,可以有效缓解这个问题,为后来的 Promise、async/await 等现代异步解决方案奠定了基础。
What are the common memory leak scenarios?
What are the common memory leak scenarios?
考察点:对内存管理和性能优化的实战经验。
答案:
内存泄漏是指程序中已分配的内存由于某种原因无法释放或回收,导致内存使用量不断增长的现象。在 JavaScript 中,虽然有垃圾回收机制,但不当的编程实践仍然可能导致内存泄漏。
1. 全局变量导致的内存泄漏:
// 常见的全局变量内存泄漏场景
function globalVariableLeaks() {
console.log('=== 全局变量内存泄漏 ===');
// 问题1:意外的全局变量
function createAccidentalGlobals() {
// 忘记使用 var 声明,创建了全局变量
accidentalGlobal = 'This becomes global!'; // 内存泄漏
// 在严格模式下会报错,但在非严格模式下会创建全局变量
this.anotherGlobal = 'Another global variable'; // 内存泄漏
}
// 问题2:全局对象累积
var globalCache = {}; // 全局缓存对象
function addToGlobalCache(key, value) {
globalCache[key] = value; // 持续添加但从不清理
// 如果不定期清理,globalCache 会无限增长
}
// 模拟数据累积
for (var i = 0; i < 1000; i++) {
addToGlobalCache('item' + i, {
id: i,
data: new Array(1000).fill('large data'),
timestamp: Date.now()
});
}
console.log('全局缓存大小:', Object.keys(globalCache).length);
// 解决方案1:使用局部作用域
function betterCaching() {
var localCache = {};
var maxCacheSize = 100;
var cacheKeys = [];
return {
set: function(key, value) {
if (cacheKeys.length >= maxCacheSize) {
// LRU 清理:删除最旧的缓存项
var oldestKey = cacheKeys.shift();
delete localCache[oldestKey];
}
localCache[key] = value;
cacheKeys.push(key);
},
get: function(key) {
return localCache[key];
},
clear: function() {
localCache = {};
cacheKeys = [];
},
size: function() {
return cacheKeys.length;
}
};
}
var smartCache = betterCaching();
// 测试智能缓存
for (var i = 0; i < 150; i++) {
smartCache.set('item' + i, { data: 'test' + i });
}
console.log('智能缓存大小:', smartCache.size()); // 应该不超过 100
createAccidentalGlobals();
}
globalVariableLeaks();
2. 闭包导致的内存泄漏:
// 闭包内存泄漏场景
function closureMemoryLeaks() {
console.log('=== 闭包内存泄漏 ===');
// 问题1:不必要的闭包引用
function createProblematicClosure() {
var largeData = new Array(1000000).fill('large string data');
var someOtherData = new Array(1000000).fill('more data');
return function(index) {
// 这个函数只需要 largeData,但闭包会保持对所有外部变量的引用
return largeData[index];
};
}
// 问题2:事件处理器中的闭包
function attachEventHandlers() {
var handlers = [];
for (var i = 0; i < 1000; i++) {
var element = document.createElement('div');
var largeObject = {
id: i,
data: new Array(10000).fill('event data ' + i)
};
// 问题:闭包捕获了 largeObject,即使元素被移除,对象仍然存在
element.onclick = function() {
console.log('Clicked element:', largeObject.id);
};
handlers.push(element);
}
return handlers;
}
// 解决方案1:及时清理闭包引用
function createOptimizedClosure() {
var largeData = new Array(1000000).fill('large string data');
// 不需要的数据不要在闭包作用域中声明
var accessor = function(index) {
return largeData[index];
};
// 提供清理方法
accessor.cleanup = function() {
largeData = null; // 显式清理引用
};
return accessor;
}
// 解决方案2:优化事件处理器
function attachOptimizedEventHandlers() {
var handlers = [];
function createEventHandler(id) {
return function() {
console.log('Clicked element:', id);
// 不引用外部的大对象
};
}
for (var i = 0; i < 1000; i++) {
var element = document.createElement('div');
element.onclick = createEventHandler(i); // 只传递必要的数据
handlers.push(element);
}
return handlers;
}
// 解决方案3:WeakMap 模式(ES5 替代方案)
function createWeakMapAlternative() {
var privateData = [];
var elementToIndex = [];
return {
set: function(element, data) {
var index = elementToIndex.indexOf(element);
if (index === -1) {
index = privateData.length;
elementToIndex.push(element);
privateData.push(data);
} else {
privateData[index] = data;
}
},
get: function(element) {
var index = elementToIndex.indexOf(element);
return index !== -1 ? privateData[index] : undefined;
},
delete: function(element) {
var index = elementToIndex.indexOf(element);
if (index !== -1) {
elementToIndex.splice(index, 1);
privateData.splice(index, 1);
}
}
};
}
var dataMap = createWeakMapAlternative();
// 测试优化方案
var optimizedAccessor = createOptimizedClosure();
console.log('访问数据:', optimizedAccessor(100));
// 清理资源
optimizedAccessor.cleanup();
var optimizedHandlers = attachOptimizedEventHandlers();
console.log('优化的事件处理器数量:', optimizedHandlers.length);
}
closureMemoryLeaks();
3. DOM 相关的内存泄漏:
// DOM 内存泄漏场景
function domMemoryLeaks() {
console.log('=== DOM 内存泄漏 ===');
// 问题1:DOM 元素的循环引用
function createCircularReference() {
var elements = [];
for (var i = 0; i < 100; i++) {
var element = document.createElement('div');
element.id = 'element-' + i;
// 创建循环引用
element.customProperty = {
element: element, // 元素引用自己
data: new Array(1000).fill('data for element ' + i)
};
// 父子元素之间的循环引用
var child = document.createElement('span');
child.parent = element;
element.child = child;
elements.push(element);
}
return elements;
}
// 问题2:未清理的 DOM 事件监听器
function createLeakyEventListeners() {
var elements = [];
var largeData = new Array(100000).fill('shared large data');
for (var i = 0; i < 50; i++) {
var element = document.createElement('button');
element.textContent = 'Button ' + i;
// 事件处理器引用外部大数据
element.addEventListener('click', function() {
console.log('Button clicked, data size:', largeData.length);
});
elements.push(element);
}
// 即使从 DOM 中移除元素,事件监听器仍然存在
return elements;
}
// 问题3:分离的 DOM 节点
function createDetachedNodes() {
var container = document.createElement('div');
var detachedElements = [];
for (var i = 0; i < 100; i++) {
var element = document.createElement('div');
element.innerHTML = '<span>Content ' + i + '</span>';
// 添加大量数据到元素
element.largeDataSet = new Array(1000).fill({
id: i,
content: 'Large content for element ' + i
});
container.appendChild(element);
// 保持对元素的引用,但从 DOM 中移除
setTimeout(function(el) {
return function() {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
// 元素被移除但 detachedElements 数组仍然引用它们
detachedElements.push(el);
};
}(element), Math.random() * 1000);
}
return detachedElements;
}
// 解决方案1:避免循环引用
function createSafeElements() {
var elements = [];
var elementData = []; // 分离数据存储
for (var i = 0; i < 100; i++) {
var element = document.createElement('div');
element.id = 'safe-element-' + i;
// 将数据存储在单独的数组中,通过索引关联
var dataIndex = elementData.length;
elementData.push({
elementId: element.id,
data: new Array(1000).fill('data for element ' + i)
});
element.setAttribute('data-index', dataIndex);
elements.push(element);
}
return {
elements: elements,
getData: function(element) {
var index = parseInt(element.getAttribute('data-index'), 10);
return elementData[index];
},
cleanup: function() {
elementData = [];
elements.forEach(function(el) {
el.removeAttribute('data-index');
});
elements = [];
}
};
}
// 解决方案2:正确管理事件监听器
function createManagedEventListeners() {
var listeners = [];
function addListener(element, event, handler) {
element.addEventListener(event, handler);
listeners.push({
element: element,
event: event,
handler: handler
});
}
function removeAllListeners() {
listeners.forEach(function(listener) {
listener.element.removeEventListener(listener.event, listener.handler);
});
listeners = [];
}
// 使用示例
var button = document.createElement('button');
var clickHandler = function() {
console.log('Button clicked safely');
};
addListener(button, 'click', clickHandler);
return {
button: button,
cleanup: removeAllListeners
};
}
// 解决方案3:DOM 节点清理管理器
function createDOMManager() {
var managedElements = [];
return {
createElement: function(tagName) {
var element = document.createElement(tagName);
managedElements.push({
element: element,
created: Date.now()
});
return element;
},
removeElement: function(element) {
// 清理事件监听器
var clone = element.cloneNode(false);
if (element.parentNode) {
element.parentNode.replaceChild(clone, element);
element.parentNode.removeChild(clone);
}
// 从管理列表中移除
managedElements = managedElements.filter(function(item) {
return item.element !== element;
});
// 清理自定义属性
for (var prop in element) {
if (element.hasOwnProperty(prop)) {
delete element[prop];
}
}
},
cleanupAll: function() {
managedElements.forEach(function(item) {
this.removeElement(item.element);
}, this);
managedElements = [];
},
getManagedCount: function() {
return managedElements.length;
}
};
}
// 测试解决方案
var safeElements = createSafeElements();
console.log('安全元素数量:', safeElements.elements.length);
var managedListeners = createManagedEventListeners();
console.log('托管监听器已创建');
var domManager = createDOMManager();
var testElement = domManager.createElement('div');
console.log('DOM管理器创建的元素数量:', domManager.getManagedCount());
// 清理资源
safeElements.cleanup();
managedListeners.cleanup();
domManager.cleanupAll();
}
domMemoryLeaks();
4. 定时器和异步操作导致的内存泄漏:
// 定时器和异步操作内存泄漏
function timerMemoryLeaks() {
console.log('=== 定时器内存泄漏 ===');
// 问题1:未清理的 setInterval
function createLeakyTimer() {
var largeDataArray = [];
var intervalId = setInterval(function() {
// 每次添加大量数据
largeDataArray.push({
timestamp: Date.now(),
data: new Array(10000).fill('timer data')
});
console.log('Timer tick, array size:', largeDataArray.length);
// 忘记清理条件或清理定时器
}, 1000);
// 返回了 intervalId 但调用者可能忘记清理
return intervalId;
}
// 问题2:setTimeout 链式调用
function createTimeoutChain() {
var chainData = [];
function scheduleNext() {
chainData.push({
id: chainData.length,
data: new Array(5000).fill('timeout chain data')
});
// 无限递归的 setTimeout 链
setTimeout(scheduleNext, 500);
}
scheduleNext();
// 没有提供停止机制
}
// 问题3:异步操作中的内存累积
function createAsyncLeak() {
var pendingRequests = [];
var responseCache = {};
function makeRequest(url) {
var requestData = {
url: url,
timestamp: Date.now(),
largePayload: new Array(1000).fill('request data')
};
pendingRequests.push(requestData);
// 模拟异步请求
setTimeout(function() {
// 响应数据累积在缓存中,从不清理
responseCache[url] = {
response: 'Response for ' + url,
timestamp: Date.now(),
requestData: requestData // 保持对请求数据的引用
};
// 忘记从 pendingRequests 中移除
console.log('Request completed:', url);
}, Math.random() * 2000);
}
// 发起大量请求
for (var i = 0; i < 100; i++) {
makeRequest('/api/endpoint-' + i);
}
return {
pending: pendingRequests,
cache: responseCache
};
}
// 解决方案1:定时器管理器
function createTimerManager() {
var timers = [];
return {
setInterval: function(callback, delay) {
var id = setInterval(callback, delay);
timers.push({ id: id, type: 'interval' });
return id;
},
setTimeout: function(callback, delay) {
var id = setTimeout(function() {
// 自动从管理列表中移除
timers = timers.filter(function(timer) {
return timer.id !== id;
});
callback();
}, delay);
timers.push({ id: id, type: 'timeout' });
return id;
},
clearTimer: function(id) {
clearInterval(id);
clearTimeout(id);
timers = timers.filter(function(timer) {
return timer.id !== id;
});
},
clearAll: function() {
timers.forEach(function(timer) {
if (timer.type === 'interval') {
clearInterval(timer.id);
} else {
clearTimeout(timer.id);
}
});
timers = [];
},
getActiveCount: function() {
return timers.length;
}
};
}
// 解决方案2:带生命周期管理的定时器
function createManagedTimer(maxDuration) {
var startTime = Date.now();
var isActive = true;
var intervals = [];
return {
setInterval: function(callback, delay) {
if (!isActive) return null;
var wrappedCallback = function() {
if (!isActive || Date.now() - startTime > maxDuration) {
this.cleanup();
return;
}
callback();
}.bind(this);
var id = setInterval(wrappedCallback, delay);
intervals.push(id);
return id;
},
cleanup: function() {
isActive = false;
intervals.forEach(clearInterval);
intervals = [];
},
isAlive: function() {
return isActive && Date.now() - startTime <= maxDuration;
}
};
}
// 解决方案3:异步请求管理器
function createRequestManager() {
var pendingRequests = [];
var responseCache = {};
var maxCacheSize = 50;
var cacheKeys = [];
return {
makeRequest: function(url, callback) {
var requestId = Date.now() + '-' + Math.random();
var request = {
id: requestId,
url: url,
timestamp: Date.now()
};
pendingRequests.push(request);
// 模拟异步请求
setTimeout(function() {
// 从待处理列表中移除
pendingRequests = pendingRequests.filter(function(req) {
return req.id !== requestId;
});
// 管理缓存大小
if (cacheKeys.length >= maxCacheSize) {
var oldestKey = cacheKeys.shift();
delete responseCache[oldestKey];
}
responseCache[url] = {
response: 'Response for ' + url,
timestamp: Date.now()
};
cacheKeys.push(url);
if (callback) callback(null, responseCache[url]);
}, Math.random() * 1000);
return requestId;
},
cancelRequest: function(requestId) {
pendingRequests = pendingRequests.filter(function(req) {
return req.id !== requestId;
});
},
clearCache: function() {
responseCache = {};
cacheKeys = [];
},
getStatus: function() {
return {
pending: pendingRequests.length,
cached: cacheKeys.length
};
}
};
}
// 测试定时器管理器
var timerManager = createTimerManager();
var intervalId = timerManager.setInterval(function() {
console.log('Managed interval tick');
}, 1000);
var timeoutId = timerManager.setTimeout(function() {
console.log('Managed timeout executed');
}, 2000);
console.log('活跃定时器数量:', timerManager.getActiveCount());
// 测试托管定时器
var managedTimer = createManagedTimer(5000); // 5秒后自动清理
managedTimer.setInterval(function() {
console.log('Managed timer with lifecycle');
}, 800);
// 测试请求管理器
var requestManager = createRequestManager();
var reqId1 = requestManager.makeRequest('/api/test1', function(error, response) {
console.log('请求完成:', response.response);
});
var reqId2 = requestManager.makeRequest('/api/test2');
console.log('请求状态:', requestManager.getStatus());
// 5秒后清理所有资源
setTimeout(function() {
console.log('清理所有定时器资源');
timerManager.clearAll();
managedTimer.cleanup();
requestManager.clearCache();
console.log('清理后的定时器数量:', timerManager.getActiveCount());
console.log('清理后的请求状态:', requestManager.getStatus());
}, 5000);
}
timerMemoryLeaks();
5. 对象引用和数据结构导致的内存泄漏:
// 对象引用内存泄漏
function objectReferenceLeaks() {
console.log('=== 对象引用内存泄漏 ===');
// 问题1:观察者模式中的内存泄漏
function createLeakyObserver() {
var observers = [];
var largeDataStore = {};
return {
subscribe: function(callback) {
var observer = {
id: Date.now() + Math.random(),
callback: callback,
data: new Array(1000).fill('observer data') // 大量数据
};
observers.push(observer);
largeDataStore[observer.id] = observer;
return observer.id;
},
notify: function(data) {
observers.forEach(function(observer) {
observer.callback(data);
});
},
// 问题:缺少 unsubscribe 方法,或者 unsubscribe 实现不当
unsubscribe: function(id) {
// 只从 observers 数组中移除,忘记清理 largeDataStore
observers = observers.filter(function(observer) {
return observer.id !== id;
});
// delete largeDataStore[id]; // 忘记这一行
},
getObserverCount: function() {
return observers.length;
},
getStoreSize: function() {
return Object.keys(largeDataStore).length;
}
};
}
// 问题2:缓存对象的无限增长
function createLeakyCache() {
var cache = {};
var metadata = {};
return {
set: function(key, value) {
cache[key] = value;
metadata[key] = {
timestamp: Date.now(),
accessCount: 0,
size: JSON.stringify(value).length
};
},
get: function(key) {
if (cache[key]) {
metadata[key].accessCount++;
metadata[key].lastAccessed = Date.now();
}
return cache[key];
},
// 缺少清理机制
getCacheSize: function() {
return Object.keys(cache).length;
}
};
}
// 问题3:数组和对象的相互引用
function createCircularReferences() {
var items = [];
var lookup = {};
for (var i = 0; i < 100; i++) {
var item = {
id: i,
data: new Array(1000).fill('item data ' + i),
relationships: []
};
// 创建循环引用
item.selfRef = item;
item.parent = lookup;
items.push(item);
lookup[i] = item;
// 创建与其他对象的引用关系
if (i > 0) {
item.relationships.push(items[i - 1]);
items[i - 1].relationships.push(item);
}
}
return { items: items, lookup: lookup };
}
// 解决方案1:改进的观察者模式
function createSafeObserver() {
var observers = [];
var observerData = {}; // 分离的数据存储
return {
subscribe: function(callback, options) {
options = options || {};
var id = Date.now() + Math.random();
var observer = {
id: id,
callback: callback,
once: options.once || false,
weight: options.weight || 1 // 用于优先级排序
};
observers.push(observer);
// 可选的大数据存储
if (options.data) {
observerData[id] = options.data;
}
return id;
},
unsubscribe: function(id) {
observers = observers.filter(function(observer) {
return observer.id !== id;
});
// 确保清理相关数据
delete observerData[id];
},
notify: function(data) {
// 过滤一次性观察者
var activeObservers = observers.slice();
activeObservers.forEach(function(observer) {
try {
observer.callback(data);
// 移除一次性观察者
if (observer.once) {
this.unsubscribe(observer.id);
}
} catch (error) {
console.error('观察者回调错误:', error);
}
}, this);
},
clear: function() {
observers = [];
observerData = {};
},
getStatus: function() {
return {
observerCount: observers.length,
dataStoreSize: Object.keys(observerData).length
};
}
};
}
// 解决方案2:智能缓存系统
function createSmartCache(options) {
options = options || {};
var maxSize = options.maxSize || 100;
var ttl = options.ttl || 300000; // 5分钟 TTL
var cache = {};
var metadata = {};
var accessOrder = []; // LRU 跟踪
function cleanup() {
var now = Date.now();
// 清理过期项
Object.keys(metadata).forEach(function(key) {
if (now - metadata[key].timestamp > ttl) {
delete cache[key];
delete metadata[key];
var index = accessOrder.indexOf(key);
if (index !== -1) {
accessOrder.splice(index, 1);
}
}
});
// LRU 清理
while (accessOrder.length > maxSize) {
var lruKey = accessOrder.shift();
delete cache[lruKey];
delete metadata[lruKey];
}
}
return {
set: function(key, value) {
cleanup();
cache[key] = value;
metadata[key] = {
timestamp: Date.now(),
accessCount: 0,
size: JSON.stringify(value).length
};
// 更新 LRU 顺序
var index = accessOrder.indexOf(key);
if (index !== -1) {
accessOrder.splice(index, 1);
}
accessOrder.push(key);
},
get: function(key) {
cleanup();
if (cache[key]) {
metadata[key].accessCount++;
metadata[key].lastAccessed = Date.now();
// 更新 LRU 顺序
var index = accessOrder.indexOf(key);
if (index !== -1) {
accessOrder.splice(index, 1);
accessOrder.push(key);
}
}
return cache[key];
},
clear: function() {
cache = {};
metadata = {};
accessOrder = [];
},
getStats: function() {
cleanup();
return {
size: Object.keys(cache).length,
maxSize: maxSize,
oldestEntry: accessOrder[0],
newestEntry: accessOrder[accessOrder.length - 1]
};
}
};
}
// 解决方案3:避免循环引用的对象管理器
function createObjectManager() {
var objects = [];
var relationships = []; // 分离关系存储
var nextId = 0;
return {
createObject: function(data) {
var id = nextId++;
var obj = {
id: id,
data: data,
created: Date.now()
};
objects.push(obj);
return id;
},
getObject: function(id) {
return objects.find(function(obj) {
return obj.id === id;
});
},
addRelationship: function(fromId, toId, type) {
type = type || 'default';
relationships.push({
from: fromId,
to: toId,
type: type,
created: Date.now()
});
},
getRelationships: function(objectId) {
return relationships.filter(function(rel) {
return rel.from === objectId || rel.to === objectId;
});
},
removeObject: function(id) {
// 移除对象
objects = objects.filter(function(obj) {
return obj.id !== id;
});
// 清理相关的关系
relationships = relationships.filter(function(rel) {
return rel.from !== id && rel.to !== id;
});
},
cleanup: function() {
objects = [];
relationships = [];
nextId = 0;
},
getStats: function() {
return {
objectCount: objects.length,
relationshipCount: relationships.length
};
}
};
}
// 测试解决方案
console.log('测试改进的观察者模式:');
var safeObserver = createSafeObserver();
var observerId1 = safeObserver.subscribe(function(data) {
console.log('观察者1收到:', data);
});
var observerId2 = safeObserver.subscribe(function(data) {
console.log('观察者2收到:', data);
}, { once: true });
safeObserver.notify('测试消息');
console.log('通知后状态:', safeObserver.getStatus());
safeObserver.notify('第二条消息'); // 观察者2不会收到
console.log('第二次通知后状态:', safeObserver.getStatus());
console.log('\n测试智能缓存:');
var smartCache = createSmartCache({ maxSize: 3, ttl: 1000 });
smartCache.set('key1', { data: 'value1' });
smartCache.set('key2', { data: 'value2' });
smartCache.set('key3', { data: 'value3' });
smartCache.set('key4', { data: 'value4' }); // 应该触发 LRU 清理
console.log('缓存统计:', smartCache.getStats());
console.log('\n测试对象管理器:');
var objectManager = createObjectManager();
var obj1 = objectManager.createObject({ name: '对象1' });
var obj2 = objectManager.createObject({ name: '对象2' });
var obj3 = objectManager.createObject({ name: '对象3' });
objectManager.addRelationship(obj1, obj2, 'friend');
objectManager.addRelationship(obj2, obj3, 'colleague');
console.log('对象管理器统计:', objectManager.getStats());
console.log('对象1的关系:', objectManager.getRelationships(obj1));
// 清理资源
safeObserver.clear();
smartCache.clear();
objectManager.cleanup();
}
objectReferenceLeaks();
6. 内存泄漏检测和预防工具:
// 内存泄漏检测和预防工具
function memoryLeakDetectionTools() {
console.log('=== 内存泄漏检测工具 ===');
// 内存使用监控器
function createMemoryMonitor() {
var snapshots = [];
var isMonitoring = false;
var monitorInterval;
return {
start: function(interval) {
if (isMonitoring) return;
interval = interval || 1000;
isMonitoring = true;
monitorInterval = setInterval(function() {
var snapshot = {
timestamp: Date.now(),
usedJSHeapSize: window.performance && window.performance.memory
? window.performance.memory.usedJSHeapSize
: null,
totalJSHeapSize: window.performance && window.performance.memory
? window.performance.memory.totalJSHeapSize
: null,
jsHeapSizeLimit: window.performance && window.performance.memory
? window.performance.memory.jsHeapSizeLimit
: null
};
snapshots.push(snapshot);
// 保持最近100个快照
if (snapshots.length > 100) {
snapshots.shift();
}
// 检测内存泄漏趋势
if (snapshots.length >= 10) {
this.checkMemoryTrend();
}
}.bind(this), interval);
},
stop: function() {
if (monitorInterval) {
clearInterval(monitorInterval);
isMonitoring = false;
}
},
checkMemoryTrend: function() {
if (snapshots.length < 10) return null;
var recent = snapshots.slice(-10);
var oldSnapshots = snapshots.slice(-20, -10);
if (recent.length < 10 || oldSnapshots.length < 10) return null;
var recentAvg = recent.reduce(function(sum, s) {
return sum + (s.usedJSHeapSize || 0);
}, 0) / recent.length;
var oldAvg = oldSnapshots.reduce(function(sum, s) {
return sum + (s.usedJSHeapSize || 0);
}, 0) / oldSnapshots.length;
var growthRate = (recentAvg - oldAvg) / oldAvg;
if (growthRate > 0.1) { // 10% 增长警告
console.warn('检测到可能的内存泄漏,增长率:', (growthRate * 100).toFixed(2) + '%');
return {
trend: 'increasing',
growthRate: growthRate,
recentAverage: recentAvg,
oldAverage: oldAvg
};
}
return {
trend: 'stable',
growthRate: growthRate
};
},
getReport: function() {
if (snapshots.length === 0) return null;
var latest = snapshots[snapshots.length - 1];
var oldest = snapshots[0];
return {
duration: latest.timestamp - oldest.timestamp,
snapshots: snapshots.length,
current: latest,
trend: this.checkMemoryTrend(),
peak: snapshots.reduce(function(max, s) {
return s.usedJSHeapSize > max.usedJSHeapSize ? s : max;
})
};
}
};
}
// 对象引用跟踪器
function createReferenceTracker() {
var trackedObjects = new Map ? new Map() : createMapPolyfill();
var nextId = 1;
function createMapPolyfill() {
var keys = [];
var values = [];
return {
set: function(key, value) {
var index = keys.indexOf(key);
if (index === -1) {
keys.push(key);
values.push(value);
} else {
values[index] = value;
}
},
get: function(key) {
var index = keys.indexOf(key);
return index !== -1 ? values[index] : undefined;
},
has: function(key) {
return keys.indexOf(key) !== -1;
},
delete: function(key) {
var index = keys.indexOf(key);
if (index !== -1) {
keys.splice(index, 1);
values.splice(index, 1);
return true;
}
return false;
},
size: function() {
return keys.length;
},
clear: function() {
keys = [];
values = [];
}
};
}
return {
track: function(obj, name) {
var id = nextId++;
var metadata = {
id: id,
name: name || 'unnamed',
created: Date.now(),
lastAccessed: Date.now()
};
trackedObjects.set(obj, metadata);
return id;
},
access: function(obj) {
var metadata = trackedObjects.get(obj);
if (metadata) {
metadata.lastAccessed = Date.now();
metadata.accessCount = (metadata.accessCount || 0) + 1;
}
},
untrack: function(obj) {
return trackedObjects.delete(obj);
},
getStaleObjects: function(maxAge) {
maxAge = maxAge || 300000; // 5分钟
var now = Date.now();
var staleObjects = [];
// 遍历跟踪的对象
if (trackedObjects.forEach) {
trackedObjects.forEach(function(metadata, obj) {
if (now - metadata.lastAccessed > maxAge) {
staleObjects.push({
object: obj,
metadata: metadata,
ageMs: now - metadata.lastAccessed
});
}
});
}
return staleObjects;
},
getReport: function() {
var totalTracked = trackedObjects.size ? trackedObjects.size() : 0;
var staleObjects = this.getStaleObjects();
return {
totalTracked: totalTracked,
staleCount: staleObjects.length,
staleObjects: staleObjects.map(function(item) {
return {
name: item.metadata.name,
id: item.metadata.id,
ageMs: item.ageMs
};
})
};
},
cleanup: function() {
trackedObjects.clear();
nextId = 1;
}
};
}
// 资源生命周期管理器
function createResourceManager() {
var resources = [];
var categories = {};
return {
register: function(resource, category, cleanup) {
category = category || 'default';
var resourceItem = {
id: Date.now() + Math.random(),
resource: resource,
category: category,
cleanup: cleanup,
created: Date.now(),
isActive: true
};
resources.push(resourceItem);
if (!categories[category]) {
categories[category] = [];
}
categories[category].push(resourceItem);
return resourceItem.id;
},
unregister: function(id) {
var resourceItem = resources.find(function(item) {
return item.id === id;
});
if (resourceItem && resourceItem.isActive) {
if (resourceItem.cleanup) {
try {
resourceItem.cleanup(resourceItem.resource);
} catch (error) {
console.error('资源清理失败:', error);
}
}
resourceItem.isActive = false;
// 从分类中移除
var categoryItems = categories[resourceItem.category];
if (categoryItems) {
var index = categoryItems.indexOf(resourceItem);
if (index !== -1) {
categoryItems.splice(index, 1);
}
}
return true;
}
return false;
},
cleanupCategory: function(category) {
var categoryItems = categories[category];
if (!categoryItems) return 0;
var cleanedCount = 0;
categoryItems.slice().forEach(function(item) {
if (this.unregister(item.id)) {
cleanedCount++;
}
}, this);
return cleanedCount;
},
cleanupAll: function() {
var cleanedCount = 0;
resources.slice().forEach(function(item) {
if (item.isActive && this.unregister(item.id)) {
cleanedCount++;
}
}, this);
return cleanedCount;
},
getReport: function() {
var activeResources = resources.filter(function(item) {
return item.isActive;
});
var categoryReport = {};
Object.keys(categories).forEach(function(category) {
categoryReport[category] = categories[category].filter(function(item) {
return item.isActive;
}).length;
});
return {
total: activeResources.length,
byCategory: categoryReport,
oldestResource: activeResources.length > 0
? Math.min.apply(Math, activeResources.map(function(item) {
return item.created;
}))
: null
};
}
};
}
// 测试检测工具
var memoryMonitor = createMemoryMonitor();
var referenceTracker = createReferenceTracker();
var resourceManager = createResourceManager();
// 开始内存监控
memoryMonitor.start(2000);
// 注册一些资源
var testObjects = [];
for (var i = 0; i < 10; i++) {
var obj = { id: i, data: new Array(1000).fill('test data') };
testObjects.push(obj);
// 跟踪对象
referenceTracker.track(obj, 'testObject' + i);
// 注册到资源管理器
resourceManager.register(obj, 'testObjects', function(resource) {
console.log('清理测试对象:', resource.id);
});
}
// 模拟一些访问
setTimeout(function() {
testObjects.forEach(function(obj, index) {
if (index % 2 === 0) {
referenceTracker.access(obj);
}
});
console.log('\n=== 检测工具报告 ===');
console.log('引用跟踪器报告:', referenceTracker.getReport());
console.log('资源管理器报告:', resourceManager.getReport());
}, 3000);
// 清理和生成最终报告
setTimeout(function() {
console.log('\n=== 最终报告 ===');
var memoryReport = memoryMonitor.getReport();
if (memoryReport) {
console.log('内存监控报告:', {
duration: memoryReport.duration + 'ms',
snapshots: memoryReport.snapshots,
currentUsage: memoryReport.current.usedJSHeapSize,
trend: memoryReport.trend
});
}
// 清理所有资源
var cleanedCount = resourceManager.cleanupAll();
console.log('清理的资源数量:', cleanedCount);
memoryMonitor.stop();
referenceTracker.cleanup();
console.log('所有检测工具已清理');
}, 8000);
}
memoryLeakDetectionTools();
内存泄漏总结:
常见场景:
预防策略:
检测方法:
performance.memory API 监控最佳实践:
What are macro tasks and micro tasks? What is their execution order?
What are macro tasks and micro tasks? What is their execution order?
考察点:事件循环的深入理解和异步任务优先级。
答案:
宏任务(Macro Task)和微任务(Micro Task)是 JavaScript 事件循环中两种不同类型的异步任务,它们有着不同的执行优先级和时机。理解它们的区别对于掌握 JavaScript 异步编程至关重要。
宏任务 (Macro Task):
宏任务是由宿主环境(浏览器或 Node.js)提供的异步任务,包括:
// 宏任务的类型和示例
function demonstrateMacroTasks() {
console.log('=== 宏任务类型演示 ===');
// 1. setTimeout 和 setInterval
console.log('1. 开始执行同步代码');
setTimeout(function() {
console.log('2. setTimeout 宏任务执行');
}, 0);
setInterval(function() {
console.log('3. setInterval 宏任务执行');
}, 1000);
// 2. setImmediate (Node.js 环境)
if (typeof setImmediate !== 'undefined') {
setImmediate(function() {
console.log('4. setImmediate 宏任务执行');
});
}
// 3. I/O 操作(文件读写、网络请求等)
// 在浏览器中模拟
if (typeof XMLHttpRequest !== 'undefined') {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
console.log('5. XHR I/O 宏任务执行');
}
};
// 不实际发送请求,仅演示
}
// 4. UI 渲染相关(浏览器环境)
if (typeof requestAnimationFrame !== 'undefined') {
requestAnimationFrame(function() {
console.log('6. requestAnimationFrame 宏任务执行');
});
}
// 5. DOM 事件
if (typeof document !== 'undefined') {
var button = document.createElement('button');
button.addEventListener('click', function() {
console.log('7. DOM 事件宏任务执行');
});
// 模拟点击
setTimeout(function() {
if (button.click) {
button.click();
}
}, 100);
}
console.log('8. 同步代码执行完毕');
}
demonstrateMacroTasks();
微任务 (Micro Task):
微任务是 JavaScript 引擎内部的异步任务,主要包括:
// 微任务的类型和示例
function demonstrateMicroTasks() {
console.log('=== 微任务类型演示 ===');
console.log('1. 开始执行同步代码');
// 1. Promise.then/catch/finally
Promise.resolve().then(function() {
console.log('2. Promise.then 微任务执行');
// 微任务中可以创建新的微任务
Promise.resolve().then(function() {
console.log('3. 嵌套的 Promise.then 微任务执行');
});
});
// 2. queueMicrotask (现代浏览器)
if (typeof queueMicrotask !== 'undefined') {
queueMicrotask(function() {
console.log('4. queueMicrotask 微任务执行');
});
}
// 3. MutationObserver (浏览器环境)
if (typeof MutationObserver !== 'undefined') {
var observer = new MutationObserver(function(mutations) {
console.log('5. MutationObserver 微任务执行');
});
var targetNode = document.createElement('div');
observer.observe(targetNode, { childList: true });
// 触发变化
setTimeout(function() {
targetNode.appendChild(document.createElement('span'));
}, 0);
}
// 4. process.nextTick (Node.js 环境 - 特殊的微任务)
if (typeof process !== 'undefined' && process.nextTick) {
process.nextTick(function() {
console.log('6. process.nextTick 微任务执行');
});
}
console.log('7. 同步代码执行完毕');
}
demonstrateMicroTasks();
执行顺序详解:
// 事件循环执行顺序演示
function demonstrateExecutionOrder() {
console.log('=== 执行顺序演示 ===');
console.log('1. 同步代码开始');
// 宏任务
setTimeout(function() {
console.log('2. 宏任务 - setTimeout');
// 在宏任务中创建微任务
Promise.resolve().then(function() {
console.log('3. 宏任务中的微任务');
});
}, 0);
// 微任务
Promise.resolve().then(function() {
console.log('4. 微任务 - Promise.then');
// 在微任务中创建新的微任务
Promise.resolve().then(function() {
console.log('5. 微任务中的新微任务');
});
// 在微任务中创建宏任务
setTimeout(function() {
console.log('6. 微任务中创建的宏任务');
}, 0);
});
// 另一个宏任务
setTimeout(function() {
console.log('7. 第二个宏任务');
}, 0);
// 另一个微任务
Promise.resolve().then(function() {
console.log('8. 第二个微任务');
});
console.log('9. 同步代码结束');
/*
* 预期输出顺序:
* 1. 同步代码开始
* 9. 同步代码结束
* 4. 微任务 - Promise.then
* 8. 第二个微任务
* 5. 微任务中的新微任务
* 2. 宏任务 - setTimeout
* 7. 第二个宏任务
* 3. 宏任务中的微任务
* 6. 微任务中创建的宏任务
*/
}
setTimeout(function() {
demonstrateExecutionOrder();
}, 500);
事件循环机制详细分析:
// 事件循环的工作原理模拟
function simulateEventLoop() {
console.log('=== 事件循环模拟 ===');
// 模拟事件循环的内部数据结构
var CallStack = [];
var MacroTaskQueue = [];
var MicroTaskQueue = [];
var isLoopRunning = false;
// 模拟调用栈
function executeCallStack() {
console.log('执行调用栈中的同步代码');
while (CallStack.length > 0) {
var task = CallStack.pop();
console.log('执行同步任务:', task.name);
task.execute();
}
}
// 模拟微任务队列执行
function executeMicroTasks() {
console.log('开始执行微任务队列');
while (MicroTaskQueue.length > 0) {
var microTask = MicroTaskQueue.shift();
console.log('执行微任务:', microTask.name);
microTask.execute();
// 微任务执行过程中可能产生新的微任务
// 需要继续执行直到微任务队列为空
}
console.log('微任务队列执行完毕');
}
// 模拟宏任务执行
function executeMacroTask() {
if (MacroTaskQueue.length > 0) {
var macroTask = MacroTaskQueue.shift();
console.log('执行宏任务:', macroTask.name);
macroTask.execute();
return true;
}
return false;
}
// 事件循环主函数
function eventLoop() {
if (isLoopRunning) return;
isLoopRunning = true;
console.log('=== 事件循环开始 ===');
// 1. 执行调用栈中的所有同步代码
executeCallStack();
// 2. 执行所有微任务
executeMicroTasks();
// 3. 执行一个宏任务(如果有的话)
if (executeMacroTask()) {
// 4. 递归调用事件循环(模拟循环)
setTimeout(eventLoop, 0);
} else {
console.log('=== 事件循环结束 ===');
isLoopRunning = false;
}
}
// 模拟添加任务的 API
var TaskSimulator = {
addSyncTask: function(name, fn) {
CallStack.push({ name: name, execute: fn });
},
addMacroTask: function(name, fn) {
MacroTaskQueue.push({ name: name, execute: fn });
},
addMicroTask: function(name, fn) {
MicroTaskQueue.push({ name: name, execute: fn });
},
start: function() {
eventLoop();
},
getStatus: function() {
return {
callStack: CallStack.length,
macroTasks: MacroTaskQueue.length,
microTasks: MicroTaskQueue.length
};
}
};
// 添加各种任务进行测试
TaskSimulator.addSyncTask('同步任务1', function() {
console.log(' -> 执行同步任务1');
// 在同步任务中添加异步任务
TaskSimulator.addMacroTask('宏任务1', function() {
console.log(' -> 执行宏任务1');
TaskSimulator.addMicroTask('宏任务中的微任务', function() {
console.log(' -> 执行宏任务中的微任务');
});
});
TaskSimulator.addMicroTask('微任务1', function() {
console.log(' -> 执行微任务1');
TaskSimulator.addMicroTask('微任务中的微任务', function() {
console.log(' -> 执行微任务中的微任务');
});
});
});
TaskSimulator.addSyncTask('同步任务2', function() {
console.log(' -> 执行同步任务2');
TaskSimulator.addMacroTask('宏任务2', function() {
console.log(' -> 执行宏任务2');
});
TaskSimulator.addMicroTask('微任务2', function() {
console.log(' -> 执行微任务2');
});
});
console.log('初始状态:', TaskSimulator.getStatus());
TaskSimulator.start();
return TaskSimulator;
}
var simulator = simulateEventLoop();
复杂场景分析:
// 复杂的任务执行顺序分析
function complexExecutionScenarios() {
console.log('=== 复杂场景分析 ===');
// 场景1:嵌套的异步任务
function scenario1() {
console.log('=== 场景1:嵌套异步任务 ===');
console.log('A');
setTimeout(function() {
console.log('B');
Promise.resolve().then(function() {
console.log('C');
});
}, 0);
Promise.resolve().then(function() {
console.log('D');
setTimeout(function() {
console.log('E');
}, 0);
});
console.log('F');
// 输出顺序:A F D B C E
}
// 场景2:多层嵌套
function scenario2() {
console.log('=== 场景2:多层嵌套 ===');
setTimeout(function() {
console.log('timeout1');
Promise.resolve().then(function() {
console.log('promise1');
setTimeout(function() {
console.log('timeout2');
}, 0);
});
}, 0);
Promise.resolve().then(function() {
console.log('promise2');
Promise.resolve().then(function() {
console.log('promise3');
});
});
setTimeout(function() {
console.log('timeout3');
}, 0);
// 输出顺序:promise2 promise3 timeout1 timeout3 promise1 timeout2
}
// 场景3:混合异步模式
function scenario3() {
console.log('=== 场景3:混合异步模式 ===');
console.log('start');
var promise1 = Promise.resolve().then(function() {
console.log('promise1 then1');
return 'promise1 result';
}).then(function(result) {
console.log('promise1 then2:', result);
});
var promise2 = new Promise(function(resolve) {
setTimeout(function() {
console.log('promise2 resolve');
resolve('promise2 result');
}, 0);
}).then(function(result) {
console.log('promise2 then:', result);
});
setTimeout(function() {
console.log('setTimeout 1');
Promise.resolve().then(function() {
console.log('setTimeout 1 - promise');
});
}, 0);
setTimeout(function() {
console.log('setTimeout 2');
}, 0);
console.log('end');
// 输出顺序:start end promise1 then1 promise1 then2 setTimeout 1 setTimeout 2 setTimeout 1 - promise promise2 resolve promise2 then
}
// 执行所有场景
setTimeout(scenario1, 100);
setTimeout(scenario2, 1000);
setTimeout(scenario3, 2000);
}
complexExecutionScenarios();
浏览器 vs Node.js 的差异:
// 不同环境中的任务执行差异
function environmentDifferences() {
console.log('=== 环境差异分析 ===');
// 检测运行环境
var isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
var isBrowser = typeof window !== 'undefined';
console.log('当前环境:', isNode ? 'Node.js' : isBrowser ? 'Browser' : 'Unknown');
if (isNode) {
// Node.js 环境特有的任务优先级
console.log('=== Node.js 任务优先级 ===');
// process.nextTick 拥有最高优先级
process.nextTick(function() {
console.log('1. process.nextTick');
});
// Promise 微任务
Promise.resolve().then(function() {
console.log('2. Promise.then');
});
// setImmediate 宏任务
setImmediate(function() {
console.log('3. setImmediate');
});
// setTimeout 宏任务
setTimeout(function() {
console.log('4. setTimeout');
}, 0);
console.log('0. 同步代码');
// Node.js 中的优先级:
// 同步代码 > process.nextTick > Promise 微任务 > setImmediate > setTimeout
} else if (isBrowser) {
// 浏览器环境的任务类型
console.log('=== 浏览器任务优先级 ===');
// 微任务
Promise.resolve().then(function() {
console.log('1. Promise.then');
});
if (typeof queueMicrotask !== 'undefined') {
queueMicrotask(function() {
console.log('2. queueMicrotask');
});
}
// 宏任务
setTimeout(function() {
console.log('3. setTimeout');
}, 0);
if (typeof requestAnimationFrame !== 'undefined') {
requestAnimationFrame(function() {
console.log('4. requestAnimationFrame');
});
}
console.log('0. 同步代码');
// 浏览器中的优先级:
// 同步代码 > 微任务(Promise、queueMicrotask) > 宏任务(setTimeout、事件、requestAnimationFrame)
}
// 通用的执行顺序测试
function universalTest() {
console.log('=== 通用执行顺序测试 ===');
console.log('sync 1');
setTimeout(function() {
console.log('macro 1');
}, 0);
Promise.resolve().then(function() {
console.log('micro 1');
Promise.resolve().then(function() {
console.log('micro 2');
});
setTimeout(function() {
console.log('macro 2');
}, 0);
});
setTimeout(function() {
console.log('macro 3');
}, 0);
console.log('sync 2');
// 预期输出:sync 1, sync 2, micro 1, micro 2, macro 1, macro 3, macro 2
}
setTimeout(universalTest, 100);
}
environmentDifferences();
实际应用中的最佳实践:
// 在实际开发中的应用和最佳实践
function practicalApplications() {
console.log('=== 实际应用最佳实践 ===');
// 1. 批量 DOM 更新优化
function optimizeDOMUpdates() {
var pendingUpdates = [];
var isUpdateScheduled = false;
function flushUpdates() {
pendingUpdates.forEach(function(update) {
update();
});
pendingUpdates = [];
isUpdateScheduled = false;
}
return function scheduleUpdate(updateFn) {
pendingUpdates.push(updateFn);
if (!isUpdateScheduled) {
isUpdateScheduled = true;
// 使用微任务批量更新
Promise.resolve().then(flushUpdates);
}
};
}
var scheduleUpdate = optimizeDOMUpdates();
// 模拟多次 DOM 更新
for (var i = 0; i < 5; i++) {
(function(index) {
scheduleUpdate(function() {
console.log('批量DOM更新:', index);
});
})(i);
}
// 2. 异步任务优先级管理
function createTaskScheduler() {
var highPriorityTasks = [];
var lowPriorityTasks = [];
var isProcessing = false;
function processHighPriorityTasks() {
while (highPriorityTasks.length > 0) {
var task = highPriorityTasks.shift();
task();
}
}
function processLowPriorityTasks() {
if (lowPriorityTasks.length > 0) {
var task = lowPriorityTasks.shift();
task();
// 使用宏任务分批处理低优先级任务,避免阻塞
if (lowPriorityTasks.length > 0) {
setTimeout(processLowPriorityTasks, 0);
} else {
isProcessing = false;
}
} else {
isProcessing = false;
}
}
return {
scheduleHighPriority: function(task) {
highPriorityTasks.push(task);
// 使用微任务立即处理高优先级任务
Promise.resolve().then(processHighPriorityTasks);
},
scheduleLowPriority: function(task) {
lowPriorityTasks.push(task);
if (!isProcessing) {
isProcessing = true;
// 使用宏任务处理低优先级任务
setTimeout(processLowPriorityTasks, 0);
}
}
};
}
var scheduler = createTaskScheduler();
// 测试任务调度
scheduler.scheduleLowPriority(function() {
console.log('低优先级任务1');
});
scheduler.scheduleHighPriority(function() {
console.log('高优先级任务1');
});
scheduler.scheduleLowPriority(function() {
console.log('低优先级任务2');
});
scheduler.scheduleHighPriority(function() {
console.log('高优先级任务2');
});
// 3. 防抖和节流的微任务优化
function createOptimizedDebounce(fn, delay) {
var timeoutId;
var pendingArgs;
var hasPendingExecution = false;
return function() {
var args = Array.prototype.slice.call(arguments);
pendingArgs = args;
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
// 使用微任务确保在下一个事件循环周期前执行
Promise.resolve().then(function() {
fn.apply(null, pendingArgs);
hasPendingExecution = false;
});
}, delay);
};
}
var debouncedLog = createOptimizedDebounce(function(message) {
console.log('防抖执行:', message);
}, 100);
// 测试防抖
debouncedLog('消息1');
debouncedLog('消息2');
debouncedLog('消息3');
}
practicalApplications();
总结:
关键概念:
执行规则:
实际应用:
环境差异:
How to implement a complete Promise (using ES5 syntax)?
How to implement a complete Promise (using ES5 syntax)?
考察点:异步编程原理和状态机的实现。
答案:
在 ES5 中实现一个完整的 Promise 需要深入理解 Promise/A+ 规范和状态机模式。以下是一个符合规范的完整实现。
基础 Promise 实现:
// ES5 Promise 实现
function MyPromise(executor) {
if (typeof executor !== 'function') {
throw new TypeError('Promise constructor takes a function argument');
}
// Promise 状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';
// 实例属性
var self = this;
self.state = PENDING;
self.value = undefined;
self.reason = undefined;
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
// resolve 函数
function resolve(value) {
if (self.state === PENDING) {
// 处理 Promise 解决过程
resolvePromise(self, value, function(val) {
self.state = FULFILLED;
self.value = val;
// 异步执行所有 onFulfilled 回调
self.onFulfilledCallbacks.forEach(function(callback) {
callback();
});
}, function(reason) {
reject(reason);
});
}
}
// reject 函数
function reject(reason) {
if (self.state === PENDING) {
self.state = REJECTED;
self.reason = reason;
// 异步执行所有 onRejected 回调
self.onRejectedCallbacks.forEach(function(callback) {
callback();
});
}
}
// 立即执行 executor
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// Promise 解决过程(Promise Resolution Procedure)
function resolvePromise(promise, x, resolve, reject) {
// 2.3.1 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
if (promise === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
var called = false; // 防止多次调用
// 2.3.2 如果 x 为 Promise,则使 promise 接受 x 的状态
if (x instanceof MyPromise) {
if (x.state === 'pending') {
x.then(function(value) {
resolvePromise(promise, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
}
// 2.3.3 如果 x 为对象或函数
else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
// 2.3.3.1 把 x.then 赋值给 then
var then = x.then;
// 2.3.3.3 如果 then 是函数,将 x 作为函数的作用域 this 调用
if (typeof then === 'function') {
then.call(x, function(y) {
// 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
}, function(r) {
// 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝执行 promise
if (called) return;
called = true;
reject(r);
});
} else {
// 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} catch (e) {
// 2.3.3.2 如果取 x.then 的值时抛出错误 e,则以 e 为据因拒绝执行 promise
if (called) return;
called = true;
reject(e);
}
} else {
// 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
// then 方法实现
MyPromise.prototype.then = function(onFulfilled, onRejected) {
var self = this;
// 参数可选,不是函数则忽略
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value) {
return value;
};
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {
throw reason;
};
var promise2 = new MyPromise(function(resolve, reject) {
if (self.state === 'fulfilled') {
// 异步执行
setTimeout(function() {
try {
var x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (self.state === 'rejected') {
setTimeout(function() {
try {
var x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (self.state === 'pending') {
// 将回调加入队列
self.onFulfilledCallbacks.push(function() {
setTimeout(function() {
try {
var x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
self.onRejectedCallbacks.push(function() {
setTimeout(function() {
try {
var x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise2;
};
// catch 方法实现
MyPromise.prototype.catch = function(onRejected) {
return this.then(null, onRejected);
};
// finally 方法实现
MyPromise.prototype.finally = function(callback) {
var P = this.constructor;
return this.then(
function(value) {
return P.resolve(callback()).then(function() {
return value;
});
},
function(reason) {
return P.resolve(callback()).then(function() {
throw reason;
});
}
);
};
静态方法实现:
// Promise.resolve 静态方法
MyPromise.resolve = function(value) {
if (value instanceof MyPromise) {
return value;
}
return new MyPromise(function(resolve) {
resolve(value);
});
};
// Promise.reject 静态方法
MyPromise.reject = function(reason) {
return new MyPromise(function(resolve, reject) {
reject(reason);
});
};
// Promise.all 实现
MyPromise.all = function(promises) {
return new MyPromise(function(resolve, reject) {
if (!Array.isArray(promises)) {
return reject(new TypeError('Promise.all accepts an array'));
}
var results = [];
var completedCount = 0;
var length = promises.length;
if (length === 0) {
return resolve(results);
}
promises.forEach(function(promise, index) {
MyPromise.resolve(promise).then(function(value) {
results[index] = value;
completedCount++;
if (completedCount === length) {
resolve(results);
}
}, function(reason) {
reject(reason);
});
});
});
};
// Promise.race 实现
MyPromise.race = function(promises) {
return new MyPromise(function(resolve, reject) {
if (!Array.isArray(promises)) {
return reject(new TypeError('Promise.race accepts an array'));
}
promises.forEach(function(promise) {
MyPromise.resolve(promise).then(resolve, reject);
});
});
};
// Promise.allSettled 实现
MyPromise.allSettled = function(promises) {
return new MyPromise(function(resolve) {
if (!Array.isArray(promises)) {
return resolve([]);
}
var results = [];
var completedCount = 0;
var length = promises.length;
if (length === 0) {
return resolve(results);
}
promises.forEach(function(promise, index) {
MyPromise.resolve(promise).then(function(value) {
results[index] = { status: 'fulfilled', value: value };
completedCount++;
if (completedCount === length) {
resolve(results);
}
}, function(reason) {
results[index] = { status: 'rejected', reason: reason };
completedCount++;
if (completedCount === length) {
resolve(results);
}
});
});
});
};
// Promise.any 实现
MyPromise.any = function(promises) {
return new MyPromise(function(resolve, reject) {
if (!Array.isArray(promises)) {
return reject(new TypeError('Promise.any accepts an array'));
}
var errors = [];
var rejectedCount = 0;
var length = promises.length;
if (length === 0) {
return reject(new AggregateError([], 'All promises were rejected'));
}
promises.forEach(function(promise, index) {
MyPromise.resolve(promise).then(function(value) {
resolve(value);
}, function(reason) {
errors[index] = reason;
rejectedCount++;
if (rejectedCount === length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
});
});
});
};
测试和验证:
// Promise 测试用例
function testMyPromise() {
console.log('=== MyPromise 测试 ===');
// 测试1:基础功能
console.log('测试1:基础resolve/reject');
var p1 = new MyPromise(function(resolve, reject) {
setTimeout(function() {
resolve('success');
}, 100);
});
p1.then(function(value) {
console.log('✓ resolve 测试通过:', value);
});
var p2 = new MyPromise(function(resolve, reject) {
setTimeout(function() {
reject('error');
}, 200);
});
p2.catch(function(reason) {
console.log('✓ reject 测试通过:', reason);
});
// 测试2:链式调用
setTimeout(function() {
console.log('\n测试2:链式调用');
new MyPromise(function(resolve) {
resolve(1);
})
.then(function(value) {
console.log('链式调用 step 1:', value);
return value * 2;
})
.then(function(value) {
console.log('链式调用 step 2:', value);
return new MyPromise(function(resolve) {
setTimeout(function() {
resolve(value * 3);
}, 100);
});
})
.then(function(value) {
console.log('✓ 链式调用测试通过:', value);
});
}, 300);
// 测试3:错误传递
setTimeout(function() {
console.log('\n测试3:错误传递');
new MyPromise(function(resolve, reject) {
reject('initial error');
})
.then(function(value) {
console.log('不应该执行');
return value;
})
.catch(function(reason) {
console.log('✓ 错误传递测试通过:', reason);
return 'recovered';
})
.then(function(value) {
console.log('✓ 错误恢复测试通过:', value);
});
}, 500);
// 测试4:Promise.all
setTimeout(function() {
console.log('\n测试4:Promise.all');
var promises = [
MyPromise.resolve(1),
MyPromise.resolve(2),
new MyPromise(function(resolve) {
setTimeout(function() {
resolve(3);
}, 100);
})
];
MyPromise.all(promises).then(function(results) {
console.log('✓ Promise.all 测试通过:', results);
});
}, 700);
// 测试5:Promise.race
setTimeout(function() {
console.log('\n测试5:Promise.race');
var promises = [
new MyPromise(function(resolve) {
setTimeout(function() { resolve('slow'); }, 200);
}),
new MyPromise(function(resolve) {
setTimeout(function() { resolve('fast'); }, 100);
})
];
MyPromise.race(promises).then(function(result) {
console.log('✓ Promise.race 测试通过:', result);
});
}, 900);
}
// 运行测试
testMyPromise();
高级特性实现:
// 扩展功能实现
function extendedPromiseFeatures() {
console.log('=== Promise 扩展功能 ===');
// 带超时的 Promise
MyPromise.timeout = function(promise, ms, timeoutMessage) {
var timeoutPromise = new MyPromise(function(resolve, reject) {
setTimeout(function() {
reject(new Error(timeoutMessage || 'Promise timeout'));
}, ms);
});
return MyPromise.race([promise, timeoutPromise]);
};
// Promise 重试机制
MyPromise.retry = function(fn, maxRetries, delay) {
return new MyPromise(function(resolve, reject) {
var attempt = 0;
function tryExecute() {
attempt++;
MyPromise.resolve(fn()).then(function(result) {
resolve(result);
}).catch(function(error) {
if (attempt < maxRetries) {
setTimeout(tryExecute, delay || 1000);
} else {
reject(error);
}
});
}
tryExecute();
});
};
// Promise 管道
MyPromise.pipe = function() {
var functions = Array.prototype.slice.call(arguments);
return function(initialValue) {
return functions.reduce(function(promise, fn) {
return promise.then(fn);
}, MyPromise.resolve(initialValue));
};
};
// Promise 缓存装饰器
function promiseCache() {
var cache = {};
return function(key, promiseFactory) {
if (cache[key]) {
return cache[key];
}
cache[key] = promiseFactory().catch(function(error) {
// 失败时清除缓存
delete cache[key];
throw error;
});
return cache[key];
};
}
// Promise 队列
function createPromiseQueue(concurrency) {
concurrency = concurrency || 1;
var queue = [];
var running = 0;
function processNext() {
if (queue.length === 0 || running >= concurrency) {
return;
}
running++;
var task = queue.shift();
task.promiseFactory().then(function(result) {
task.resolve(result);
}).catch(function(error) {
task.reject(error);
}).finally(function() {
running--;
processNext();
});
}
return {
add: function(promiseFactory) {
return new MyPromise(function(resolve, reject) {
queue.push({
promiseFactory: promiseFactory,
resolve: resolve,
reject: reject
});
processNext();
});
},
size: function() {
return queue.length;
},
pending: function() {
return running;
}
};
}
// 测试扩展功能
console.log('测试超时功能:');
var slowPromise = new MyPromise(function(resolve) {
setTimeout(function() {
resolve('completed');
}, 2000);
});
MyPromise.timeout(slowPromise, 1000, 'Operation timed out')
.catch(function(error) {
console.log('✓ 超时测试通过:', error.message);
});
console.log('\n测试重试功能:');
var attemptCount = 0;
MyPromise.retry(function() {
attemptCount++;
if (attemptCount < 3) {
return MyPromise.reject('Attempt ' + attemptCount + ' failed');
}
return MyPromise.resolve('Success on attempt ' + attemptCount);
}, 3, 100).then(function(result) {
console.log('✓ 重试测试通过:', result);
});
console.log('\n测试管道功能:');
var pipeline = MyPromise.pipe(
function(x) { return x * 2; },
function(x) { return x + 10; },
function(x) { return 'Result: ' + x; }
);
pipeline(5).then(function(result) {
console.log('✓ 管道测试通过:', result);
});
console.log('\n测试队列功能:');
var queue = createPromiseQueue(2);
for (var i = 0; i < 5; i++) {
(function(index) {
queue.add(function() {
return new MyPromise(function(resolve) {
setTimeout(function() {
console.log('队列任务', index, '完成');
resolve('Task ' + index);
}, Math.random() * 1000);
});
});
})(i);
}
}
setTimeout(extendedPromiseFeatures, 2000);
性能优化和调试工具:
// Promise 性能监控和调试
function promiseDebugging() {
console.log('=== Promise 调试工具 ===');
// Promise 状态监控器
function createPromiseMonitor() {
var promises = new Map ? new Map() : createMapPolyfill();
var stats = {
created: 0,
fulfilled: 0,
rejected: 0,
pending: 0
};
function createMapPolyfill() {
var keys = [], values = [];
return {
set: function(k, v) {
var i = keys.indexOf(k);
if (i === -1) { keys.push(k); values.push(v); }
else values[i] = v;
},
get: function(k) {
var i = keys.indexOf(k);
return i !== -1 ? values[i] : undefined;
},
has: function(k) { return keys.indexOf(k) !== -1; },
delete: function(k) {
var i = keys.indexOf(k);
if (i !== -1) { keys.splice(i, 1); values.splice(i, 1); return true; }
return false;
}
};
}
return {
track: function(promise, name) {
var info = {
name: name || 'unnamed',
created: Date.now(),
state: 'pending'
};
promises.set(promise, info);
stats.created++;
stats.pending++;
// 监听状态变化
promise.then(function(value) {
info.state = 'fulfilled';
info.fulfilled = Date.now();
info.value = value;
stats.fulfilled++;
stats.pending--;
}, function(reason) {
info.state = 'rejected';
info.rejected = Date.now();
info.reason = reason;
stats.rejected++;
stats.pending--;
});
return promise;
},
getStats: function() {
return Object.assign({}, stats);
},
getPromiseInfo: function(promise) {
return promises.get(promise);
},
getAllPromises: function() {
var result = [];
if (promises.forEach) {
promises.forEach(function(info, promise) {
result.push(info);
});
}
return result;
}
};
}
// 创建监控器实例
var monitor = createPromiseMonitor();
// 测试监控功能
var p1 = monitor.track(MyPromise.resolve('success'), 'test-resolve');
var p2 = monitor.track(MyPromise.reject('error'), 'test-reject');
var p3 = monitor.track(new MyPromise(function(resolve) {
setTimeout(resolve, 1000);
}), 'test-pending');
setTimeout(function() {
console.log('Promise 统计:', monitor.getStats());
console.log('所有 Promise 信息:', monitor.getAllPromises());
}, 1500);
}
setTimeout(promiseDebugging, 3000);
总结:
核心特性:
关键实现点:
静态方法:
Promise.resolve/reject - 创建已决议的 PromisePromise.all/race - 组合多个 PromisePromise.allSettled/any - 高级组合方法最佳实践:
这个实现完全符合 Promise/A+ 规范,可以通过官方测试套件的验证。
What is modularization? How to implement modules in ES5?
What is modularization? How to implement modules in ES5?
- 考察点:代码组织和命名空间管理。
How to implement the Observer pattern?
How to implement the Observer pattern?
- 考察点:设计模式和事件驱动编程。
How to implement the Publish-Subscribe pattern?
How to implement the Publish-Subscribe pattern?
- 考察点:解耦和事件通信机制。
What is the Singleton pattern? How to implement it in JavaScript?
What is the Singleton pattern? How to implement it in JavaScript?
考察点:设计模式和对象创建控制。
答案:
单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点。在 JavaScript 中,单例模式常用于管理全局状态、配置对象、缓存、日志记录器等场景。
单例模式的核心特点:
ES5 实现方式:
1. 基础闭包实现:
// 最简单的单例实现
function createSingleton() {
var instance = null;
function Singleton() {
// 私有属性
var privateData = {
name: 'Singleton Instance',
created: new Date(),
counter: 0
};
// 公共方法
this.getName = function() {
return privateData.name;
};
this.getCreatedTime = function() {
return privateData.created;
};
this.increment = function() {
privateData.counter++;
return privateData.counter;
};
this.getCounter = function() {
return privateData.counter;
};
this.setName = function(name) {
privateData.name = name;
};
}
return function() {
if (!instance) {
instance = new Singleton();
}
return instance;
};
}
// 使用示例
var SingletonClass = createSingleton();
var obj1 = SingletonClass();
var obj2 = SingletonClass();
console.log(obj1 === obj2); // true
console.log(obj1.getName()); // "Singleton Instance"
obj1.increment();
console.log(obj2.getCounter()); // 1 (共享状态)
2. 构造函数单例:
// 构造函数方式实现单例
function DatabaseConnection() {
// 检查是否已存在实例
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
// 私有属性
var connectionConfig = {
host: 'localhost',
port: 3306,
database: 'myapp',
connected: false,
connectionTime: null
};
// 公共方法
this.connect = function() {
if (!connectionConfig.connected) {
connectionConfig.connected = true;
connectionConfig.connectionTime = new Date();
console.log('数据库连接已建立');
}
return this;
};
this.disconnect = function() {
if (connectionConfig.connected) {
connectionConfig.connected = false;
connectionConfig.connectionTime = null;
console.log('数据库连接已断开');
}
return this;
};
this.isConnected = function() {
return connectionConfig.connected;
};
this.getConnectionInfo = function() {
return {
host: connectionConfig.host,
port: connectionConfig.port,
database: connectionConfig.database,
connected: connectionConfig.connected,
connectionTime: connectionConfig.connectionTime
};
};
this.query = function(sql) {
if (!connectionConfig.connected) {
throw new Error('数据库未连接');
}
console.log('执行查询: ' + sql);
// 模拟查询结果
return {
rows: [],
affectedRows: 0,
timestamp: new Date()
};
};
// 存储实例引用
DatabaseConnection.instance = this;
// 冻结实例,防止修改
if (Object.freeze) {
Object.freeze(this);
}
}
// 使用示例
var db1 = new DatabaseConnection();
var db2 = new DatabaseConnection();
console.log(db1 === db2); // true
db1.connect();
console.log(db2.isConnected()); // true
3. 模块模式单例:
// 使用模块模式实现单例
var Logger = (function() {
var instance;
var logs = [];
var logLevel = 'INFO';
var maxLogs = 1000;
function createLogger() {
return {
// 日志级别
LEVELS: {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3
},
// 设置日志级别
setLevel: function(level) {
logLevel = level;
return this;
},
// 获取日志级别
getLevel: function() {
return logLevel;
},
// 记录日志
log: function(level, message) {
var timestamp = new Date().toISOString();
var logEntry = {
level: level,
message: message,
timestamp: timestamp
};
logs.push(logEntry);
// 限制日志数量
if (logs.length > maxLogs) {
logs.shift();
}
// 输出到控制台
var consoleMessage = '[' + timestamp + '] ' + level + ': ' + message;
switch (level) {
case 'ERROR':
console.error(consoleMessage);
break;
case 'WARN':
console.warn(consoleMessage);
break;
case 'DEBUG':
console.debug ? console.debug(consoleMessage) : console.log(consoleMessage);
break;
default:
console.log(consoleMessage);
}
return this;
},
// 便捷方法
error: function(message) {
return this.log('ERROR', message);
},
warn: function(message) {
return this.log('WARN', message);
},
info: function(message) {
return this.log('INFO', message);
},
debug: function(message) {
return this.log('DEBUG', message);
},
// 获取所有日志
getLogs: function(level) {
if (level) {
return logs.filter(function(log) {
return log.level === level;
});
}
return logs.slice(); // 返回副本
},
// 清空日志
clear: function() {
logs = [];
return this;
},
// 获取日志统计
getStats: function() {
var stats = {};
logs.forEach(function(log) {
stats[log.level] = (stats[log.level] || 0) + 1;
});
return {
total: logs.length,
byLevel: stats,
oldestLog: logs.length > 0 ? logs[0].timestamp : null,
newestLog: logs.length > 0 ? logs[logs.length - 1].timestamp : null
};
},
// 导出日志
export: function(format) {
format = format || 'json';
switch (format.toLowerCase()) {
case 'json':
return JSON.stringify(logs, null, 2);
case 'csv':
var csvLines = ['timestamp,level,message'];
logs.forEach(function(log) {
csvLines.push([
log.timestamp,
log.level,
'"' + log.message.replace(/"/g, '""') + '"'
].join(','));
});
return csvLines.join('\n');
case 'text':
return logs.map(function(log) {
return '[' + log.timestamp + '] ' + log.level + ': ' + log.message;
}).join('\n');
default:
throw new Error('不支持的导出格式: ' + format);
}
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = createLogger();
}
return instance;
},
// 直接提供静态方法
log: function(level, message) {
return this.getInstance().log(level, message);
},
error: function(message) {
return this.getInstance().error(message);
},
warn: function(message) {
return this.getInstance().warn(message);
},
info: function(message) {
return this.getInstance().info(message);
},
debug: function(message) {
return this.getInstance().debug(message);
}
};
})();
// 使用示例
Logger.info('应用启动');
Logger.warn('这是一个警告');
Logger.error('发生了错误');
var logger1 = Logger.getInstance();
var logger2 = Logger.getInstance();
console.log(logger1 === logger2); // true
console.log(logger1.getStats());
4. 惰性单例实现:
// 惰性单例 - 只在需要时创建
function createLazySingleton(constructor) {
var instance;
return function() {
if (!instance) {
// 使用 apply 传递参数给构造函数
instance = constructor.apply(this, arguments);
}
return instance;
};
}
// 配置管理器
function ConfigManager(initialConfig) {
var config = initialConfig || {};
var listeners = [];
return {
get: function(key) {
if (arguments.length === 0) {
// 返回所有配置的副本
return JSON.parse(JSON.stringify(config));
}
if (key.indexOf('.') !== -1) {
// 支持嵌套属性访问 'app.database.host'
var keys = key.split('.');
var value = config;
for (var i = 0; i < keys.length; i++) {
if (value && typeof value === 'object' && keys[i] in value) {
value = value[keys[i]];
} else {
return undefined;
}
}
return value;
}
return config[key];
},
set: function(key, value) {
var oldValue = this.get(key);
if (key.indexOf('.') !== -1) {
// 支持嵌套属性设置
var keys = key.split('.');
var target = config;
for (var i = 0; i < keys.length - 1; i++) {
if (!target[keys[i]] || typeof target[keys[i]] !== 'object') {
target[keys[i]] = {};
}
target = target[keys[i]];
}
target[keys[keys.length - 1]] = value;
} else {
config[key] = value;
}
// 通知监听器
this._notifyChange(key, value, oldValue);
return this;
},
has: function(key) {
return this.get(key) !== undefined;
},
remove: function(key) {
var oldValue = this.get(key);
if (key.indexOf('.') !== -1) {
var keys = key.split('.');
var target = config;
for (var i = 0; i < keys.length - 1; i++) {
if (!target[keys[i]]) {
return this;
}
target = target[keys[i]];
}
delete target[keys[keys.length - 1]];
} else {
delete config[key];
}
this._notifyChange(key, undefined, oldValue);
return this;
},
// 配置变化监听
onChange: function(callback) {
if (typeof callback === 'function') {
listeners.push(callback);
}
return this;
},
_notifyChange: function(key, newValue, oldValue) {
listeners.forEach(function(callback) {
try {
callback(key, newValue, oldValue);
} catch (error) {
console.error('配置变化监听器错误:', error);
}
});
},
// 批量更新
update: function(updates) {
for (var key in updates) {
if (updates.hasOwnProperty(key)) {
this.set(key, updates[key]);
}
}
return this;
},
// 重置配置
reset: function(newConfig) {
config = newConfig || {};
this._notifyChange('*', config, null);
return this;
}
};
}
// 创建惰性单例
var getConfigManager = createLazySingleton(ConfigManager);
// 使用示例
var config1 = getConfigManager({
app: {
name: 'MyApp',
version: '1.0.0'
},
database: {
host: 'localhost',
port: 3306
}
});
var config2 = getConfigManager();
console.log(config1 === config2); // true
config1.set('app.debug', true);
console.log(config2.get('app.debug')); // true
5. 线程安全单例(防止并发问题):
// 在 JavaScript 中模拟线程安全的单例
function createThreadSafeSingleton() {
var instance = null;
var isCreating = false;
var waitingCallbacks = [];
function Singleton() {
var data = {
id: Math.random().toString(36).substr(2, 9),
created: new Date(),
operations: 0
};
this.getId = function() {
return data.id;
};
this.getCreatedTime = function() {
return data.created;
};
this.performOperation = function(operation) {
data.operations++;
console.log('执行操作 #' + data.operations + ': ' + operation);
return data.operations;
};
this.getStats = function() {
return {
id: data.id,
created: data.created,
operations: data.operations
};
};
}
return function(callback) {
// 如果实例已存在,直接返回
if (instance) {
if (callback) {
setTimeout(function() { callback(instance); }, 0);
}
return instance;
}
// 如果正在创建,加入等待队列
if (isCreating) {
if (callback) {
waitingCallbacks.push(callback);
}
return null;
}
// 开始创建实例
isCreating = true;
// 模拟异步创建过程
setTimeout(function() {
instance = new Singleton();
isCreating = false;
// 通知所有等待的回调
waitingCallbacks.forEach(function(cb) {
try {
cb(instance);
} catch (error) {
console.error('回调执行错误:', error);
}
});
waitingCallbacks = [];
// 如果有回调,也执行
if (callback) {
callback(instance);
}
}, Math.random() * 100); // 随机延迟模拟异步
return null; // 异步创建时返回 null
};
}
// 使用示例
var getThreadSafeSingleton = createThreadSafeSingleton();
// 并发请求测试
for (var i = 0; i < 5; i++) {
(function(index) {
getThreadSafeSingleton(function(instance) {
console.log('回调 ' + index + ' 收到实例:', instance.getId());
});
})(i);
}
6. 单例注册表:
// 单例注册表 - 管理多个命名单例
var SingletonRegistry = (function() {
var instances = {};
return {
register: function(name, constructor) {
if (instances[name]) {
throw new Error('单例 "' + name + '" 已存在');
}
instances[name] = {
constructor: constructor,
instance: null,
created: false
};
return this;
},
getInstance: function(name) {
var entry = instances[name];
if (!entry) {
throw new Error('单例 "' + name + '" 未注册');
}
if (!entry.created) {
entry.instance = new entry.constructor();
entry.created = true;
}
return entry.instance;
},
hasInstance: function(name) {
return instances[name] && instances[name].created;
},
unregister: function(name) {
if (instances[name]) {
delete instances[name];
return true;
}
return false;
},
listRegistered: function() {
return Object.keys(instances);
},
clear: function() {
instances = {};
return this;
}
};
})();
// 注册不同的单例
SingletonRegistry.register('logger', function() {
this.logs = [];
this.log = function(message) {
this.logs.push({
message: message,
timestamp: new Date()
});
};
});
SingletonRegistry.register('cache', function() {
var data = {};
this.set = function(key, value) {
data[key] = value;
};
this.get = function(key) {
return data[key];
};
this.clear = function() {
data = {};
};
});
// 使用注册的单例
var logger = SingletonRegistry.getInstance('logger');
var cache = SingletonRegistry.getInstance('cache');
logger.log('应用启动');
cache.set('user', { name: 'John' });
// 获取同样的实例
var logger2 = SingletonRegistry.getInstance('logger');
console.log(logger === logger2); // true
实际应用场景:
// 实际应用场景示例
function practicalSingletonExamples() {
// 1. 应用配置管理
var AppConfig = (function() {
var instance;
var config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retryCount: 3,
debug: false
};
function ConfigManager() {
return {
get: function(key) {
return config[key];
},
set: function(key, value) {
config[key] = value;
},
getAll: function() {
return Object.assign({}, config);
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = new ConfigManager();
}
return instance;
}
};
})();
// 2. 事件总线
var EventBus = (function() {
var instance;
function EventManager() {
var events = {};
return {
on: function(event, callback) {
if (!events[event]) {
events[event] = [];
}
events[event].push(callback);
},
off: function(event, callback) {
if (events[event]) {
var index = events[event].indexOf(callback);
if (index !== -1) {
events[event].splice(index, 1);
}
}
},
emit: function(event, data) {
if (events[event]) {
events[event].forEach(function(callback) {
callback(data);
});
}
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = new EventManager();
}
return instance;
}
};
})();
// 3. HTTP 客户端
var HttpClient = (function() {
var instance;
function Client() {
var baseConfig = {
baseURL: '',
timeout: 5000,
headers: {}
};
return {
setBaseURL: function(url) {
baseConfig.baseURL = url;
return this;
},
setHeader: function(name, value) {
baseConfig.headers[name] = value;
return this;
},
request: function(config) {
// 合并配置
var requestConfig = Object.assign({}, baseConfig, config);
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
var url = requestConfig.baseURL + (config.url || '');
xhr.open(config.method || 'GET', url);
// 设置请求头
for (var header in requestConfig.headers) {
xhr.setRequestHeader(header, requestConfig.headers[header]);
}
xhr.timeout = requestConfig.timeout;
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
data: JSON.parse(xhr.responseText),
status: xhr.status,
statusText: xhr.statusText
});
} else {
reject(new Error('HTTP ' + xhr.status + ': ' + xhr.statusText));
}
};
xhr.onerror = function() {
reject(new Error('Network Error'));
};
xhr.ontimeout = function() {
reject(new Error('Request Timeout'));
};
xhr.send(config.data ? JSON.stringify(config.data) : null);
});
},
get: function(url, config) {
return this.request(Object.assign({ method: 'GET', url: url }, config));
},
post: function(url, data, config) {
return this.request(Object.assign({ method: 'POST', url: url, data: data }, config));
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = new Client();
}
return instance;
}
};
})();
// 使用示例
var config = AppConfig.getInstance();
var eventBus = EventBus.getInstance();
var http = HttpClient.getInstance();
config.set('debug', true);
eventBus.on('user-login', function(user) {
console.log('用户登录:', user.name);
});
http.setBaseURL('https://api.example.com')
.setHeader('Authorization', 'Bearer token');
console.log('实际应用单例演示完成');
}
practicalSingletonExamples();
单例模式的优缺点:
优点:
缺点:
最佳实践:
单例模式在 JavaScript 中是一个非常实用的设计模式,特别适合管理应用级别的资源和配置。
How to implement a simple MVC framework?
How to implement a simple MVC framework?
考察点:架构设计和代码组织能力。
答案:
MVC(Model-View-Controller)是一种经典的软件架构模式,它将应用程序分为三个相互关联的部分:模型(数据)、视图(界面)、控制器(逻辑)。在 ES5 中实现一个简单的 MVC 框架可以帮助我们更好地理解现代前端框架的设计思想。
MVC 架构核心概念:
基础框架实现:
// 简单 MVC 框架实现
var SimpleMVC = (function() {
// 事件系统 - MVC 各部分通信的基础
function EventEmitter() {
this.events = {};
}
EventEmitter.prototype.on = function(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return this;
};
EventEmitter.prototype.off = function(event, callback) {
if (this.events[event]) {
if (callback) {
var index = this.events[event].indexOf(callback);
if (index !== -1) {
this.events[event].splice(index, 1);
}
} else {
this.events[event] = [];
}
}
return this;
};
EventEmitter.prototype.emit = function(event, data) {
if (this.events[event]) {
this.events[event].forEach(function(callback) {
try {
callback(data);
} catch (error) {
console.error('事件处理错误:', error);
}
});
}
return this;
};
// 工具函数
function extend(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
function isFunction(obj) {
return typeof obj === 'function';
}
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
// Model 基类
function Model(attributes) {
EventEmitter.call(this);
this.attributes = attributes || {};
this.changed = {};
this.previous = {};
this.validationRules = {};
this.initialize.apply(this, arguments);
}
// Model 继承 EventEmitter
Model.prototype = Object.create(EventEmitter.prototype);
Model.prototype.constructor = Model;
Model.prototype.initialize = function() {
// 子类可以重写此方法
};
Model.prototype.get = function(attr) {
return this.attributes[attr];
};
Model.prototype.set = function(key, val, options) {
var attrs;
// 处理参数
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
attrs = {};
attrs[key] = val;
}
options = options || {};
// 验证数据
if (!options.silent && !this.validate(attrs)) {
return false;
}
var changes = [];
var changing = this._changing;
this._changing = true;
if (!changing) {
this.changed = {};
this.previous = extend({}, this.attributes);
}
for (var attr in attrs) {
val = attrs[attr];
if (this.attributes[attr] !== val) {
this.changed[attr] = val;
changes.push(attr);
}
this.attributes[attr] = val;
}
// 触发变化事件
if (!options.silent) {
if (changes.length) {
this.emit('change', this);
}
for (var i = 0; i < changes.length; i++) {
this.emit('change:' + changes[i], this, this.attributes[changes[i]]);
}
}
this._changing = false;
return this;
};
Model.prototype.has = function(attr) {
return this.attributes[attr] !== undefined;
};
Model.prototype.unset = function(attr, options) {
if (this.has(attr)) {
delete this.attributes[attr];
if (!options || !options.silent) {
this.emit('change:' + attr, this, undefined);
this.emit('change', this);
}
}
return this;
};
Model.prototype.clear = function(options) {
var attrs = {};
for (var key in this.attributes) {
attrs[key] = undefined;
}
return this.set(attrs, extend({}, options, { unset: true }));
};
Model.prototype.toJSON = function() {
return extend({}, this.attributes);
};
Model.prototype.validate = function(attrs) {
// 基础验证框架
for (var attr in attrs) {
var rules = this.validationRules[attr];
if (rules) {
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (!rule.validator(attrs[attr])) {
this.emit('invalid', this, rule.message, attr);
return false;
}
}
}
}
return true;
};
Model.prototype.addValidation = function(attr, validator, message) {
if (!this.validationRules[attr]) {
this.validationRules[attr] = [];
}
this.validationRules[attr].push({
validator: validator,
message: message
});
return this;
};
// View 基类
function View(options) {
EventEmitter.call(this);
options = options || {};
this.model = options.model;
this.el = options.el;
this.template = options.template;
this.events = options.events || {};
this.initialize.apply(this, arguments);
this.delegateEvents();
}
// View 继承 EventEmitter
View.prototype = Object.create(EventEmitter.prototype);
View.prototype.constructor = View;
View.prototype.initialize = function() {
// 子类可以重写此方法
};
View.prototype.render = function() {
if (this.template) {
var html = this.template(this.model ? this.model.toJSON() : {});
if (this.el) {
this.el.innerHTML = html;
}
}
return this;
};
View.prototype.delegateEvents = function() {
var self = this;
if (!this.el || !this.events) return;
// 清除之前的事件
this.undelegateEvents();
for (var key in this.events) {
var method = this.events[key];
if (!isFunction(method)) {
method = this[method];
}
if (method) {
var parts = key.split(' ');
var eventName = parts[0];
var selector = parts[1];
this._addEventListener(eventName, selector, method.bind(this));
}
}
};
View.prototype._addEventListener = function(eventName, selector, handler) {
var self = this;
if (selector) {
// 事件委托
this.el.addEventListener(eventName, function(e) {
var target = e.target;
while (target && target !== self.el) {
if (target.matches && target.matches(selector)) {
handler.call(self, e, target);
break;
}
target = target.parentNode;
}
});
} else {
// 直接绑定
this.el.addEventListener(eventName, handler);
}
};
View.prototype.undelegateEvents = function() {
if (this.el) {
// 简化版本:创建新元素替换旧元素来清除所有事件
var newEl = this.el.cloneNode(true);
this.el.parentNode.replaceChild(newEl, this.el);
this.el = newEl;
}
return this;
};
View.prototype.remove = function() {
if (this.el && this.el.parentNode) {
this.el.parentNode.removeChild(this.el);
}
this.undelegateEvents();
return this;
};
// Controller 基类
function Controller(options) {
EventEmitter.call(this);
options = options || {};
this.models = {};
this.views = {};
this.routes = {};
this.initialize.apply(this, arguments);
}
// Controller 继承 EventEmitter
Controller.prototype = Object.create(EventEmitter.prototype);
Controller.prototype.constructor = Controller;
Controller.prototype.initialize = function() {
// 子类可以重写此方法
};
Controller.prototype.addModel = function(name, model) {
this.models[name] = model;
return this;
};
Controller.prototype.addView = function(name, view) {
this.views[name] = view;
return this;
};
Controller.prototype.getModel = function(name) {
return this.models[name];
};
Controller.prototype.getView = function(name) {
return this.views[name];
};
Controller.prototype.route = function(path, handler) {
this.routes[path] = handler;
return this;
};
Controller.prototype.navigate = function(path) {
var handler = this.routes[path];
if (handler && isFunction(handler)) {
handler.call(this, path);
}
return this;
};
// 简单的模板引擎
function SimpleTemplate(templateString) {
this.template = templateString;
}
SimpleTemplate.prototype.render = function(data) {
var template = this.template;
// 替换变量 {{variable}}
template = template.replace(/\{\{(\w+)\}\}/g, function(match, key) {
return data[key] !== undefined ? data[key] : '';
});
// 处理循环 {{#each array}}...{{/each}}
template = template.replace(/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g, function(match, arrayName, content) {
var array = data[arrayName];
if (!Array.isArray(array)) return '';
return array.map(function(item, index) {
return content.replace(/\{\{(\w+)\}\}/g, function(match, key) {
return item[key] !== undefined ? item[key] : '';
}).replace(/\{\{@index\}\}/g, index);
}).join('');
});
// 处理条件 {{#if condition}}...{{/if}}
template = template.replace(/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, function(match, condition, content) {
return data[condition] ? content : '';
});
return template;
};
// 框架入口
function createApp() {
return {
Model: Model,
View: View,
Controller: Controller,
Template: SimpleTemplate,
// 工具方法
extend: extend,
// 创建模型
createModel: function(proto) {
function CustomModel() {
Model.apply(this, arguments);
}
CustomModel.prototype = Object.create(Model.prototype);
CustomModel.prototype.constructor = CustomModel;
if (proto) {
extend(CustomModel.prototype, proto);
}
return CustomModel;
},
// 创建视图
createView: function(proto) {
function CustomView() {
View.apply(this, arguments);
}
CustomView.prototype = Object.create(View.prototype);
CustomView.prototype.constructor = CustomView;
if (proto) {
extend(CustomView.prototype, proto);
}
return CustomView;
},
// 创建控制器
createController: function(proto) {
function CustomController() {
Controller.apply(this, arguments);
}
CustomController.prototype = Object.create(Controller.prototype);
CustomController.prototype.constructor = CustomController;
if (proto) {
extend(CustomController.prototype, proto);
}
return CustomController;
}
};
}
return {
createApp: createApp,
Model: Model,
View: View,
Controller: Controller,
Template: SimpleTemplate
};
})();
// 使用示例
console.log('=== MVC 框架使用示例 ===');
// 创建应用实例
var app = SimpleMVC.createApp();
// 创建用户模型
var UserModel = app.createModel({
defaults: {
name: '',
email: '',
age: 0
},
initialize: function() {
// 添加验证规则
this.addValidation('email', function(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}, '邮箱格式不正确');
this.addValidation('age', function(age) {
return age > 0 && age < 150;
}, '年龄必须在1-150之间');
},
getDisplayName: function() {
return this.get('name') + ' (' + this.get('age') + '岁)';
}
});
// 创建用户视图
var UserView = app.createView({
events: {
'click .save-btn': 'onSave',
'click .delete-btn': 'onDelete',
'change input': 'onInputChange'
},
initialize: function() {
// 监听模型变化
if (this.model) {
this.model.on('change', this.render.bind(this));
this.model.on('invalid', this.showError.bind(this));
}
this.template = new app.Template([
'<div class="user-form">',
' <h3>用户信息</h3>',
' <div class="field">',
' <label>姓名:</label>',
' <input type="text" name="name" value="{{name}}" />',
' </div>',
' <div class="field">',
' <label>邮箱:</label>',
' <input type="email" name="email" value="{{email}}" />',
' </div>',
' <div class="field">',
' <label>年龄:</label>',
' <input type="number" name="age" value="{{age}}" />',
' </div>',
' <div class="actions">',
' <button class="save-btn">保存</button>',
' <button class="delete-btn">删除</button>',
' </div>',
' <div class="display">',
' <strong>显示名称:</strong> <span class="display-name">{{displayName}}</span>',
' </div>',
' <div class="error-message" style="color: red;"></div>',
'</div>'
].join('\n'));
},
render: function() {
var data = this.model.toJSON();
data.displayName = this.model.getDisplayName();
var html = this.template.render(data);
this.el.innerHTML = html;
return this;
},
onInputChange: function(e) {
var name = e.target.name;
var value = e.target.value;
if (e.target.type === 'number') {
value = parseInt(value, 10) || 0;
}
this.model.set(name, value);
},
onSave: function() {
console.log('保存用户:', this.model.toJSON());
this.clearError();
},
onDelete: function() {
console.log('删除用户:', this.model.get('name'));
this.model.clear();
},
showError: function(model, message) {
var errorEl = this.el.querySelector('.error-message');
if (errorEl) {
errorEl.textContent = message;
}
},
clearError: function() {
var errorEl = this.el.querySelector('.error-message');
if (errorEl) {
errorEl.textContent = '';
}
}
});
// 创建用户列表视图
var UserListView = app.createView({
events: {
'click .add-user-btn': 'onAddUser',
'click .user-item': 'onSelectUser'
},
initialize: function() {
this.users = [];
this.selectedUser = null;
this.template = new app.Template([
'<div class="user-list">',
' <h3>用户列表</h3>',
' <button class="add-user-btn">添加用户</button>',
' <div class="users">',
' {{#each users}}',
' <div class="user-item" data-index="{{@index}}">',
' <strong>{{name}}</strong> - {{email}} ({{age}}岁)',
' </div>',
' {{/each}}',
' </div>',
'</div>'
].join('\n'));
},
render: function() {
var data = {
users: this.users.map(function(user) {
return user.toJSON();
})
};
var html = this.template.render(data);
this.el.innerHTML = html;
return this;
},
addUser: function(user) {
this.users.push(user);
// 监听用户变化
user.on('change', this.render.bind(this));
this.render();
return this;
},
onAddUser: function() {
var newUser = new UserModel({
name: 'New User',
email: '[email protected]',
age: 25
});
this.addUser(newUser);
this.emit('user:selected', newUser);
},
onSelectUser: function(e) {
var index = parseInt(e.target.getAttribute('data-index'), 10);
var user = this.users[index];
if (user) {
this.selectedUser = user;
this.emit('user:selected', user);
}
}
});
// 创建应用控制器
var AppController = app.createController({
initialize: function() {
this.currentUser = null;
this.setupViews();
this.setupRoutes();
},
setupViews: function() {
// 创建容器元素
var container = document.createElement('div');
container.style.display = 'flex';
container.style.gap = '20px';
document.body.appendChild(container);
// 用户列表容器
var listContainer = document.createElement('div');
listContainer.style.flex = '1';
container.appendChild(listContainer);
// 用户详情容器
var detailContainer = document.createElement('div');
detailContainer.style.flex = '1';
container.appendChild(detailContainer);
// 创建用户列表视图
var userListView = new UserListView({
el: listContainer
});
// 创建用户详情视图
var userDetailView = new UserView({
el: detailContainer,
model: new UserModel() // 初始空模型
});
// 连接视图
userListView.on('user:selected', function(user) {
userDetailView.model = user;
userDetailView.render();
});
// 添加到控制器
this.addView('userList', userListView);
this.addView('userDetail', userDetailView);
// 初始渲染
userListView.render();
userDetailView.render();
// 添加一些示例用户
this.addSampleUsers();
},
setupRoutes: function() {
this.route('/', this.showHome);
this.route('/users', this.showUsers);
this.route('/user/:id', this.showUser);
},
addSampleUsers: function() {
var userList = this.getView('userList');
var sampleUsers = [
{ name: '张三', email: '[email protected]', age: 28 },
{ name: '李四', email: '[email protected]', age: 32 },
{ name: '王五', email: '[email protected]', age: 25 }
];
sampleUsers.forEach(function(userData) {
var user = new UserModel(userData);
userList.addUser(user);
});
},
showHome: function() {
console.log('显示首页');
},
showUsers: function() {
console.log('显示用户列表');
},
showUser: function(path) {
var id = path.split(':')[1];
console.log('显示用户详情:', id);
}
});
// 启动应用
var appController = new AppController();
console.log('MVC 框架演示应用已启动');
高级功能扩展:
// MVC 框架的高级功能
function advancedMVCFeatures() {
// 1. 数据绑定和观察者
function DataBinding() {
this.bindings = {};
}
DataBinding.prototype.bind = function(element, model, attribute) {
var binding = {
element: element,
model: model,
attribute: attribute
};
// 双向绑定
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
element.addEventListener('input', function() {
model.set(attribute, element.value);
});
}
// 模型到视图
model.on('change:' + attribute, function(model, value) {
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
element.value = value;
} else {
element.textContent = value;
}
});
// 初始化值
var initialValue = model.get(attribute);
if (initialValue !== undefined) {
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
element.value = initialValue;
} else {
element.textContent = initialValue;
}
}
return binding;
};
// 2. 路由系统
function Router() {
this.routes = {};
this.currentRoute = null;
this.history = [];
// 监听历史变化
window.addEventListener('popstate', this.handlePopState.bind(this));
}
Router.prototype.route = function(pattern, handler) {
this.routes[pattern] = {
pattern: new RegExp('^' + pattern.replace(/:\w+/g, '([^/]+)') + '$'),
handler: handler,
original: pattern
};
return this;
};
Router.prototype.navigate = function(path, trigger) {
if (trigger !== false) {
window.history.pushState({}, '', path);
}
this.handleRoute(path);
return this;
};
Router.prototype.handleRoute = function(path) {
for (var pattern in this.routes) {
var route = this.routes[pattern];
var matches = path.match(route.pattern);
if (matches) {
this.currentRoute = path;
this.history.push(path);
// 提取参数
var params = matches.slice(1);
route.handler.apply(this, params);
return true;
}
}
console.warn('No route found for:', path);
return false;
};
Router.prototype.handlePopState = function(event) {
this.handleRoute(window.location.pathname);
};
Router.prototype.back = function() {
window.history.back();
return this;
};
// 3. 状态管理
function StateManager() {
this.state = {};
this.listeners = {};
this.middleware = [];
}
StateManager.prototype.getState = function() {
return JSON.parse(JSON.stringify(this.state));
};
StateManager.prototype.setState = function(updates) {
var oldState = this.getState();
// 应用中间件
var action = { type: 'SET_STATE', payload: updates };
for (var i = 0; i < this.middleware.length; i++) {
action = this.middleware[i](this.state, action) || action;
}
// 更新状态
this.state = Object.assign({}, this.state, action.payload);
// 通知监听器
for (var key in this.listeners) {
this.listeners[key](this.state, oldState);
}
return this;
};
StateManager.prototype.subscribe = function(callback) {
var id = Date.now() + Math.random();
this.listeners[id] = callback;
return function() {
delete this.listeners[id];
}.bind(this);
};
StateManager.prototype.addMiddleware = function(middleware) {
this.middleware.push(middleware);
return this;
};
// 4. 组件系统
function Component(options) {
this.options = options || {};
this.props = this.options.props || {};
this.state = this.options.initialState || {};
this.element = null;
this.children = [];
this.parent = null;
this.initialize();
}
Component.prototype.initialize = function() {
// 子类可以重写
};
Component.prototype.setState = function(updates) {
var oldState = this.state;
this.state = Object.assign({}, this.state, updates);
// 触发重新渲染
this.update(oldState);
return this;
};
Component.prototype.render = function() {
// 子类必须实现
throw new Error('Component must implement render method');
};
Component.prototype.mount = function(container) {
this.element = this.render();
if (container && this.element) {
container.appendChild(this.element);
}
this.componentDidMount();
return this;
};
Component.prototype.update = function(prevState) {
if (this.element) {
var newElement = this.render();
if (this.element.parentNode) {
this.element.parentNode.replaceChild(newElement, this.element);
}
this.element = newElement;
}
this.componentDidUpdate(prevState);
return this;
};
Component.prototype.unmount = function() {
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
}
this.componentWillUnmount();
return this;
};
Component.prototype.componentDidMount = function() {
// 生命周期钩子
};
Component.prototype.componentDidUpdate = function(prevState) {
// 生命周期钩子
};
Component.prototype.componentWillUnmount = function() {
// 生命周期钩子
};
// 使用示例
console.log('=== 高级功能演示 ===');
// 创建路由器
var router = new Router();
router.route('/home', function() {
console.log('Home page');
});
router.route('/user/:id', function(id) {
console.log('User page for id:', id);
});
// 创建状态管理器
var stateManager = new StateManager();
// 添加日志中间件
stateManager.addMiddleware(function(state, action) {
console.log('State change:', action);
return action;
});
stateManager.setState({ user: { name: 'John', age: 30 } });
// 创建组件
function TodoComponent(options) {
Component.call(this, options);
}
TodoComponent.prototype = Object.create(Component.prototype);
TodoComponent.prototype.constructor = TodoComponent;
TodoComponent.prototype.initialize = function() {
this.state = {
todos: [],
newTodo: ''
};
};
TodoComponent.prototype.render = function() {
var div = document.createElement('div');
div.innerHTML = [
'<h3>Todo List</h3>',
'<input type="text" placeholder="Add new todo..." />',
'<button onclick="this.addTodo()">Add</button>',
'<ul>',
this.state.todos.map(function(todo, index) {
return '<li>' + todo + ' <button onclick="this.removeTodo(' + index + ')">Remove</button></li>';
}).join(''),
'</ul>'
].join('');
return div;
};
TodoComponent.prototype.addTodo = function() {
var input = this.element.querySelector('input');
if (input.value.trim()) {
this.setState({
todos: this.state.todos.concat([input.value.trim()])
});
input.value = '';
}
};
TodoComponent.prototype.removeTodo = function(index) {
var newTodos = this.state.todos.slice();
newTodos.splice(index, 1);
this.setState({ todos: newTodos });
};
console.log('高级功能演示完成');
}
setTimeout(advancedMVCFeatures, 1000);
MVC 框架总结:
核心特性:
设计原则:
适用场景:
这个简单的 MVC 框架展示了现代前端框架的核心概念,为理解 Angular、Backbone.js 等框架奠定了基础。
What are the performance optimization methods in JavaScript?
What are the performance optimization methods in JavaScript?
考察点:实际项目经验和性能调优。
答案:
JavaScript 性能优化是前端开发中的重要主题,涉及代码执行效率、内存使用、网络请求等多个方面。在 ES5 环境下,我们需要掌握各种优化技术和工具来提升应用性能。
性能优化分类:
代码层面性能优化:
// JavaScript 性能优化技术实现
var PerformanceOptimizer = (function() {
// 1. 循环优化技术
function LoopOptimization() {
console.log('=== 循环优化示例 ===');
var arr = [];
for (var i = 0; i < 10000; i++) {
arr.push(i);
}
// 低效的循环方式
function slowLoop(array) {
var start = Date.now();
var result = [];
for (var i = 0; i < array.length; i++) { // 每次都计算 length
if (array[i] % 2 === 0) {
result.push(array[i]);
}
}
console.log('慢循环耗时:', Date.now() - start, 'ms');
return result;
}
// 优化的循环方式
function fastLoop(array) {
var start = Date.now();
var result = [];
var length = array.length; // 缓存长度
for (var i = 0; i < length; i++) {
var item = array[i]; // 缓存数组项
if (item % 2 === 0) {
result.push(item);
}
}
console.log('快循环耗时:', Date.now() - start, 'ms');
return result;
}
// 反向循环(某些情况下更快)
function reverseLoop(array) {
var start = Date.now();
var result = [];
var i = array.length;
while (i--) {
if (array[i] % 2 === 0) {
result.unshift(array[i]); // 保持顺序
}
}
console.log('反向循环耗时:', Date.now() - start, 'ms');
return result;
}
// 使用原生方法(通常更快)
function nativeLoop(array) {
var start = Date.now();
var result = array.filter(function(item) {
return item % 2 === 0;
});
console.log('原生方法耗时:', Date.now() - start, 'ms');
return result;
}
// 性能对比
slowLoop(arr);
fastLoop(arr);
reverseLoop(arr);
nativeLoop(arr);
}
// 2. 字符串操作优化
function StringOptimization() {
console.log('=== 字符串优化示例 ===');
// 低效的字符串拼接
function slowStringConcat() {
var start = Date.now();
var result = '';
for (var i = 0; i < 10000; i++) {
result += 'item' + i + ' '; // 每次都创建新字符串
}
console.log('慢字符串拼接耗时:', Date.now() - start, 'ms');
return result;
}
// 使用数组优化字符串拼接
function fastStringConcat() {
var start = Date.now();
var parts = [];
for (var i = 0; i < 10000; i++) {
parts.push('item', i, ' ');
}
var result = parts.join('');
console.log('快字符串拼接耗时:', Date.now() - start, 'ms');
return result;
}
// 字符串池优化
function StringPool() {
this.pool = {};
}
StringPool.prototype.intern = function(str) {
if (!(str in this.pool)) {
this.pool[str] = str;
}
return this.pool[str];
};
StringPool.prototype.clear = function() {
this.pool = {};
};
StringPool.prototype.size = function() {
return Object.keys(this.pool).length;
};
var stringPool = new StringPool();
// 演示字符串池
function demonstrateStringPool() {
var strings = ['hello', 'world', 'hello', 'javascript', 'world'];
strings.forEach(function(str) {
var pooled = stringPool.intern(str);
console.log('原始:', str, '池化:', pooled, '相同引用:', str === pooled);
});
console.log('字符串池大小:', stringPool.size());
}
slowStringConcat();
fastStringConcat();
demonstrateStringPool();
}
// 3. 对象和数组优化
function ObjectArrayOptimization() {
console.log('=== 对象数组优化示例 ===');
// 对象属性访问优化
function PropertyAccess() {
var obj = {
deeply: {
nested: {
property: {
value: 'target'
}
}
}
};
// 低效:重复属性访问
function slowAccess(iterations) {
var start = Date.now();
for (var i = 0; i < iterations; i++) {
var value = obj.deeply.nested.property.value; // 每次都遍历路径
if (value === 'target') {
// do something
}
}
console.log('慢属性访问耗时:', Date.now() - start, 'ms');
}
// 优化:缓存属性引用
function fastAccess(iterations) {
var start = Date.now();
var targetValue = obj.deeply.nested.property.value; // 缓存引用
for (var i = 0; i < iterations; i++) {
if (targetValue === 'target') {
// do something
}
}
console.log('快属性访问耗时:', Date.now() - start, 'ms');
}
slowAccess(100000);
fastAccess(100000);
}
// 数组操作优化
function ArrayOperations() {
var largeArray = [];
for (var i = 0; i < 10000; i++) {
largeArray.push({ id: i, value: Math.random() });
}
// 低效的数组查找
function slowFind(id) {
var start = Date.now();
var result = null;
for (var i = 0; i < largeArray.length; i++) {
if (largeArray[i].id === id) {
result = largeArray[i];
break;
}
}
console.log('慢查找耗时:', Date.now() - start, 'ms');
return result;
}
// 使用索引优化查找
function createIndexedArray() {
var start = Date.now();
var indexed = {};
for (var i = 0; i < largeArray.length; i++) {
var item = largeArray[i];
indexed[item.id] = item;
}
console.log('创建索引耗时:', Date.now() - start, 'ms');
return indexed;
}
function fastFind(indexed, id) {
var start = Date.now();
var result = indexed[id];
console.log('快查找耗时:', Date.now() - start, 'ms');
return result;
}
// 性能对比
slowFind(5000);
var indexed = createIndexedArray();
fastFind(indexed, 5000);
}
PropertyAccess();
ArrayOperations();
}
// 4. 函数调用优化
function FunctionOptimization() {
console.log('=== 函数优化示例 ===');
// 函数调用开销
function ExpensiveFunction(data) {
// 模拟复杂计算
var result = 0;
for (var i = 0; i < data.length; i++) {
result += Math.sqrt(data[i]) * Math.sin(data[i]);
}
return result;
}
// 记忆化优化
function createMemoizedFunction(fn) {
var cache = {};
return function() {
var key = JSON.stringify(arguments);
if (cache[key]) {
return cache[key];
}
var result = fn.apply(this, arguments);
cache[key] = result;
return result;
};
}
// 函数去抖优化
function debounce(func, wait) {
var timeout;
return function() {
var context = this;
var args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 函数节流优化
function throttle(func, limit) {
var inThrottle;
return function() {
var context = this;
var args = arguments;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(function() {
inThrottle = false;
}, limit);
}
};
}
// 演示优化效果
var data = [];
for (var i = 0; i < 1000; i++) {
data.push(Math.random() * 100);
}
var memoizedExpensive = createMemoizedFunction(ExpensiveFunction);
console.log('首次调用:');
console.time('expensive');
ExpensiveFunction(data);
console.timeEnd('expensive');
console.log('记忆化首次调用:');
console.time('memoized-first');
memoizedExpensive(data);
console.timeEnd('memoized-first');
console.log('记忆化再次调用:');
console.time('memoized-second');
memoizedExpensive(data);
console.timeEnd('memoized-second');
}
return {
runLoopOptimization: LoopOptimization,
runStringOptimization: StringOptimization,
runObjectArrayOptimization: ObjectArrayOptimization,
runFunctionOptimization: FunctionOptimization
};
})();
// DOM 操作优化
var DOMOptimizer = (function() {
// 1. 批量 DOM 操作
function BatchDOMOperations() {
console.log('=== DOM 批量操作优化 ===');
// 低效的 DOM 操作
function slowDOMOperations() {
var container = document.createElement('div');
document.body.appendChild(container);
var start = Date.now();
// 每次操作都触发重排重绘
for (var i = 0; i < 1000; i++) {
var div = document.createElement('div');
div.textContent = 'Item ' + i;
div.style.width = (100 + i) + 'px';
div.style.height = '30px';
div.style.backgroundColor = i % 2 === 0 ? '#f0f0f0' : '#e0e0e0';
container.appendChild(div);
}
console.log('慢 DOM 操作耗时:', Date.now() - start, 'ms');
document.body.removeChild(container);
}
// 优化的 DOM 操作
function fastDOMOperations() {
var container = document.createElement('div');
var start = Date.now();
// 使用文档片段批量添加
var fragment = document.createDocumentFragment();
for (var i = 0; i < 1000; i++) {
var div = document.createElement('div');
div.textContent = 'Item ' + i;
div.style.cssText = 'width: ' + (100 + i) + 'px; height: 30px; background-color: ' +
(i % 2 === 0 ? '#f0f0f0' : '#e0e0e0');
fragment.appendChild(div);
}
container.appendChild(fragment); // 一次性添加
document.body.appendChild(container);
console.log('快 DOM 操作耗时:', Date.now() - start, 'ms');
setTimeout(function() {
document.body.removeChild(container);
}, 100);
}
// 使用 innerHTML 优化
function htmlStringOptimization() {
var container = document.createElement('div');
var start = Date.now();
var html = [];
for (var i = 0; i < 1000; i++) {
html.push('<div style="width: ' + (100 + i) + 'px; height: 30px; background-color: ' +
(i % 2 === 0 ? '#f0f0f0' : '#e0e0e0') + ';">Item ' + i + '</div>');
}
container.innerHTML = html.join('');
document.body.appendChild(container);
console.log('HTML 字符串优化耗时:', Date.now() - start, 'ms');
setTimeout(function() {
document.body.removeChild(container);
}, 200);
}
slowDOMOperations();
setTimeout(fastDOMOperations, 500);
setTimeout(htmlStringOptimization, 1000);
}
// 2. 样式操作优化
function StyleOptimization() {
console.log('=== 样式操作优化 ===');
var testDiv = document.createElement('div');
testDiv.style.position = 'absolute';
testDiv.style.top = '-1000px';
testDiv.style.left = '-1000px';
testDiv.style.width = '100px';
testDiv.style.height = '100px';
document.body.appendChild(testDiv);
// 低效的样式操作
function slowStyleChanges() {
var start = Date.now();
// 每次修改都可能触发重排重绘
testDiv.style.width = '200px'; // 重排
testDiv.style.height = '200px'; // 重排
testDiv.style.backgroundColor = 'red'; // 重绘
testDiv.style.border = '5px solid blue'; // 重排
testDiv.style.padding = '10px'; // 重排
console.log('慢样式修改耗时:', Date.now() - start, 'ms');
}
// 优化的样式操作
function fastStyleChanges() {
var start = Date.now();
// 一次性修改所有样式
testDiv.style.cssText = 'position: absolute; top: -1000px; left: -1000px; width: 200px; height: 200px; background-color: red; border: 5px solid blue; padding: 10px;';
console.log('快样式修改耗时:', Date.now() - start, 'ms');
}
// 使用类名优化
function classNameOptimization() {
// 添加 CSS 规则
var style = document.createElement('style');
style.textContent = '.optimized-style { width: 200px; height: 200px; background-color: red; border: 5px solid blue; padding: 10px; }';
document.head.appendChild(style);
var start = Date.now();
testDiv.className = 'optimized-style';
console.log('类名优化耗时:', Date.now() - start, 'ms');
setTimeout(function() {
document.head.removeChild(style);
}, 100);
}
slowStyleChanges();
setTimeout(fastStyleChanges, 100);
setTimeout(classNameOptimization, 200);
setTimeout(function() {
document.body.removeChild(testDiv);
}, 500);
}
return {
runBatchDOMOperations: BatchDOMOperations,
runStyleOptimization: StyleOptimization
};
})();
// 内存管理优化
var MemoryOptimizer = (function() {
// 1. 内存泄漏检测和防护
function MemoryLeakPrevention() {
console.log('=== 内存泄漏防护 ===');
// 事件监听器管理
function EventListenerManager() {
this.listeners = [];
}
EventListenerManager.prototype.addEventListener = function(element, event, handler, options) {
element.addEventListener(event, handler, options);
this.listeners.push({
element: element,
event: event,
handler: handler,
options: options
});
return this;
};
EventListenerManager.prototype.removeEventListener = function(element, event, handler) {
element.removeEventListener(event, handler);
// 从记录中移除
for (var i = this.listeners.length - 1; i >= 0; i--) {
var listener = this.listeners[i];
if (listener.element === element &&
listener.event === event &&
listener.handler === handler) {
this.listeners.splice(i, 1);
break;
}
}
return this;
};
EventListenerManager.prototype.removeAllListeners = function() {
for (var i = 0; i < this.listeners.length; i++) {
var listener = this.listeners[i];
listener.element.removeEventListener(listener.event, listener.handler);
}
this.listeners = [];
return this;
};
// 定时器管理
function TimerManager() {
this.timers = [];
}
TimerManager.prototype.setTimeout = function(callback, delay) {
var id = setTimeout(callback, delay);
this.timers.push({ id: id, type: 'timeout' });
return id;
};
TimerManager.prototype.setInterval = function(callback, delay) {
var id = setInterval(callback, delay);
this.timers.push({ id: id, type: 'interval' });
return id;
};
TimerManager.prototype.clearTimeout = function(id) {
clearTimeout(id);
this.removeTimer(id);
};
TimerManager.prototype.clearInterval = function(id) {
clearInterval(id);
this.removeTimer(id);
};
TimerManager.prototype.removeTimer = function(id) {
for (var i = this.timers.length - 1; i >= 0; i--) {
if (this.timers[i].id === id) {
this.timers.splice(i, 1);
break;
}
}
};
TimerManager.prototype.clearAllTimers = function() {
for (var i = 0; i < this.timers.length; i++) {
var timer = this.timers[i];
if (timer.type === 'timeout') {
clearTimeout(timer.id);
} else if (timer.type === 'interval') {
clearInterval(timer.id);
}
}
this.timers = [];
};
// 使用示例
var eventManager = new EventListenerManager();
var timerManager = new TimerManager();
var button = document.createElement('button');
button.textContent = '点击测试';
document.body.appendChild(button);
function handleClick() {
console.log('按钮被点击');
}
eventManager.addEventListener(button, 'click', handleClick);
var timerId = timerManager.setInterval(function() {
console.log('定时器执行');
}, 1000);
// 清理资源
setTimeout(function() {
eventManager.removeAllListeners();
timerManager.clearAllTimers();
document.body.removeChild(button);
console.log('清理完成');
}, 3000);
}
// 2. 对象池技术
function ObjectPool() {
console.log('=== 对象池优化 ===');
// 通用对象池
function GenericObjectPool(createFn, resetFn, maxSize) {
this.createFn = createFn;
this.resetFn = resetFn;
this.maxSize = maxSize || 100;
this.pool = [];
this.created = 0;
this.reused = 0;
}
GenericObjectPool.prototype.acquire = function() {
var obj;
if (this.pool.length > 0) {
obj = this.pool.pop();
this.reused++;
} else {
obj = this.createFn();
this.created++;
}
return obj;
};
GenericObjectPool.prototype.release = function(obj) {
if (this.pool.length < this.maxSize) {
if (this.resetFn) {
this.resetFn(obj);
}
this.pool.push(obj);
}
};
GenericObjectPool.prototype.getStats = function() {
return {
poolSize: this.pool.length,
created: this.created,
reused: this.reused,
reuseRatio: this.reused / (this.created + this.reused)
};
};
// DOM 元素池示例
var divPool = new GenericObjectPool(
function() {
return document.createElement('div');
},
function(div) {
div.innerHTML = '';
div.className = '';
div.style.cssText = '';
},
50
);
// 使用对象池创建大量元素
var elements = [];
console.log('创建 1000 个 div 元素');
var start = Date.now();
for (var i = 0; i < 1000; i++) {
var div = divPool.acquire();
div.textContent = 'Item ' + i;
elements.push(div);
}
console.log('创建耗时:', Date.now() - start, 'ms');
console.log('对象池统计:', divPool.getStats());
// 释放元素回池
start = Date.now();
for (var i = 0; i < elements.length; i++) {
divPool.release(elements[i]);
}
console.log('释放耗时:', Date.now() - start, 'ms');
console.log('释放后统计:', divPool.getStats());
}
return {
runMemoryLeakPrevention: MemoryLeakPrevention,
runObjectPool: ObjectPool
};
})();
// 性能监控工具
var PerformanceMonitor = (function() {
function PerformanceProfiler() {
this.profiles = {};
this.running = {};
}
PerformanceProfiler.prototype.start = function(name) {
this.running[name] = {
startTime: Date.now(),
startMemory: this.getMemoryUsage()
};
};
PerformanceProfiler.prototype.end = function(name) {
if (!this.running[name]) {
console.warn('性能分析 "' + name + '" 未开始');
return;
}
var running = this.running[name];
var endTime = Date.now();
var endMemory = this.getMemoryUsage();
var profile = {
duration: endTime - running.startTime,
memoryDelta: endMemory - running.startMemory,
startMemory: running.startMemory,
endMemory: endMemory,
timestamp: new Date().toISOString()
};
if (!this.profiles[name]) {
this.profiles[name] = [];
}
this.profiles[name].push(profile);
delete this.running[name];
console.log('性能分析 "' + name + '":', profile);
return profile;
};
PerformanceProfiler.prototype.getMemoryUsage = function() {
// 在支持的浏览器中获取内存使用情况
if (window.performance && window.performance.memory) {
return window.performance.memory.usedJSHeapSize;
}
return 0;
};
PerformanceProfiler.prototype.getProfile = function(name) {
return this.profiles[name] || [];
};
PerformanceProfiler.prototype.getAverageProfile = function(name) {
var profiles = this.profiles[name];
if (!profiles || profiles.length === 0) {
return null;
}
var totalDuration = 0;
var totalMemoryDelta = 0;
for (var i = 0; i < profiles.length; i++) {
totalDuration += profiles[i].duration;
totalMemoryDelta += profiles[i].memoryDelta;
}
return {
count: profiles.length,
averageDuration: totalDuration / profiles.length,
averageMemoryDelta: totalMemoryDelta / profiles.length,
totalDuration: totalDuration,
totalMemoryDelta: totalMemoryDelta
};
};
PerformanceProfiler.prototype.clear = function(name) {
if (name) {
delete this.profiles[name];
delete this.running[name];
} else {
this.profiles = {};
this.running = {};
}
};
return {
Profiler: PerformanceProfiler
};
})();
// 使用示例和性能测试
console.log('=== JavaScript 性能优化演示开始 ===');
// 创建性能分析器
var profiler = new PerformanceMonitor.Profiler();
// 1. 运行代码优化示例
profiler.start('LoopOptimization');
PerformanceOptimizer.runLoopOptimization();
profiler.end('LoopOptimization');
setTimeout(function() {
profiler.start('StringOptimization');
PerformanceOptimizer.runStringOptimization();
profiler.end('StringOptimization');
setTimeout(function() {
profiler.start('ObjectArrayOptimization');
PerformanceOptimizer.runObjectArrayOptimization();
profiler.end('ObjectArrayOptimization');
setTimeout(function() {
profiler.start('FunctionOptimization');
PerformanceOptimizer.runFunctionOptimization();
profiler.end('FunctionOptimization');
// 显示性能统计
console.log('=== 性能统计 ===');
console.log('循环优化:', profiler.getAverageProfile('LoopOptimization'));
console.log('字符串优化:', profiler.getAverageProfile('StringOptimization'));
console.log('对象数组优化:', profiler.getAverageProfile('ObjectArrayOptimization'));
console.log('函数优化:', profiler.getAverageProfile('FunctionOptimization'));
}, 100);
}, 100);
}, 100);
// 2. DOM 优化演示
setTimeout(function() {
DOMOptimizer.runBatchDOMOperations();
setTimeout(function() {
DOMOptimizer.runStyleOptimization();
}, 1500);
}, 2000);
// 3. 内存管理演示
setTimeout(function() {
MemoryOptimizer.runMemoryLeakPrevention();
setTimeout(function() {
MemoryOptimizer.runObjectPool();
}, 4000);
}, 5000);
console.log('性能优化演示程序已启动,请查看控制台输出');
网络和加载优化:
// 网络和资源加载优化技术
var NetworkOptimizer = (function() {
// 1. 资源预加载
function ResourcePreloader() {
this.cache = {};
this.loading = {};
}
ResourcePreloader.prototype.preloadImage = function(src, callback) {
if (this.cache[src]) {
callback && callback(null, this.cache[src]);
return;
}
if (this.loading[src]) {
this.loading[src].callbacks.push(callback);
return;
}
this.loading[src] = { callbacks: [callback] };
var img = new Image();
var self = this;
img.onload = function() {
self.cache[src] = img;
var callbacks = self.loading[src].callbacks;
delete self.loading[src];
callbacks.forEach(function(cb) {
cb && cb(null, img);
});
};
img.onerror = function() {
var callbacks = self.loading[src].callbacks;
delete self.loading[src];
callbacks.forEach(function(cb) {
cb && cb(new Error('Failed to load image: ' + src));
});
};
img.src = src;
};
ResourcePreloader.prototype.preloadScript = function(src, callback) {
if (this.cache[src]) {
callback && callback(null, this.cache[src]);
return;
}
var script = document.createElement('script');
var self = this;
script.onload = function() {
self.cache[src] = script;
callback && callback(null, script);
};
script.onerror = function() {
callback && callback(new Error('Failed to load script: ' + src));
};
script.src = src;
document.head.appendChild(script);
};
// 2. 请求队列管理
function RequestQueue(maxConcurrent) {
this.maxConcurrent = maxConcurrent || 6;
this.queue = [];
this.running = [];
}
RequestQueue.prototype.add = function(requestFn) {
return new Promise(function(resolve, reject) {
this.queue.push({
requestFn: requestFn,
resolve: resolve,
reject: reject
});
this.process();
}.bind(this));
};
RequestQueue.prototype.process = function() {
while (this.running.length < this.maxConcurrent && this.queue.length > 0) {
var item = this.queue.shift();
this.running.push(item);
item.requestFn()
.then(function(result) {
item.resolve(result);
}.bind(this))
.catch(function(error) {
item.reject(error);
}.bind(this))
.finally(function() {
var index = this.running.indexOf(item);
if (index !== -1) {
this.running.splice(index, 1);
}
this.process();
}.bind(this));
}
};
// 3. 缓存策略
function CacheManager(maxSize, ttl) {
this.maxSize = maxSize || 100;
this.ttl = ttl || 300000; // 5分钟
this.cache = {};
this.accessOrder = [];
}
CacheManager.prototype.set = function(key, value) {
var now = Date.now();
// 如果已存在,更新访问顺序
if (this.cache[key]) {
var index = this.accessOrder.indexOf(key);
if (index !== -1) {
this.accessOrder.splice(index, 1);
}
}
this.cache[key] = {
value: value,
timestamp: now,
expires: now + this.ttl
};
this.accessOrder.push(key);
// 清理过期和超出大小限制
this.cleanup();
};
CacheManager.prototype.get = function(key) {
var item = this.cache[key];
if (!item) {
return null;
}
// 检查是否过期
if (Date.now() > item.expires) {
this.delete(key);
return null;
}
// 更新访问顺序
var index = this.accessOrder.indexOf(key);
if (index !== -1) {
this.accessOrder.splice(index, 1);
this.accessOrder.push(key);
}
return item.value;
};
CacheManager.prototype.delete = function(key) {
delete this.cache[key];
var index = this.accessOrder.indexOf(key);
if (index !== -1) {
this.accessOrder.splice(index, 1);
}
};
CacheManager.prototype.cleanup = function() {
var now = Date.now();
// 删除过期项
for (var key in this.cache) {
if (this.cache[key].expires < now) {
this.delete(key);
}
}
// 如果仍然超出大小限制,删除最少使用的项
while (this.accessOrder.length > this.maxSize) {
var oldestKey = this.accessOrder.shift();
delete this.cache[oldestKey];
}
};
return {
Preloader: ResourcePreloader,
RequestQueue: RequestQueue,
CacheManager: CacheManager
};
})();
// 使用示例
console.log('=== 网络优化演示 ===');
var preloader = new NetworkOptimizer.Preloader();
var requestQueue = new NetworkOptimizer.RequestQueue(3);
var cacheManager = new NetworkOptimizer.CacheManager(50, 60000);
// 缓存演示
cacheManager.set('user:123', { name: 'John', age: 30 });
console.log('缓存获取:', cacheManager.get('user:123'));
console.log('JavaScript 性能优化技术演示完成!');
性能优化最佳实践总结:
代码层面:
内存管理:
DOM 操作:
网络优化:
这些优化技术在 ES5 环境下仍然非常有效,是前端性能优化的基础。
What is memoization? How to implement it?
What is memoization? How to implement it?
考察点:缓存优化和算法改进。
答案:
函数记忆化(Memoization)是一种优化技术,通过缓存函数的计算结果来避免重复计算,特别适用于计算开销大、输入参数相同时结果相同的纯函数。这种技术能够显著提高程序的性能,特别是在递归算法和重复计算场景中。
记忆化的核心概念:
基础记忆化实现:
// 基础记忆化技术实现
var Memoization = (function() {
// 1. 简单记忆化函数
function simpleMemoize(fn) {
var cache = {};
return function() {
var key = JSON.stringify(arguments);
if (cache.hasOwnProperty(key)) {
console.log('从缓存返回:', key);
return cache[key];
}
console.log('计算新结果:', key);
var result = fn.apply(this, arguments);
cache[key] = result;
return result;
};
}
// 2. 高级记忆化函数(支持自定义键生成器)
function advancedMemoize(fn, keyGenerator, maxCacheSize) {
var cache = {};
var cacheKeys = [];
var maxSize = maxCacheSize || 1000;
// 默认键生成器
var generateKey = keyGenerator || function() {
return JSON.stringify(Array.prototype.slice.call(arguments));
};
return function() {
var key = generateKey.apply(this, arguments);
if (cache.hasOwnProperty(key)) {
// 将使用过的键移到最后(LRU)
var index = cacheKeys.indexOf(key);
if (index !== -1) {
cacheKeys.splice(index, 1);
cacheKeys.push(key);
}
return cache[key];
}
var result = fn.apply(this, arguments);
// 如果缓存满了,删除最久未使用的项
if (cacheKeys.length >= maxSize) {
var oldestKey = cacheKeys.shift();
delete cache[oldestKey];
}
cache[key] = result;
cacheKeys.push(key);
return result;
};
}
// 3. 记忆化类装饰器
function MemoizedFunction(fn, options) {
options = options || {};
this.originalFunction = fn;
this.cache = {};
this.stats = {
hits: 0,
misses: 0,
computations: 0
};
this.maxCacheSize = options.maxCacheSize || 1000;
this.ttl = options.ttl || null; // 生存时间(毫秒)
this.keyGenerator = options.keyGenerator || this.defaultKeyGenerator;
this.cacheKeys = [];
// 绑定上下文
var self = this;
this.memoized = function() {
return self.call.apply(self, arguments);
};
}
MemoizedFunction.prototype.defaultKeyGenerator = function() {
return JSON.stringify(Array.prototype.slice.call(arguments));
};
MemoizedFunction.prototype.call = function() {
var key = this.keyGenerator.apply(this, arguments);
var now = Date.now();
// 检查缓存
if (this.cache.hasOwnProperty(key)) {
var cacheEntry = this.cache[key];
// 检查是否过期
if (!this.ttl || (now - cacheEntry.timestamp < this.ttl)) {
this.stats.hits++;
// LRU 更新
var index = this.cacheKeys.indexOf(key);
if (index !== -1) {
this.cacheKeys.splice(index, 1);
this.cacheKeys.push(key);
}
return cacheEntry.value;
} else {
// 过期,删除
this.evict(key);
}
}
// 计算新结果
this.stats.misses++;
this.stats.computations++;
var result = this.originalFunction.apply(this, arguments);
// 缓存结果
this.set(key, result, now);
return result;
};
MemoizedFunction.prototype.set = function(key, value, timestamp) {
// 检查缓存大小限制
if (this.cacheKeys.length >= this.maxCacheSize) {
var oldestKey = this.cacheKeys.shift();
delete this.cache[oldestKey];
}
this.cache[key] = {
value: value,
timestamp: timestamp || Date.now()
};
this.cacheKeys.push(key);
};
MemoizedFunction.prototype.evict = function(key) {
if (this.cache.hasOwnProperty(key)) {
delete this.cache[key];
var index = this.cacheKeys.indexOf(key);
if (index !== -1) {
this.cacheKeys.splice(index, 1);
}
}
};
MemoizedFunction.prototype.clear = function() {
this.cache = {};
this.cacheKeys = [];
this.stats = {
hits: 0,
misses: 0,
computations: 0
};
};
MemoizedFunction.prototype.getStats = function() {
var total = this.stats.hits + this.stats.misses;
return {
hits: this.stats.hits,
misses: this.stats.misses,
computations: this.stats.computations,
hitRate: total > 0 ? (this.stats.hits / total * 100).toFixed(2) + '%' : '0%',
cacheSize: this.cacheKeys.length
};
};
return {
simple: simpleMemoize,
advanced: advancedMemoize,
MemoizedFunction: MemoizedFunction
};
})();
// 使用示例和性能测试
console.log('=== 记忆化技术演示 ===');
// 1. 斐波那契数列 - 经典递归优化案例
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
var memoizedFibonacci = Memoization.simple(fibonacci);
console.log('=== 斐波那契数列性能对比 ===');
// 普通递归(小数值测试)
console.time('普通斐波那契(30)');
console.log('fibonacci(30) =', fibonacci(30));
console.timeEnd('普通斐波那契(30)');
// 记忆化递归
console.time('记忆化斐波那契(30)');
console.log('memoizedFibonacci(30) =', memoizedFibonacci(30));
console.timeEnd('记忆化斐波那契(30)');
// 再次调用记忆化版本(应该很快)
console.time('记忆化斐波那契(30) - 第二次');
console.log('memoizedFibonacci(30) 再次 =', memoizedFibonacci(30));
console.timeEnd('记忆化斐波那契(30) - 第二次');
// 2. 复杂计算函数优化
function expensiveCalculation(x, y, iterations) {
console.log('执行复杂计算:', x, y, iterations);
var result = 0;
for (var i = 0; i < iterations; i++) {
result += Math.sin(x * i) + Math.cos(y * i) + Math.sqrt(i + 1);
}
return result;
}
// 使用高级记忆化
var memoizedCalculation = Memoization.advanced(
expensiveCalculation,
function(x, y, iterations) {
// 自定义键生成器,处理浮点数精度问题
return x.toFixed(6) + '|' + y.toFixed(6) + '|' + iterations;
},
100 // 最大缓存 100 项
);
console.log('=== 复杂计算优化演示 ===');
console.time('复杂计算 - 首次');
var result1 = memoizedCalculation(3.14159, 2.71828, 10000);
console.timeEnd('复杂计算 - 首次');
console.time('复杂计算 - 缓存');
var result2 = memoizedCalculation(3.14159, 2.71828, 10000);
console.timeEnd('复杂计算 - 缓存');
console.log('结果相同:', result1 === result2);
// 3. 高级记忆化功能演示
console.log('=== 高级记忆化功能 ===');
function slowFunction(n) {
// 模拟慢速计算
var result = 0;
for (var i = 0; i < n * 100000; i++) {
result += Math.sqrt(i);
}
return result;
}
var advancedMemoized = new Memoization.MemoizedFunction(slowFunction, {
maxCacheSize: 10,
ttl: 5000, // 5秒过期
keyGenerator: function(n) {
return 'slow_' + n;
}
});
// 测试缓存功能
for (var i = 1; i <= 5; i++) {
console.time('计算 slowFunction(' + i + ')');
advancedMemoized.memoized(i);
console.timeEnd('计算 slowFunction(' + i + ')');
}
console.log('首轮统计:', advancedMemoized.getStats());
// 再次调用相同参数
for (var i = 1; i <= 5; i++) {
console.time('缓存 slowFunction(' + i + ')');
advancedMemoized.memoized(i);
console.timeEnd('缓存 slowFunction(' + i + ')');
}
console.log('二轮统计:', advancedMemoized.getStats());
高级记忆化模式:
// 高级记忆化模式和应用场景
var AdvancedMemoizationPatterns = (function() {
// 1. 多参数记忆化
function MultiArgumentMemoization() {
// 处理多参数的记忆化
function createMultiArgMemoize() {
var cache = new Map(); // 使用 Map 处理复杂键
return function memoizeMultiArg(fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
var key = args.map(function(arg) {
if (typeof arg === 'object' && arg !== null) {
return JSON.stringify(arg);
}
return String(arg);
}).join('|');
if (cache.has(key)) {
return cache.get(key);
}
var result = fn.apply(this, args);
cache.set(key, result);
return result;
};
};
}
// 示例:多参数计算函数
function matrixMultiply(a, b) {
console.log('执行矩阵乘法计算');
var rows = a.length;
var cols = b[0].length;
var result = [];
for (var i = 0; i < rows; i++) {
result[i] = [];
for (var j = 0; j < cols; j++) {
var sum = 0;
for (var k = 0; k < b.length; k++) {
sum += a[i][k] * b[k][j];
}
result[i][j] = sum;
}
}
return result;
}
var memoizedMatrixMultiply = createMultiArgMemoize()(matrixMultiply);
// 测试矩阵乘法记忆化
var matrixA = [[1, 2], [3, 4]];
var matrixB = [[5, 6], [7, 8]];
console.log('首次矩阵乘法:');
console.time('matrix-multiply-1');
var result1 = memoizedMatrixMultiply(matrixA, matrixB);
console.timeEnd('matrix-multiply-1');
console.log('结果:', result1);
console.log('缓存矩阵乘法:');
console.time('matrix-multiply-2');
var result2 = memoizedMatrixMultiply(matrixA, matrixB);
console.timeEnd('matrix-multiply-2');
console.log('结果相同:', JSON.stringify(result1) === JSON.stringify(result2));
}
// 2. 异步函数记忆化
function AsyncMemoization() {
function memoizeAsync(asyncFn, keyGenerator) {
var cache = {};
var pending = {};
return function() {
var args = Array.prototype.slice.call(arguments);
var callback = args.pop(); // 最后一个参数是回调函数
var key = keyGenerator ?
keyGenerator.apply(this, args) :
JSON.stringify(args);
// 检查缓存
if (cache.hasOwnProperty(key)) {
console.log('异步缓存命中:', key);
setTimeout(function() {
callback(null, cache[key]);
}, 0);
return;
}
// 检查是否正在请求中
if (pending[key]) {
console.log('加入待处理队列:', key);
pending[key].push(callback);
return;
}
// 新请求
pending[key] = [callback];
console.log('新异步请求:', key);
var newArgs = args.concat([function(err, result) {
var callbacks = pending[key];
delete pending[key];
if (!err) {
cache[key] = result;
}
callbacks.forEach(function(cb) {
cb(err, result);
});
}]);
asyncFn.apply(this, newArgs);
};
}
// 模拟异步数据获取
function fetchUserData(userId, callback) {
console.log('正在获取用户数据:', userId);
setTimeout(function() {
var userData = {
id: userId,
name: 'User ' + userId,
email: 'user' + userId + '@example.com',
timestamp: Date.now()
};
callback(null, userData);
}, Math.random() * 1000 + 500); // 500-1500ms 随机延迟
}
var memoizedFetchUser = memoizeAsync(fetchUserData, function(userId) {
return 'user:' + userId;
});
// 测试异步记忆化
console.log('开始异步记忆化测试');
// 同时请求相同用户数据
for (var i = 0; i < 3; i++) {
memoizedFetchUser('123', function(err, data) {
console.log('用户数据回调:', err ? err.message : data);
});
}
// 延迟后再次请求
setTimeout(function() {
memoizedFetchUser('123', function(err, data) {
console.log('缓存用户数据:', err ? err.message : data);
});
}, 2000);
}
// 3. 条件记忆化
function ConditionalMemoization() {
function createConditionalMemoize(shouldCache, shouldCompute) {
var cache = {};
return function(fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
var key = JSON.stringify(args);
// 检查是否应该计算
if (shouldCompute && !shouldCompute.apply(this, args)) {
console.log('跳过计算:', key);
return null;
}
// 检查缓存
if (cache.hasOwnProperty(key)) {
console.log('条件缓存命中:', key);
return cache[key];
}
var result = fn.apply(this, args);
// 检查是否应该缓存
if (!shouldCache || shouldCache.call(this, result, args)) {
cache[key] = result;
console.log('结果已缓存:', key);
} else {
console.log('结果未缓存:', key);
}
return result;
};
};
}
// 示例:只缓存大于特定值的结果
function expensiveComputation(n) {
console.log('执行计算:', n);
return n * n * n; // 立方计算
}
var conditionalMemoized = createConditionalMemoize(
function(result, args) {
// 只缓存结果大于 100 的计算
return result > 100;
},
function(n) {
// 只计算正数
return n > 0;
}
)(expensiveComputation);
console.log('条件记忆化测试:');
// 这些会被计算但不会被缓存(结果 <= 100)
console.log('计算 3^3 =', conditionalMemoized(3)); // 27
console.log('再次计算 3^3 =', conditionalMemoized(3)); // 再次计算
// 这些会被计算和缓存(结果 > 100)
console.log('计算 5^3 =', conditionalMemoized(5)); // 125
console.log('缓存 5^3 =', conditionalMemoized(5)); // 从缓存获取
// 这个不会被计算(负数)
console.log('跳过 -5 =', conditionalMemoized(-5)); // null
}
// 4. 记忆化装饰器模式
function DecoratorPattern() {
// 可组合的记忆化装饰器
function createMemoizeDecorator(options) {
options = options || {};
return function(target, propertyKey, descriptor) {
var originalMethod = descriptor.value;
var cache = {};
descriptor.value = function() {
var key = JSON.stringify(arguments);
if (cache.hasOwnProperty(key)) {
return cache[key];
}
var result = originalMethod.apply(this, arguments);
cache[key] = result;
return result;
};
// 添加清理方法
descriptor.value.clearCache = function() {
cache = {};
};
return descriptor;
};
}
// 使用装饰器的类示例
function Calculator() {}
Calculator.prototype.fibonacci = function(n) {
console.log('计算斐波那契:', n);
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
};
Calculator.prototype.factorial = function(n) {
console.log('计算阶乘:', n);
if (n <= 1) return 1;
return n * this.factorial(n - 1);
};
// 手动应用装饰器(ES5 环境)
var memoizeDecorator = createMemoizeDecorator();
var fibDescriptor = { value: Calculator.prototype.fibonacci };
var factorialDescriptor = { value: Calculator.prototype.factorial };
Calculator.prototype.fibonacci = memoizeDecorator(Calculator.prototype, 'fibonacci', fibDescriptor).value;
Calculator.prototype.factorial = memoizeDecorator(Calculator.prototype, 'factorial', factorialDescriptor).value;
var calc = new Calculator();
console.log('装饰器模式测试:');
console.log('fibonacci(10) =', calc.fibonacci(10));
console.log('fibonacci(10) 再次 =', calc.fibonacci(10)); // 缓存
console.log('factorial(5) =', calc.factorial(5));
console.log('factorial(5) 再次 =', calc.factorial(5)); // 缓存
}
return {
runMultiArgumentMemoization: MultiArgumentMemoization,
runAsyncMemoization: AsyncMemoization,
runConditionalMemoization: ConditionalMemoization,
runDecoratorPattern: DecoratorPattern
};
})();
// 实用记忆化工具集
var MemoizationUtils = (function() {
// LRU 缓存实现
function LRUCache(capacity) {
this.capacity = capacity || 100;
this.cache = {};
this.order = [];
}
LRUCache.prototype.get = function(key) {
if (this.cache.hasOwnProperty(key)) {
// 移到最后
var index = this.order.indexOf(key);
if (index !== -1) {
this.order.splice(index, 1);
this.order.push(key);
}
return this.cache[key];
}
return undefined;
};
LRUCache.prototype.set = function(key, value) {
if (this.cache.hasOwnProperty(key)) {
// 更新现有值
this.cache[key] = value;
var index = this.order.indexOf(key);
if (index !== -1) {
this.order.splice(index, 1);
this.order.push(key);
}
} else {
// 新值
if (this.order.length >= this.capacity) {
// 删除最久未使用的
var oldest = this.order.shift();
delete this.cache[oldest];
}
this.cache[key] = value;
this.order.push(key);
}
};
LRUCache.prototype.clear = function() {
this.cache = {};
this.order = [];
};
LRUCache.prototype.size = function() {
return this.order.length;
};
// 基于 LRU 的记忆化
function lruMemoize(fn, capacity) {
var lru = new LRUCache(capacity);
return function() {
var key = JSON.stringify(arguments);
var cached = lru.get(key);
if (cached !== undefined) {
return cached;
}
var result = fn.apply(this, arguments);
lru.set(key, result);
return result;
};
}
// 记忆化工厂
function createMemoizer(options) {
options = options || {};
var cacheType = options.cache || 'simple';
var keyFn = options.keyGenerator || JSON.stringify;
var ttl = options.ttl;
var maxSize = options.maxSize;
return function(fn) {
var cache;
switch (cacheType) {
case 'lru':
cache = new LRUCache(maxSize);
return function() {
var key = keyFn(arguments);
var cached = cache.get(key);
if (cached !== undefined) {
return cached;
}
var result = fn.apply(this, arguments);
cache.set(key, result);
return result;
};
case 'ttl':
cache = {};
return function() {
var key = keyFn(arguments);
var now = Date.now();
if (cache[key] && (now - cache[key].timestamp < ttl)) {
return cache[key].value;
}
var result = fn.apply(this, arguments);
cache[key] = {
value: result,
timestamp: now
};
return result;
};
default: // simple
cache = {};
return function() {
var key = keyFn(arguments);
if (cache.hasOwnProperty(key)) {
return cache[key];
}
var result = fn.apply(this, arguments);
cache[key] = result;
return result;
};
}
};
}
return {
LRUCache: LRUCache,
lruMemoize: lruMemoize,
createMemoizer: createMemoizer
};
})();
// 演示所有记忆化技术
console.log('开始高级记忆化模式演示...');
setTimeout(function() {
console.log('\n=== 多参数记忆化 ===');
AdvancedMemoizationPatterns.runMultiArgumentMemoization();
}, 1000);
setTimeout(function() {
console.log('\n=== 异步记忆化 ===');
AdvancedMemoizationPatterns.runAsyncMemoization();
}, 2000);
setTimeout(function() {
console.log('\n=== 条件记忆化 ===');
AdvancedMemoizationPatterns.runConditionalMemoization();
}, 3000);
setTimeout(function() {
console.log('\n=== 装饰器模式 ===');
AdvancedMemoizationPatterns.runDecoratorPattern();
}, 4000);
setTimeout(function() {
console.log('\n=== LRU 记忆化演示 ===');
function slowSum(a, b, c) {
console.log('计算求和:', a, b, c);
return a + b + c;
}
var lruMemoizedSum = MemoizationUtils.lruMemoize(slowSum, 3);
// 测试 LRU 缓存
console.log('sum(1,2,3) =', lruMemoizedSum(1, 2, 3));
console.log('sum(4,5,6) =', lruMemoizedSum(4, 5, 6));
console.log('sum(7,8,9) =', lruMemoizedSum(7, 8, 9));
console.log('sum(10,11,12) =', lruMemoizedSum(10, 11, 12)); // 应该淘汰第一个
console.log('sum(1,2,3) 再次 =', lruMemoizedSum(1, 2, 3)); // 应该重新计算
}, 5000);
console.log('记忆化技术全面演示完成!');
记忆化应用场景:
适用场景:
不适用场景:
性能考虑:
记忆化是一种强大的优化技术,合理使用能显著提升应用性能,特别是在计算密集型和重复调用的场景中效果明显。
How to implement JSON.stringify?
How to implement JSON.stringify?
考察点:数据序列化和递归算法。
答案:
JSON.stringify 是将 JavaScript 值转换为 JSON 字符串的核心方法。实现一个完整的 JSON 序列化器需要处理各种数据类型、循环引用、特殊值转换等复杂情况。
JSON 序列化核心概念:
完整的 JSON.stringify 实现:
// 完整的 JSON 序列化实现
var JSONStringifier = (function() {
// 1. 基础 JSON.stringify 实现
function stringify(value, replacer, space) {
// 循环引用检测
var seen = [];
// 格式化配置
var indent = '';
var gap = '';
if (typeof space === 'number') {
space = Math.min(10, Math.max(0, Math.floor(space)));
gap = new Array(space + 1).join(' ');
} else if (typeof space === 'string') {
gap = space.slice(0, 10);
}
// 处理 replacer 参数
var propertyList = null;
var replacerFunction = null;
if (typeof replacer === 'function') {
replacerFunction = replacer;
} else if (Array.isArray(replacer)) {
propertyList = [];
for (var i = 0; i < replacer.length; i++) {
var item = replacer[i];
if (typeof item === 'string' || typeof item === 'number') {
var key = String(item);
if (propertyList.indexOf(key) === -1) {
propertyList.push(key);
}
}
}
}
// 主序列化函数
function serializeValue(key, holder, indent) {
var value = holder[key];
// 调用 replacer 函数
if (replacerFunction) {
value = replacerFunction.call(holder, key, value);
}
// 处理 toJSON 方法
if (value && typeof value === 'object' && typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
return isFinite(value) ? String(value) : 'null';
case 'boolean':
return String(value);
case 'undefined':
case 'function':
case 'symbol':
return undefined;
case 'object':
if (value === null) {
return 'null';
}
// 循环引用检测
if (seen.indexOf(value) !== -1) {
throw new TypeError('Converting circular structure to JSON');
}
seen.push(value);
var result;
if (Array.isArray(value)) {
result = serializeArray(value, indent);
} else {
result = serializeObject(value, indent);
}
seen.pop();
return result;
default:
return undefined;
}
}
// 序列化对象
function serializeObject(obj, indent) {
var stepback = indent;
indent += gap;
var partial = [];
var keys = propertyList || Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (obj.hasOwnProperty(key)) {
var serializedValue = serializeValue(key, obj, indent);
if (serializedValue !== undefined) {
var member = quote(key) + ':';
if (gap) {
member += ' ';
}
member += serializedValue;
partial.push(member);
}
}
}
if (partial.length === 0) {
return '{}';
}
if (!gap) {
return '{' + partial.join(',') + '}';
}
return '{\n' + indent + partial.join(',\n' + indent) + '\n' + stepback + '}';
}
// 序列化数组
function serializeArray(arr, indent) {
var stepback = indent;
indent += gap;
var partial = [];
for (var i = 0; i < arr.length; i++) {
var serializedValue = serializeValue(String(i), arr, indent);
partial.push(serializedValue !== undefined ? serializedValue : 'null');
}
if (partial.length === 0) {
return '[]';
}
if (!gap) {
return '[' + partial.join(',') + ']';
}
return '[\n' + indent + partial.join(',\n' + indent) + '\n' + stepback + ']';
}
// 字符串转义
function quote(string) {
var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
var meta = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"': '\\"',
'\\': '\\\\'
};
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function(a) {
var c = meta[a];
return typeof c === 'string' ?
c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
// 开始序列化
return serializeValue('', { '': value }, '');
}
// 2. 增强版本,支持更多特性
function enhancedStringify(value, replacer, space, options) {
options = options || {};
var maxDepth = options.maxDepth || Infinity;
var currentDepth = 0;
var seen = [];
// 自定义类型处理器
var typeHandlers = options.typeHandlers || {};
function serializeWithDepth(key, holder, indent, depth) {
if (depth > maxDepth) {
throw new Error('Maximum depth exceeded');
}
var value = holder[key];
// 调用 replacer
if (typeof replacer === 'function') {
value = replacer.call(holder, key, value);
}
// 检查自定义类型处理器
var valueType = Object.prototype.toString.call(value);
if (typeHandlers[valueType]) {
return typeHandlers[valueType](value, key, holder);
}
// 处理特殊对象类型
if (value instanceof Date) {
return '"' + value.toISOString() + '"';
}
if (value instanceof RegExp) {
return options.serializeRegExp ?
'{"__type__":"RegExp","source":"' + value.source + '","flags":"' + value.flags + '"}' :
'{}';
}
if (value instanceof Error) {
return options.serializeError ?
'{"__type__":"Error","name":"' + value.name + '","message":"' + value.message + '"}' :
'{}';
}
// 基础类型处理
switch (typeof value) {
case 'string':
return JSON.stringify(value);
case 'number':
if (isNaN(value)) {
return options.serializeNaN ? '"NaN"' : 'null';
}
if (!isFinite(value)) {
return options.serializeInfinity ?
(value > 0 ? '"Infinity"' : '"-Infinity"') :
'null';
}
return String(value);
case 'boolean':
return String(value);
case 'undefined':
return options.serializeUndefined ? '"__undefined__"' : undefined;
case 'function':
return options.serializeFunction ?
'"' + value.toString().replace(/"/g, '\\"') + '"' :
undefined;
case 'symbol':
return options.serializeSymbol ?
'"' + value.toString() + '"' :
undefined;
case 'object':
if (value === null) {
return 'null';
}
// 循环引用处理
if (seen.indexOf(value) !== -1) {
if (options.handleCircular === 'null') {
return 'null';
} else if (options.handleCircular === 'ref') {
return '"[Circular Reference]"';
} else {
throw new TypeError('Converting circular structure to JSON');
}
}
seen.push(value);
var result;
if (Array.isArray(value)) {
result = '[' + value.map(function(item, index) {
var serialized = serializeWithDepth(String(index), value, indent + ' ', depth + 1);
return serialized !== undefined ? serialized : 'null';
}).join(',') + ']';
} else {
var pairs = [];
for (var key in value) {
if (value.hasOwnProperty(key)) {
var serialized = serializeWithDepth(key, value, indent + ' ', depth + 1);
if (serialized !== undefined) {
pairs.push(JSON.stringify(key) + ':' + serialized);
}
}
}
result = '{' + pairs.join(',') + '}';
}
seen.pop();
return result;
}
}
return serializeWithDepth('', { '': value }, '', 0);
}
return {
stringify: stringify,
enhancedStringify: enhancedStringify
};
})();
// 使用示例和测试
console.log('=== JSON.stringify 实现测试 ===');
// 1. 基本类型测试
var testCases = {
string: 'hello world',
number: 42,
boolean: true,
null: null,
undefined: undefined,
array: [1, 2, 3, 'four', true, null],
object: { a: 1, b: 'two', c: true, d: null },
nested: {
user: {
name: 'John',
age: 30,
hobbies: ['reading', 'coding']
},
meta: {
created: new Date(),
version: 1.0
}
}
};
console.log('原生 JSON.stringify:');
for (var key in testCases) {
try {
console.log(key + ':', JSON.stringify(testCases[key]));
} catch (e) {
console.log(key + ':', 'Error -', e.message);
}
}
console.log('\n自实现 JSON.stringify:');
for (var key in testCases) {
try {
console.log(key + ':', JSONStringifier.stringify(testCases[key]));
} catch (e) {
console.log(key + ':', 'Error -', e.message);
}
}
// 2. 循环引用测试
var circularObj = { name: 'test' };
circularObj.self = circularObj;
console.log('\n=== 循环引用测试 ===');
try {
JSONStringifier.stringify(circularObj);
} catch (e) {
console.log('循环引用错误:', e.message);
}
// 3. replacer 函数测试
var data = {
name: 'John',
age: 30,
password: 'secret',
email: '[email protected]'
};
function replacer(key, value) {
if (key === 'password') {
return '[HIDDEN]';
}
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}
console.log('\n=== Replacer 函数测试 ===');
console.log('原生:', JSON.stringify(data, replacer));
console.log('自实现:', JSONStringifier.stringify(data, replacer));
// 4. space 参数测试
console.log('\n=== Space 参数测试 ===');
console.log('原生 (space=2):');
console.log(JSON.stringify(data, null, 2));
console.log('自实现 (space=2):');
console.log(JSONStringifier.stringify(data, null, 2));
// 5. 增强版功能测试
console.log('\n=== 增强版功能测试 ===');
var complexData = {
date: new Date(),
regex: /hello/gi,
error: new Error('test error'),
fn: function() { return 'hello'; },
symbol: Symbol('test'),
nan: NaN,
infinity: Infinity,
undefined: undefined
};
var enhancedOptions = {
serializeRegExp: true,
serializeError: true,
serializeFunction: true,
serializeSymbol: true,
serializeNaN: true,
serializeInfinity: true,
serializeUndefined: true
};
console.log('增强版序列化:');
console.log(JSONStringifier.enhancedStringify(complexData, null, null, enhancedOptions));
How to implement JSON.parse?
How to implement JSON.parse?
考察点:字符串解析和状态机设计。
答案:
JSON.parse 是将 JSON 字符串解析为 JavaScript 值的核心方法。实现一个完整的 JSON 解析器需要词法分析、语法分析、错误处理等技术。
JSON 解析核心概念:
完整的 JSON.parse 实现:
// 完整的 JSON 解析器实现
var JSONParser = (function() {
// 1. 词法分析器
function Lexer(input) {
this.input = input;
this.position = 0;
this.line = 1;
this.column = 1;
}
Lexer.prototype.current = function() {
return this.input[this.position];
};
Lexer.prototype.peek = function(offset) {
return this.input[this.position + (offset || 1)];
};
Lexer.prototype.advance = function() {
if (this.current() === '\n') {
this.line++;
this.column = 1;
} else {
this.column++;
}
this.position++;
};
Lexer.prototype.skipWhitespace = function() {
while (this.position < this.input.length) {
var char = this.current();
if (char === ' ' || char === '\t' || char === '\n' || char === '\r') {
this.advance();
} else {
break;
}
}
};
Lexer.prototype.readString = function() {
var result = '';
this.advance(); // 跳过开始的引号
while (this.position < this.input.length) {
var char = this.current();
if (char === '"') {
this.advance(); // 跳过结束的引号
return result;
}
if (char === '\\') {
this.advance();
var escaped = this.current();
switch (escaped) {
case '"':
result += '"';
break;
case '\\':
result += '\\';
break;
case '/':
result += '/';
break;
case 'b':
result += '\b';
break;
case 'f':
result += '\f';
break;
case 'n':
result += '\n';
break;
case 'r':
result += '\r';
break;
case 't':
result += '\t';
break;
case 'u':
// Unicode 转义序列
this.advance();
var unicode = '';
for (var i = 0; i < 4; i++) {
if (this.position >= this.input.length) {
throw new SyntaxError('Unexpected end of input in unicode escape');
}
var hexChar = this.current();
if (!/[0-9a-fA-F]/.test(hexChar)) {
throw new SyntaxError('Invalid unicode escape sequence');
}
unicode += hexChar;
this.advance();
}
result += String.fromCharCode(parseInt(unicode, 16));
continue;
default:
throw new SyntaxError('Invalid escape sequence: \\' + escaped);
}
this.advance();
} else if (char.charCodeAt(0) < 32) {
throw new SyntaxError('Unescaped control character in string');
} else {
result += char;
this.advance();
}
}
throw new SyntaxError('Unterminated string');
};
Lexer.prototype.readNumber = function() {
var start = this.position;
var hasDecimal = false;
var hasExponent = false;
// 处理负号
if (this.current() === '-') {
this.advance();
}
// 处理整数部分
if (this.current() === '0') {
this.advance();
} else if (/[1-9]/.test(this.current())) {
while (/[0-9]/.test(this.current())) {
this.advance();
}
} else {
throw new SyntaxError('Invalid number format');
}
// 处理小数部分
if (this.current() === '.') {
hasDecimal = true;
this.advance();
if (!/[0-9]/.test(this.current())) {
throw new SyntaxError('Invalid number format: missing digits after decimal point');
}
while (/[0-9]/.test(this.current())) {
this.advance();
}
}
// 处理指数部分
if (this.current() === 'e' || this.current() === 'E') {
hasExponent = true;
this.advance();
if (this.current() === '+' || this.current() === '-') {
this.advance();
}
if (!/[0-9]/.test(this.current())) {
throw new SyntaxError('Invalid number format: missing digits in exponent');
}
while (/[0-9]/.test(this.current())) {
this.advance();
}
}
var numberStr = this.input.slice(start, this.position);
return parseFloat(numberStr);
};
Lexer.prototype.readLiteral = function() {
var start = this.position;
while (this.position < this.input.length && /[a-zA-Z]/.test(this.current())) {
this.advance();
}
var literal = this.input.slice(start, this.position);
switch (literal) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
throw new SyntaxError('Invalid literal: ' + literal);
}
};
// 2. 语法解析器
function Parser(input) {
this.lexer = new Lexer(input);
this.reviver = null;
}
Parser.prototype.parse = function(reviver) {
this.reviver = reviver;
this.lexer.skipWhitespace();
if (this.lexer.position >= this.lexer.input.length) {
throw new SyntaxError('Unexpected end of JSON input');
}
var result = this.parseValue();
this.lexer.skipWhitespace();
if (this.lexer.position < this.lexer.input.length) {
throw new SyntaxError('Unexpected token at position ' + this.lexer.position);
}
return this.applyReviver('', result);
};
Parser.prototype.parseValue = function() {
this.lexer.skipWhitespace();
var char = this.lexer.current();
switch (char) {
case '"':
return this.lexer.readString();
case '{':
return this.parseObject();
case '[':
return this.parseArray();
case 't':
case 'f':
case 'n':
return this.lexer.readLiteral();
default:
if (char === '-' || /[0-9]/.test(char)) {
return this.lexer.readNumber();
}
throw new SyntaxError('Unexpected token: ' + char + ' at position ' + this.lexer.position);
}
};
Parser.prototype.parseObject = function() {
var obj = {};
this.lexer.advance(); // 跳过 '{'
this.lexer.skipWhitespace();
// 处理空对象
if (this.lexer.current() === '}') {
this.lexer.advance();
return obj;
}
while (true) {
this.lexer.skipWhitespace();
// 读取键
if (this.lexer.current() !== '"') {
throw new SyntaxError('Expected string key at position ' + this.lexer.position);
}
var key = this.lexer.readString();
this.lexer.skipWhitespace();
// 读取冒号
if (this.lexer.current() !== ':') {
throw new SyntaxError('Expected ":" after key at position ' + this.lexer.position);
}
this.lexer.advance();
// 读取值
var value = this.parseValue();
obj[key] = value;
this.lexer.skipWhitespace();
var next = this.lexer.current();
if (next === '}') {
this.lexer.advance();
break;
} else if (next === ',') {
this.lexer.advance();
} else {
throw new SyntaxError('Expected "," or "}" at position ' + this.lexer.position);
}
}
return obj;
};
Parser.prototype.parseArray = function() {
var arr = [];
this.lexer.advance(); // 跳过 '['
this.lexer.skipWhitespace();
// 处理空数组
if (this.lexer.current() === ']') {
this.lexer.advance();
return arr;
}
while (true) {
var value = this.parseValue();
arr.push(value);
this.lexer.skipWhitespace();
var next = this.lexer.current();
if (next === ']') {
this.lexer.advance();
break;
} else if (next === ',') {
this.lexer.advance();
} else {
throw new SyntaxError('Expected "," or "]" at position ' + this.lexer.position);
}
}
return arr;
};
Parser.prototype.applyReviver = function(key, value) {
if (this.reviver) {
if (typeof value === 'object' && value !== null) {
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
value[i] = this.applyReviver(String(i), value[i]);
}
} else {
for (var prop in value) {
if (value.hasOwnProperty(prop)) {
value[prop] = this.applyReviver(prop, value[prop]);
}
}
}
}
return this.reviver(key, value);
}
return value;
};
// 3. 主解析函数
function parse(text, reviver) {
if (typeof text !== 'string') {
throw new TypeError('JSON.parse expects a string');
}
try {
var parser = new Parser(text);
return parser.parse(reviver);
} catch (error) {
// 增强错误信息
if (error instanceof SyntaxError) {
var parser = new Parser(text);
var line = parser.lexer.line;
var column = parser.lexer.column;
throw new SyntaxError(error.message + ' at line ' + line + ' column ' + column);
}
throw error;
}
}
return {
parse: parse,
Lexer: Lexer,
Parser: Parser
};
})();
// 使用示例和测试
console.log('=== JSON.parse 实现测试 ===');
// 1. 基本类型测试
var jsonStrings = [
'"hello world"',
'42',
'true',
'false',
'null',
'[1,2,3,"four",true,null]',
'{"a":1,"b":"two","c":true,"d":null}',
'{"user":{"name":"John","age":30},"active":true}'
];
console.log('解析测试:');
jsonStrings.forEach(function(jsonStr, index) {
try {
var native = JSON.parse(jsonStr);
var custom = JSONParser.parse(jsonStr);
console.log('Test ' + (index + 1) + ':',
JSON.stringify(native) === JSON.stringify(custom) ? 'PASS' : 'FAIL');
console.log(' Input:', jsonStr);
console.log(' Native:', JSON.stringify(native));
console.log(' Custom:', JSON.stringify(custom));
} catch (e) {
console.log('Test ' + (index + 1) + ' Error:', e.message);
}
});
// 2. reviver 函数测试
var jsonWithDates = '{"name":"John","birthDate":"2023-01-01T00:00:00.000Z","age":30}';
function dateReviver(key, value) {
if (key === 'birthDate') {
return new Date(value);
}
return value;
}
console.log('\n=== Reviver 函数测试 ===');
console.log('原始 JSON:', jsonWithDates);
var nativeResult = JSON.parse(jsonWithDates, dateReviver);
var customResult = JSONParser.parse(jsonWithDates, dateReviver);
console.log('Native result:', nativeResult);
console.log('Custom result:', customResult);
console.log('Date objects equal:',
nativeResult.birthDate instanceof Date &&
customResult.birthDate instanceof Date &&
nativeResult.birthDate.getTime() === customResult.birthDate.getTime());
// 3. 错误处理测试
var invalidJsons = [
'{"invalid": }',
'[1,2,3,]',
'{"unclosed": "string',
'{invalid_key: "value"}',
'{"duplicate": 1, "duplicate": 2}',
'undefined',
'{,}',
'[,]'
];
console.log('\n=== 错误处理测试 ===');
invalidJsons.forEach(function(invalidJson, index) {
try {
JSONParser.parse(invalidJson);
console.log('Test ' + (index + 1) + ': Should have thrown error for:', invalidJson);
} catch (e) {
console.log('Test ' + (index + 1) + ': Correctly caught error -', e.message.split('\n')[0]);
}
});
console.log('\nJSON 解析器实现完成!');
高级特性和优化:
// JSON 解析器的高级特性
var AdvancedJSONParser = (function() {
// 1. 流式解析器(处理大型 JSON)
function StreamingParser() {
this.buffer = '';
this.state = 'start';
this.stack = [];
this.current = null;
this.key = null;
}
StreamingParser.prototype.write = function(chunk) {
this.buffer += chunk;
this.process();
};
StreamingParser.prototype.process = function() {
// 流式处理逻辑(简化版)
while (this.buffer.length > 0) {
this.processNextToken();
}
};
StreamingParser.prototype.processNextToken = function() {
// 实际的流式处理会更复杂
// 这里只是演示概念
console.log('Processing stream chunk...');
};
// 2. 容错解析器
function TolerantParser(options) {
this.options = options || {};
this.allowComments = this.options.allowComments || false;
this.allowTrailingCommas = this.options.allowTrailingCommas || false;
this.allowSingleQuotes = this.options.allowSingleQuotes || false;
}
TolerantParser.prototype.parse = function(text) {
// 预处理,移除注释
if (this.allowComments) {
text = this.removeComments(text);
}
// 处理单引号
if (this.allowSingleQuotes) {
text = this.normalizeSingleQuotes(text);
}
// 处理尾随逗号
if (this.allowTrailingCommas) {
text = this.removeTrailingCommas(text);
}
return JSONParser.parse(text);
};
TolerantParser.prototype.removeComments = function(text) {
// 简化的注释移除(实际实现需要更复杂的状态机)
return text.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/\/\/.*$/gm, '');
};
TolerantParser.prototype.normalizeSingleQuotes = function(text) {
var result = '';
var inDoubleQuote = false;
var inSingleQuote = false;
var escaped = false;
for (var i = 0; i < text.length; i++) {
var char = text[i];
if (escaped) {
result += char;
escaped = false;
continue;
}
if (char === '\\') {
escaped = true;
result += char;
continue;
}
if (char === '"' && !inSingleQuote) {
inDoubleQuote = !inDoubleQuote;
result += char;
} else if (char === "'" && !inDoubleQuote) {
inSingleQuote = !inSingleQuote;
result += '"'; // 转换为双引号
} else {
result += char;
}
}
return result;
};
TolerantParser.prototype.removeTrailingCommas = function(text) {
return text.replace(/,(\s*[}\]])/g, '$1');
};
// 3. 性能优化的解析器
function OptimizedParser() {
this.tokenCache = {};
this.parseCache = {};
}
OptimizedParser.prototype.parse = function(text) {
// 缓存相同的解析结果
if (this.parseCache[text]) {
return this.parseCache[text];
}
var result = JSONParser.parse(text);
// 只缓存小型结果
if (text.length < 1000) {
this.parseCache[text] = result;
}
return result;
};
return {
StreamingParser: StreamingParser,
TolerantParser: TolerantParser,
OptimizedParser: OptimizedParser
};
})();
// 综合测试
console.log('\n=== 高级特性测试 ===');
// 容错解析测试
var tolerantParser = new AdvancedJSONParser.TolerantParser({
allowComments: true,
allowTrailingCommas: true,
allowSingleQuotes: true
});
var relaxedJson = `{
// 这是注释
'name': 'John',
"age": 30,
"hobbies": ['reading', 'coding',], // 尾随逗号
}`;
try {
var tolerantResult = tolerantParser.parse(relaxedJson);
console.log('容错解析成功:', tolerantResult);
} catch (e) {
console.log('容错解析失败:', e.message);
}
console.log('\n完整的 JSON 解析器实现完成!');
JSON 解析器总结:
核心功能:
高级特性:
应用场景:
这个实现涵盖了 JSON 解析的所有核心概念,为理解编译原理和解析器设计提供了良好的基础。
What is tail call optimization? How to avoid stack overflow in JavaScript?
What is tail call optimization? How to avoid stack overflow in JavaScript?
考察点:递归优化和内存管理。
答案:
尾调用优化(Tail Call Optimization,TCO)是一种编译器或解释器的优化技术,用于避免递归函数调用时的栈溢出问题。虽然 ES5 不支持尾调用优化,但我们可以通过其他技术来实现类似的效果。
尾调用优化核心概念:
ES5 中避免栈溢出的技术:
// 尾调用优化和栈溢出避免技术
var StackOptimization = (function() {
// 1. 传统递归 vs 尾递归转换
function RecursionOptimization() {
console.log('=== 递归优化示例 ===');
// 传统递归(会栈溢出)
function factorialNormal(n) {
if (n <= 1) return 1;
return n * factorialNormal(n - 1); // 不是尾调用
}
// 尾递归优化(ES6+ 中可优化)
function factorialTail(n, acc) {
acc = acc || 1;
if (n <= 1) return acc;
return factorialTail(n - 1, n * acc); // 尾调用
}
// ES5 手动优化 - 循环替换递归
function factorialIterative(n) {
var result = 1;
for (var i = 2; i <= n; i++) {
result *= i;
}
return result;
}
// 蹦床技术(Trampoline)
function trampolineFactorial(n, acc) {
acc = acc || 1;
function bounce(fn) {
while (typeof fn === 'function') {
fn = fn();
}
return fn;
}
function factorial(n, acc) {
if (n <= 1) return acc;
return function() {
return factorial(n - 1, n * acc);
};
}
return bounce(function() { return factorial(n, acc); });
}
// 性能测试
var testN = 1000;
console.log('传统递归 factorial(' + testN + '):');
try {
console.time('normal');
var result1 = factorialNormal(testN);
console.timeEnd('normal');
console.log('结果长度:', result1.toString().length);
} catch (e) {
console.log('栈溢出:', e.message);
}
console.log('迭代版本 factorial(' + testN + '):');
console.time('iterative');
var result2 = factorialIterative(testN);
console.timeEnd('iterative');
console.log('结果长度:', result2.toString().length);
console.log('蹦床版本 factorial(' + testN + '):');
console.time('trampoline');
var result3 = trampolineFactorial(testN);
console.timeEnd('trampoline');
console.log('结果长度:', result3.toString().length);
console.log('结果一致性:',
result2.toString() === result3.toString() ? 'PASS' : 'FAIL');
}
// 2. 蹦床技术框架
function TrampolineFramework() {
// 通用蹦床实现
function Trampoline() {
this.maxIterations = 100000; // 防止无限循环
}
Trampoline.prototype.run = function(fn) {
var iterations = 0;
var result = fn;
while (typeof result === 'function' && iterations < this.maxIterations) {
result = result();
iterations++;
}
if (iterations >= this.maxIterations) {
throw new Error('Maximum trampoline iterations exceeded');
}
return result;
};
// 辅助函数:创建延迟调用
function thunk(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
return fn.apply(this, args);
};
}
// 辅助函数:创建尾调用
function tailCall(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
return fn.apply(this, args);
};
}
// 斐波那契数列 - 蹦床版本
function fibonacciTrampoline(n, a, b) {
a = a || 0;
b = b || 1;
if (n === 0) return a;
if (n === 1) return b;
return tailCall(fibonacciTrampoline, n - 1, b, a + b);
}
// 数组求和 - 蹦床版本
function sumArrayTrampoline(arr, index, acc) {
index = index || 0;
acc = acc || 0;
if (index >= arr.length) return acc;
return tailCall(sumArrayTrampoline, arr, index + 1, acc + arr[index]);
}
// 树遍历 - 蹦床版本
function traverseTreeTrampoline(node, callback, stack) {
stack = stack || [];
if (!node) {
if (stack.length === 0) return;
return tailCall(traverseTreeTrampoline, stack.pop(), callback, stack);
}
callback(node);
if (node.right) stack.push(node.right);
if (node.left) {
return tailCall(traverseTreeTrampoline, node.left, callback, stack);
}
if (stack.length > 0) {
return tailCall(traverseTreeTrampoline, stack.pop(), callback, stack);
}
}
// 使用示例
var trampoline = new Trampoline();
console.log('=== 蹦床技术示例 ===');
// 斐波那契测试
console.log('fibonacci(1000):');
console.time('fib-trampoline');
var fibResult = trampoline.run(thunk(fibonacciTrampoline, 1000));
console.timeEnd('fib-trampoline');
console.log('结果长度:', fibResult.toString().length);
// 数组求和测试
var largeArray = [];
for (var i = 0; i < 10000; i++) {
largeArray.push(i);
}
console.log('大数组求和(10000项):');
console.time('sum-trampoline');
var sumResult = trampoline.run(thunk(sumArrayTrampoline, largeArray));
console.timeEnd('sum-trampoline');
console.log('求和结果:', sumResult);
// 树遍历测试
function createDeepTree(depth) {
if (depth <= 0) return null;
return {
value: depth,
left: createDeepTree(depth - 1),
right: null
};
}
var deepTree = createDeepTree(1000);
var traversedValues = [];
console.log('深度树遍历(1000层):');
console.time('tree-trampoline');
trampoline.run(thunk(traverseTreeTrampoline, deepTree, function(node) {
traversedValues.push(node.value);
}));
console.timeEnd('tree-trampoline');
console.log('遍历节点数:', traversedValues.length);
return {
Trampoline: Trampoline,
thunk: thunk,
tailCall: tailCall
};
}
// 3. 延续传递风格 (Continuation Passing Style)
function CPSOptimization() {
console.log('=== CPS 优化示例 ===');
// 传统递归
function factorialNormal(n) {
if (n <= 1) return 1;
return n * factorialNormal(n - 1);
}
// CPS 风格
function factorialCPS(n, cont) {
if (n <= 1) {
return cont(1);
}
return setTimeout(function() {
factorialCPS(n - 1, function(result) {
cont(n * result);
});
}, 0); // 异步化避免栈溢出
}
// 同步 CPS(仍可能栈溢出,但结构清晰)
function factorialCPSSync(n, cont) {
if (n <= 1) {
return cont(1);
}
return factorialCPSSync(n - 1, function(result) {
return cont(n * result);
});
}
// 异步 CPS 包装器
function factorialAsync(n, callback) {
function cpsHelper(n, cont) {
if (n <= 1) {
return setTimeout(function() { cont(1); }, 0);
}
setTimeout(function() {
cpsHelper(n - 1, function(result) {
cont(n * result);
});
}, 0);
}
cpsHelper(n, callback);
}
// 数组映射 CPS
function mapCPS(array, transform, cont, index) {
index = index || 0;
if (index >= array.length) {
return cont([]);
}
setTimeout(function() {
mapCPS(array, transform, function(restResult) {
var transformedItem = transform(array[index]);
cont([transformedItem].concat(restResult));
}, index + 1);
}, 0);
}
// 使用示例
console.log('CPS factorial(10):');
factorialAsync(10, function(result) {
console.log('CPS 结果:', result);
});
console.log('CPS 数组映射:');
var testArray = [1, 2, 3, 4, 5];
mapCPS(testArray, function(x) { return x * x; }, function(result) {
console.log('映射结果:', result);
});
}
// 4. 堆栈安全的递归包装器
function StackSafeRecursion() {
// 自动检测和处理栈溢出
function makeSafe(fn, maxDepth) {
maxDepth = maxDepth || 1000;
var currentDepth = 0;
function safeWrapper() {
currentDepth++;
if (currentDepth > maxDepth) {
currentDepth = 0;
// 使用 setTimeout 重置调用栈
var args = Array.prototype.slice.call(arguments);
var self = this;
return new Promise(function(resolve) {
setTimeout(function() {
resolve(fn.apply(self, args));
}, 0);
});
}
try {
var result = fn.apply(this, arguments);
currentDepth--;
return result;
} catch (error) {
currentDepth--;
throw error;
}
}
return safeWrapper;
}
// 递归转迭代的通用工具
function recursionToIteration(recursiveFn) {
return function() {
var stack = [{ fn: recursiveFn, args: arguments, context: this }];
var result = undefined;
while (stack.length > 0) {
var current = stack.pop();
try {
result = current.fn.apply(current.context, current.args);
// 如果返回的是函数调用,添加到栈中
if (typeof result === 'function') {
stack.push({
fn: result,
args: [],
context: current.context
});
}
} catch (error) {
throw error;
}
}
return result;
};
}
// Y 组合子实现(固定点组合子)
function YCombinator(fn) {
return (function(x) {
return fn(function(v) {
return x(x)(v);
});
})(function(x) {
return fn(function(v) {
return x(x)(v);
});
});
}
// 使用 Y 组合子的阶乘
var factorialY = YCombinator(function(factorial) {
return function(n) {
return n <= 1 ? 1 : n * factorial(n - 1);
};
});
console.log('=== 栈安全递归示例 ===');
// 测试栈安全包装器
var unsafeFib = function(n) {
if (n <= 1) return n;
return unsafeFib(n - 1) + unsafeFib(n - 2);
};
var safeFib = makeSafe(function(n) {
if (n <= 1) return n;
return safeFib(n - 1) + safeFib(n - 2);
}, 100);
console.log('Y 组合子 factorial(10):', factorialY(10));
return {
makeSafe: makeSafe,
recursionToIteration: recursionToIteration,
YCombinator: YCombinator
};
}
return {
runRecursionOptimization: RecursionOptimization,
createTrampolineFramework: TrampolineFramework,
runCPSOptimization: CPSOptimization,
createStackSafeRecursion: StackSafeRecursion
};
})();
// 内存管理和栈监控工具
var StackMonitor = (function() {
function StackTracker() {
this.maxDepth = 0;
this.currentDepth = 0;
this.calls = [];
this.enabled = false;
}
StackTracker.prototype.enable = function() {
this.enabled = true;
this.instrument();
};
StackTracker.prototype.disable = function() {
this.enabled = false;
};
StackTracker.prototype.instrument = function() {
var self = this;
var originalError = Error;
// 拦截函数调用来跟踪栈深度
Error = function() {
var error = originalError.apply(this, arguments);
if (self.enabled) {
var stack = error.stack || '';
var depth = (stack.match(/at /g) || []).length;
self.currentDepth = depth;
if (depth > self.maxDepth) {
self.maxDepth = depth;
}
self.calls.push({
timestamp: Date.now(),
depth: depth,
stack: stack
});
}
return error;
};
// 保持原型链
Error.prototype = originalError.prototype;
};
StackTracker.prototype.getStats = function() {
return {
maxDepth: this.maxDepth,
currentDepth: this.currentDepth,
totalCalls: this.calls.length,
averageDepth: this.calls.length > 0 ?
this.calls.reduce(function(sum, call) { return sum + call.depth; }, 0) / this.calls.length : 0
};
};
StackTracker.prototype.reset = function() {
this.maxDepth = 0;
this.currentDepth = 0;
this.calls = [];
};
// 栈溢出预测器
function StackOverflowPredictor() {
this.warningThreshold = 8000; // Chrome 默认约 10000
this.errorThreshold = 9500;
this.callbacks = [];
}
StackOverflowPredictor.prototype.onWarning = function(callback) {
this.callbacks.push({ type: 'warning', callback: callback });
};
StackOverflowPredictor.prototype.onError = function(callback) {
this.callbacks.push({ type: 'error', callback: callback });
};
StackOverflowPredictor.prototype.checkStack = function() {
try {
var error = new Error();
var depth = (error.stack.match(/at /g) || []).length;
if (depth >= this.errorThreshold) {
this.notify('error', depth);
return 'error';
} else if (depth >= this.warningThreshold) {
this.notify('warning', depth);
return 'warning';
}
return 'safe';
} catch (e) {
return 'unknown';
}
};
StackOverflowPredictor.prototype.notify = function(type, depth) {
this.callbacks.forEach(function(cb) {
if (cb.type === type) {
cb.callback(depth);
}
});
};
return {
StackTracker: StackTracker,
StackOverflowPredictor: StackOverflowPredictor
};
})();
// 使用示例和测试
console.log('=== 尾调用优化和栈安全技术演示 ===');
// 1. 基础递归优化
setTimeout(function() {
StackOptimization.runRecursionOptimization();
}, 100);
// 2. 蹦床技术
setTimeout(function() {
console.log('\n=== 蹦床框架演示 ===');
var trampolineTools = StackOptimization.createTrampolineFramework();
}, 2000);
// 3. CPS 优化
setTimeout(function() {
StackOptimization.runCPSOptimization();
}, 3000);
// 4. 栈安全工具
setTimeout(function() {
console.log('\n=== 栈安全工具演示 ===');
var stackTools = StackOptimization.createStackSafeRecursion();
}, 4000);
// 5. 栈监控工具
setTimeout(function() {
console.log('\n=== 栈监控演示 ===');
var tracker = new StackMonitor.StackTracker();
var predictor = new StackMonitor.StackOverflowPredictor();
predictor.onWarning(function(depth) {
console.log('栈深度警告:', depth);
});
predictor.onError(function(depth) {
console.log('栈深度错误:', depth);
});
// 测试递归函数的栈使用
function deepRecursion(n) {
if (n <= 0) return n;
var status = predictor.checkStack();
if (status === 'error') {
throw new Error('Stack overflow prevented at depth ' + n);
}
return deepRecursion(n - 1);
}
try {
deepRecursion(1000);
} catch (e) {
console.log('递归被安全终止:', e.message);
}
}, 5000);
console.log('栈溢出防护技术演示开始...');
栈优化最佳实践总结:
核心技术:
适用场景:
性能考虑:
尾调用优化虽然在 ES5 中不被原生支持,但通过这些技术可以有效避免栈溢出,实现安全的深度递归。
How to implement a simple template engine?
How to implement a simple template engine?
考察点:字符串处理和代码生成。
答案:
模板引擎是一种将数据与模板结合生成最终输出的工具,广泛用于前端视图渲染、邮件模板、代码生成等场景。实现一个模板引擎需要掌握字符串解析、词法分析、代码生成等技术。
模板引擎核心概念:
完整的模板引擎实现:
// 完整的模板引擎实现
var TemplateEngine = (function() {
// 1. 基础模板引擎
function SimpleTemplate(template) {
this.template = template;
this.compiled = null;
}
SimpleTemplate.prototype.render = function(data) {
data = data || {};
// 简单的变量替换
return this.template.replace(/\{\{(\w+)\}\}/g, function(match, key) {
return data.hasOwnProperty(key) ? data[key] : '';
});
};
// 2. 高级模板引擎
function AdvancedTemplate(options) {
options = options || {};
this.options = {
openTag: options.openTag || '{{',
closeTag: options.closeTag || '}}',
escapeHtml: options.escapeHtml !== false,
allowCode: options.allowCode || false,
helpers: options.helpers || {}
};
this.cache = {};
}
// 词法分析器
AdvancedTemplate.prototype.tokenize = function(template) {
var tokens = [];
var current = 0;
var openTag = this.options.openTag;
var closeTag = this.options.closeTag;
while (current < template.length) {
var openPos = template.indexOf(openTag, current);
if (openPos === -1) {
// 没有更多标签,剩余都是文本
if (current < template.length) {
tokens.push({
type: 'text',
value: template.slice(current)
});
}
break;
}
// 添加开始标签前的文本
if (openPos > current) {
tokens.push({
type: 'text',
value: template.slice(current, openPos)
});
}
// 查找结束标签
var closePos = template.indexOf(closeTag, openPos + openTag.length);
if (closePos === -1) {
throw new Error('Unclosed template tag at position ' + openPos);
}
// 提取标签内容
var tagContent = template.slice(openPos + openTag.length, closePos).trim();
// 解析标签类型
var token = this.parseTag(tagContent);
tokens.push(token);
current = closePos + closeTag.length;
}
return tokens;
};
// 解析标签内容
AdvancedTemplate.prototype.parseTag = function(content) {
// 条件判断 {{#if condition}}
if (content.match(/^#if\s+(.+)$/)) {
return {
type: 'if',
condition: RegExp.$1.trim()
};
}
// 结束条件 {{/if}}
if (content === '/if') {
return { type: 'endif' };
}
// 否则条件 {{#else}}
if (content === '#else') {
return { type: 'else' };
}
// 循环开始 {{#each items}}
if (content.match(/^#each\s+(.+)$/)) {
return {
type: 'each',
collection: RegExp.$1.trim()
};
}
// 结束循环 {{/each}}
if (content === '/each') {
return { type: 'endeach' };
}
// 包含其他模板 {{>partial}}
if (content.match(/^>\s*(.+)$/)) {
return {
type: 'partial',
name: RegExp.$1.trim()
};
}
// 助手函数调用 {{helper arg1 arg2}}
if (content.match(/^(\w+)\s+(.+)$/)) {
var helperName = RegExp.$1;
var args = RegExp.$2.split(/\s+/);
return {
type: 'helper',
name: helperName,
args: args
};
}
// 原始输出(不转义) {{{variable}}}
if (content.match(/^{(.+)}$/)) {
return {
type: 'raw',
expression: RegExp.$1.trim()
};
}
// 普通变量 {{variable}}
return {
type: 'variable',
expression: content
};
};
// 编译模板
AdvancedTemplate.prototype.compile = function(template) {
if (this.cache[template]) {
return this.cache[template];
}
var tokens = this.tokenize(template);
var ast = this.buildAST(tokens);
var code = this.generateCode(ast);
// 创建模板函数
var templateFn = new Function('data', 'helpers', 'partials', 'engine', code);
this.cache[template] = templateFn;
return templateFn;
};
// 构建抽象语法树
AdvancedTemplate.prototype.buildAST = function(tokens) {
var ast = { type: 'root', children: [] };
var stack = [ast];
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
var current = stack[stack.length - 1];
switch (token.type) {
case 'text':
case 'variable':
case 'raw':
case 'helper':
case 'partial':
current.children.push(token);
break;
case 'if':
var ifNode = {
type: 'if',
condition: token.condition,
then: { type: 'block', children: [] },
else: null
};
current.children.push(ifNode);
stack.push(ifNode.then);
break;
case 'else':
// 回退到 if 节点
stack.pop();
var ifNode = current.children[current.children.length - 1];
if (ifNode.type !== 'if') {
throw new Error('Unexpected else without if');
}
ifNode.else = { type: 'block', children: [] };
stack.push(ifNode.else);
break;
case 'endif':
stack.pop();
break;
case 'each':
var eachNode = {
type: 'each',
collection: token.collection,
body: { type: 'block', children: [] }
};
current.children.push(eachNode);
stack.push(eachNode.body);
break;
case 'endeach':
stack.pop();
break;
}
}
if (stack.length > 1) {
throw new Error('Unclosed template blocks');
}
return ast;
};
// 代码生成
AdvancedTemplate.prototype.generateCode = function(node) {
var self = this;
function generate(node, indent) {
indent = indent || 0;
var spaces = new Array(indent + 1).join(' ');
switch (node.type) {
case 'root':
case 'block':
var code = spaces + 'var __output = [];\n';
for (var i = 0; i < node.children.length; i++) {
code += generate(node.children[i], indent);
}
if (indent === 0) {
code += spaces + 'return __output.join("");\n';
}
return code;
case 'text':
var escaped = JSON.stringify(node.value);
return spaces + '__output.push(' + escaped + ');\n';
case 'variable':
var expr = self.generateExpression(node.expression);
var output = self.options.escapeHtml ?
'engine.escapeHtml(' + expr + ')' : expr;
return spaces + '__output.push(' + output + ');\n';
case 'raw':
var expr = self.generateExpression(node.expression);
return spaces + '__output.push(' + expr + ');\n';
case 'if':
var condition = self.generateExpression(node.condition);
var code = spaces + 'if (' + condition + ') {\n';
code += generate(node.then, indent + 1);
code += spaces + '}';
if (node.else) {
code += ' else {\n';
code += generate(node.else, indent + 1);
code += spaces + '}';
}
code += '\n';
return code;
case 'each':
var collection = self.generateExpression(node.collection);
var code = spaces + 'var __collection = ' + collection + ';\n';
code += spaces + 'if (__collection && __collection.length) {\n';
code += spaces + ' for (var __i = 0; __i < __collection.length; __i++) {\n';
code += spaces + ' var __item = __collection[__i];\n';
code += spaces + ' var __index = __i;\n';
// 临时添加循环变量到数据上下文
code += spaces + ' var __oldItem = data.item;\n';
code += spaces + ' var __oldIndex = data.index;\n';
code += spaces + ' data.item = __item;\n';
code += spaces + ' data.index = __index;\n';
code += generate(node.body, indent + 2);
// 恢复原始数据上下文
code += spaces + ' data.item = __oldItem;\n';
code += spaces + ' data.index = __oldIndex;\n';
code += spaces + ' }\n';
code += spaces + '}\n';
return code;
case 'helper':
var helperName = node.name;
var args = node.args.map(function(arg) {
return self.generateExpression(arg);
}).join(', ');
var helperCall = 'helpers.' + helperName + '(' + args + ')';
return spaces + '__output.push(' + helperCall + ');\n';
case 'partial':
var partialName = JSON.stringify(node.name);
var partialCall = 'engine.renderPartial(' + partialName + ', data, partials)';
return spaces + '__output.push(' + partialCall + ');\n';
default:
throw new Error('Unknown node type: ' + node.type);
}
}
return generate(node);
};
// 生成表达式代码
AdvancedTemplate.prototype.generateExpression = function(expr) {
// 处理点号属性访问
if (expr.indexOf('.') !== -1) {
var parts = expr.split('.');
var code = 'data';
for (var i = 0; i < parts.length; i++) {
code += ' && data.' + parts[i];
}
code += ' ? ' + expr.replace(/\b(\w+)/g, 'data.$1') + ' : ""';
return '(' + code + ')';
}
// 简单变量
return 'data.' + expr + ' !== undefined ? data.' + expr + ' : ""';
};
// HTML 转义
AdvancedTemplate.prototype.escapeHtml = function(str) {
if (typeof str !== 'string') {
return str;
}
return str.replace(/[&<>"']/g, function(match) {
var escapeMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return escapeMap[match];
});
};
// 渲染部分模板
AdvancedTemplate.prototype.renderPartial = function(name, data, partials) {
if (!partials || !partials[name]) {
return '';
}
var template = partials[name];
var compiled = this.compile(template);
return compiled(data, this.options.helpers, partials, this);
};
// 渲染模板
AdvancedTemplate.prototype.render = function(template, data, partials) {
data = data || {};
partials = partials || {};
var compiled = this.compile(template);
return compiled(data, this.options.helpers, partials, this);
};
return {
SimpleTemplate: SimpleTemplate,
AdvancedTemplate: AdvancedTemplate
};
})();
// 使用示例和测试
console.log('=== 模板引擎演示 ===');
// 1. 基础模板测试
console.log('=== 基础模板引擎 ===');
var simpleTemplate = new TemplateEngine.SimpleTemplate(
'Hello {{name}}! You are {{age}} years old.'
);
var simpleData = { name: 'John', age: 30 };
console.log('简单模板结果:', simpleTemplate.render(simpleData));
// 2. 高级模板测试
console.log('\n=== 高级模板引擎 ===');
var engine = new TemplateEngine.AdvancedTemplate({
helpers: {
uppercase: function(str) {
return String(str).toUpperCase();
},
formatDate: function(date) {
return new Date(date).toLocaleDateString();
},
multiply: function(a, b) {
return (a || 0) * (b || 0);
}
}
});
// 条件渲染测试
var conditionalTemplate = `
<div>
<h1>User Profile</h1>
{{#if user.name}}
<p>Name: {{user.name}}</p>
{{/if}}
{{#if user.isAdmin}}
<p>Role: Administrator</p>
{{#else}}
<p>Role: Regular User</p>
{{/if}}
{{#if user.email}}
<p>Email: {{user.email}}</p>
{{/if}}
</div>
`.trim();
var userData = {
user: {
name: 'Alice Smith',
isAdmin: false,
email: '[email protected]'
}
};
console.log('条件模板结果:');
console.log(engine.render(conditionalTemplate, userData));
// 循环渲染测试
var loopTemplate = `
<ul class="todo-list">
{{#each todos}}
<li class="todo-item">
<span>{{index}}: {{item.title}}</span>
{{#if item.completed}}
<span class="status">✓ Completed</span>
{{#else}}
<span class="status">○ Pending</span>
{{/if}}
</li>
{{/each}}
</ul>
`.trim();
var todoData = {
todos: [
{ title: 'Learn JavaScript', completed: true },
{ title: 'Build a website', completed: false },
{ title: 'Write documentation', completed: false }
]
};
console.log('\n循环模板结果:');
console.log(engine.render(loopTemplate, todoData));
// 助手函数测试
var helperTemplate = `
<div>
<h2>{{uppercase user.name}}</h2>
<p>Joined: {{formatDate user.joinDate}}</p>
<p>Score: {{multiply user.baseScore user.multiplier}}</p>
</div>
`.trim();
var helperData = {
user: {
name: 'bob jones',
joinDate: '2023-01-15',
baseScore: 85,
multiplier: 1.2
}
};
console.log('\n助手函数模板结果:');
console.log(engine.render(helperTemplate, helperData));
// 部分模板测试
var mainTemplate = `
<html>
<head><title>{{title}}</title></head>
<body>
{{>header}}
<main>{{content}}</main>
{{>footer}}
</body>
</html>
`.trim();
var partials = {
header: '<header><h1>{{siteName}}</h1></header>',
footer: '<footer><p>© {{year}} {{siteName}}</p></footer>'
};
var pageData = {
title: 'My Website',
siteName: 'Awesome Site',
content: 'Welcome to our website!',
year: 2023
};
console.log('\n部分模板结果:');
console.log(engine.render(mainTemplate, pageData, partials));
高级模板引擎特性:
// 高级模板引擎特性扩展
var AdvancedTemplateFeatures = (function() {
// 1. 模板继承系统
function TemplateInheritance() {
this.layouts = {};
this.blocks = {};
}
TemplateInheritance.prototype.registerLayout = function(name, template) {
this.layouts[name] = template;
};
TemplateInheritance.prototype.extend = function(layoutName, childTemplate) {
var layout = this.layouts[layoutName];
if (!layout) {
throw new Error('Layout not found: ' + layoutName);
}
// 解析子模板中的 block 定义
var blocks = this.parseBlocks(childTemplate);
// 替换布局中的 block 占位符
return this.replaceBlocks(layout, blocks);
};
TemplateInheritance.prototype.parseBlocks = function(template) {
var blocks = {};
var blockRegex = /\{\{#block\s+(\w+)\}\}([\s\S]*?)\{\{\/block\}\}/g;
var match;
while ((match = blockRegex.exec(template)) !== null) {
blocks[match[1]] = match[2];
}
return blocks;
};
TemplateInheritance.prototype.replaceBlocks = function(layout, blocks) {
return layout.replace(/\{\{#block\s+(\w+)\}\}([\s\S]*?)\{\{\/block\}\}/g,
function(match, blockName, defaultContent) {
return blocks[blockName] || defaultContent;
});
};
// 2. 表达式引擎
function ExpressionEngine() {
this.operators = {
'+': function(a, b) { return a + b; },
'-': function(a, b) { return a - b; },
'*': function(a, b) { return a * b; },
'/': function(a, b) { return a / b; },
'===': function(a, b) { return a === b; },
'!==': function(a, b) { return a !== b; },
'>': function(a, b) { return a > b; },
'<': function(a, b) { return a < b; },
'>=': function(a, b) { return a >= b; },
'<=': function(a, b) { return a <= b; },
'&&': function(a, b) { return a && b; },
'||': function(a, b) { return a || b; }
};
}
ExpressionEngine.prototype.evaluate = function(expression, context) {
// 简化的表达式解析器
// 实际实现需要更复杂的词法和语法分析
// 处理简单的二元表达式
for (var op in this.operators) {
var index = expression.indexOf(' ' + op + ' ');
if (index !== -1) {
var left = expression.substring(0, index).trim();
var right = expression.substring(index + op.length + 2).trim();
var leftValue = this.getValue(left, context);
var rightValue = this.getValue(right, context);
return this.operators[op](leftValue, rightValue);
}
}
// 单一值
return this.getValue(expression, context);
};
ExpressionEngine.prototype.getValue = function(expr, context) {
expr = expr.trim();
// 字符串字面量
if (expr.match(/^["'].*["']$/)) {
return expr.slice(1, -1);
}
// 数字字面量
if (!isNaN(expr)) {
return parseFloat(expr);
}
// 布尔字面量
if (expr === 'true') return true;
if (expr === 'false') return false;
// 变量访问
var parts = expr.split('.');
var value = context;
for (var i = 0; i < parts.length; i++) {
value = value && value[parts[i]];
}
return value;
};
// 3. 模板调试器
function TemplateDebugger() {
this.enabled = false;
this.logs = [];
}
TemplateDebugger.prototype.enable = function() {
this.enabled = true;
};
TemplateDebugger.prototype.disable = function() {
this.enabled = false;
};
TemplateDebugger.prototype.log = function(message, data) {
if (this.enabled) {
this.logs.push({
timestamp: Date.now(),
message: message,
data: data
});
console.log('[Template Debug]', message, data);
}
};
TemplateDebugger.prototype.getLogs = function() {
return this.logs.slice();
};
TemplateDebugger.prototype.clear = function() {
this.logs = [];
};
// 4. 模板性能分析器
function TemplateProfiler() {
this.profiles = {};
}
TemplateProfiler.prototype.start = function(name) {
this.profiles[name] = {
startTime: Date.now(),
endTime: null,
duration: null
};
};
TemplateProfiler.prototype.end = function(name) {
if (this.profiles[name]) {
var profile = this.profiles[name];
profile.endTime = Date.now();
profile.duration = profile.endTime - profile.startTime;
console.log('[Template Profile]', name, 'took', profile.duration, 'ms');
}
};
TemplateProfiler.prototype.getProfile = function(name) {
return this.profiles[name];
};
TemplateProfiler.prototype.getAllProfiles = function() {
return Object.keys(this.profiles).map(function(name) {
return {
name: name,
profile: this.profiles[name]
};
}.bind(this));
};
return {
TemplateInheritance: TemplateInheritance,
ExpressionEngine: ExpressionEngine,
TemplateDebugger: TemplateDebugger,
TemplateProfiler: TemplateProfiler
};
})();
// 高级特性演示
console.log('\n=== 高级模板特性演示 ===');
// 模板继承测试
var inheritance = new AdvancedTemplateFeatures.TemplateInheritance();
inheritance.registerLayout('base', `
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
{{#block styles}}
<link rel="stylesheet" href="default.css">
{{/block}}
</head>
<body>
<header>{{#block header}}Default Header{{/block}}</header>
<main>{{#block content}}Default Content{{/block}}</main>
<footer>{{#block footer}}Default Footer{{/block}}</footer>
{{#block scripts}}
<script src="default.js"></script>
{{/block}}
</body>
</html>
`.trim());
var childTemplate = `
{{#block styles}}
<link rel="stylesheet" href="custom.css">
<link rel="stylesheet" href="page-specific.css">
{{/block}}
{{#block header}}
<h1>Welcome to My Site</h1>
<nav>Navigation here</nav>
{{/block}}
{{#block content}}
<h2>Page Content</h2>
<p>This is the main content of the page.</p>
{{/block}}
`;
console.log('模板继承结果:');
console.log(inheritance.extend('base', childTemplate));
// 表达式引擎测试
var expressionEngine = new AdvancedTemplateFeatures.ExpressionEngine();
console.log('\n表达式计算测试:');
var context = { a: 10, b: 5, user: { age: 25 } };
console.log('a + b =', expressionEngine.evaluate('a + b', context));
console.log('a > b =', expressionEngine.evaluate('a > b', context));
console.log('user.age >= 18 =', expressionEngine.evaluate('user.age >= 18', context));
console.log('\n模板引擎实现完成!');
模板引擎总结:
核心功能:
高级特性:
应用场景:
性能优化:
这个模板引擎实现涵盖了现代模板引擎的核心特性,可以作为学习模板引擎原理和实现自定义模板系统的基础。
What is a sandbox environment? How to implement a JavaScript sandbox?
What is a sandbox environment? How to implement a JavaScript sandbox?
考察点:安全编程和代码隔离。
答案:
JavaScript 沙箱是一种安全机制,用于在受限环境中执行不受信任的代码,防止恶意代码访问宿主环境的敏感资源。在 ES5 中,我们可以通过多种技术实现不同安全级别的代码沙箱。
沙箱环境核心概念:
完整的 JavaScript 沙箱实现:
// JavaScript 沙箱实现
var JavaScriptSandbox = (function() {
// 1. 基础沙箱 - 作用域隔离
function BasicSandbox() {
this.globalContext = {};
this.whitelistedGlobals = ['console', 'Math', 'Date', 'JSON', 'parseInt', 'parseFloat', 'isNaN', 'isFinite'];
}
BasicSandbox.prototype.createContext = function() {
var context = {};
// 添加白名单中的全局对象
for (var i = 0; i < this.whitelistedGlobals.length; i++) {
var name = this.whitelistedGlobals[i];
if (typeof window[name] !== 'undefined') {
context[name] = window[name];
}
}
// 添加安全的 console 实现
context.console = {
log: function() {
console.log.apply(console, ['[Sandbox]'].concat(Array.prototype.slice.call(arguments)));
},
warn: function() {
console.warn.apply(console, ['[Sandbox]'].concat(Array.prototype.slice.call(arguments)));
},
error: function() {
console.error.apply(console, ['[Sandbox]'].concat(Array.prototype.slice.call(arguments)));
}
};
return context;
};
BasicSandbox.prototype.execute = function(code, context) {
context = context || this.createContext();
try {
// 使用 with 语句创建作用域(虽然不推荐,但在沙箱中可用)
var func = new Function('context', 'with(context) { return (function() { ' + code + ' })(); }');
return func(context);
} catch (error) {
console.error('沙箱执行错误:', error.message);
return { error: error.message };
}
};
// 2. 高级沙箱 - 完整的安全控制
function AdvancedSandbox(options) {
options = options || {};
this.maxExecutionTime = options.maxExecutionTime || 5000; // 5秒
this.maxMemoryUsage = options.maxMemoryUsage || 10 * 1024 * 1024; // 10MB
this.allowedAPIs = options.allowedAPIs || [];
this.blockedPatterns = options.blockedPatterns || [
/eval/g,
/Function/g,
/setTimeout/g,
/setInterval/g,
/XMLHttpRequest/g,
/fetch/g,
/import/g,
/require/g
];
this.executionCount = 0;
this.memoryUsage = 0;
}
AdvancedSandbox.prototype.validateCode = function(code) {
// 检查危险模式
for (var i = 0; i < this.blockedPatterns.length; i++) {
if (this.blockedPatterns[i].test(code)) {
throw new Error('代码包含被禁止的模式: ' + this.blockedPatterns[i]);
}
}
// 检查代码长度
if (code.length > 50000) {
throw new Error('代码长度超出限制');
}
return true;
};
AdvancedSandbox.prototype.createSecureContext = function() {
var context = {
// 数学函数
Math: {
abs: Math.abs,
ceil: Math.ceil,
floor: Math.floor,
max: Math.max,
min: Math.min,
pow: Math.pow,
random: Math.random,
round: Math.round,
sqrt: Math.sqrt
},
// 类型转换
parseInt: parseInt,
parseFloat: parseFloat,
isNaN: isNaN,
isFinite: isFinite,
// JSON 处理
JSON: {
parse: JSON.parse,
stringify: JSON.stringify
},
// 安全的控制台
console: {
log: function() {
console.log.apply(console, ['[Secure Sandbox]'].concat(Array.prototype.slice.call(arguments)));
}
},
// 受限的数组和对象方法
Array: function() {
return Array.prototype.slice.call(arguments);
},
Object: {
keys: Object.keys,
hasOwnProperty: function(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
}
};
// 添加自定义允许的API
for (var i = 0; i < this.allowedAPIs.length; i++) {
var api = this.allowedAPIs[i];
if (typeof window[api.name] !== 'undefined') {
context[api.name] = api.implementation || window[api.name];
}
}
return context;
};
AdvancedSandbox.prototype.executeWithTimeout = function(code, context, timeout) {
var self = this;
var startTime = Date.now();
var isCompleted = false;
var result = null;
var error = null;
// 创建执行函数
var executeCode = function() {
try {
var func = new Function(
'context',
'var window = undefined; var document = undefined; var global = undefined; ' +
'with(context) { return (function() { "use strict"; ' + code + ' })(); }'
);
result = func(context);
} catch (e) {
error = e;
}
isCompleted = true;
};
// 异步执行以支持超时
setTimeout(executeCode, 0);
// 等待执行完成或超时
var checkComplete = function() {
if (isCompleted) {
if (error) throw error;
return result;
}
if (Date.now() - startTime > timeout) {
throw new Error('代码执行超时');
}
// 继续等待
setTimeout(checkComplete, 10);
};
return checkComplete();
};
AdvancedSandbox.prototype.execute = function(code, customContext) {
this.executionCount++;
try {
// 验证代码
this.validateCode(code);
// 创建安全上下文
var context = customContext || this.createSecureContext();
// 执行代码
var result = this.executeWithTimeout(code, context, this.maxExecutionTime);
return {
success: true,
result: result,
executionTime: Date.now() - Date.now(),
memoryUsage: this.memoryUsage
};
} catch (error) {
return {
success: false,
error: error.message,
executionCount: this.executionCount
};
}
};
// 3. iframe 沙箱 - 基于 DOM 隔离
function IframeSandbox(options) {
options = options || {};
this.iframe = null;
this.allowedOrigins = options.allowedOrigins || [];
this.sandbox = options.sandbox || 'allow-scripts';
this.messageHandlers = {};
}
IframeSandbox.prototype.createIframe = function() {
var iframe = document.createElement('iframe');
// 设置沙箱属性
iframe.sandbox = this.sandbox;
iframe.style.display = 'none';
// 创建安全的HTML内容
var htmlContent = `
<!DOCTYPE html>
<html>
<head>
<title>Sandbox</title>
<meta charset="utf-8">
</head>
<body>
<script>
window.sandboxExecute = function(code, context) {
try {
context = context || {};
// 创建受限的执行环境
var sandbox = {
console: {
log: function() {
parent.postMessage({
type: 'console.log',
args: Array.prototype.slice.call(arguments)
}, '*');
}
},
Math: Math,
JSON: JSON
};
// 合并用户提供的上下文
for (var key in context) {
if (context.hasOwnProperty(key)) {
sandbox[key] = context[key];
}
}
// 执行代码
var func = new Function('sandbox',
'with(sandbox) { return (function() { ' + code + ' })(); }');
var result = func(sandbox);
// 发送结果
parent.postMessage({
type: 'execution.result',
success: true,
result: result
}, '*');
} catch (error) {
parent.postMessage({
type: 'execution.result',
success: false,
error: error.message
}, '*');
}
};
// 监听来自父窗口的消息
window.addEventListener('message', function(event) {
if (event.data.type === 'execute') {
sandboxExecute(event.data.code, event.data.context);
}
});
</script>
</body>
</html>
`;
document.body.appendChild(iframe);
// 设置内容
iframe.contentDocument.open();
iframe.contentDocument.write(htmlContent);
iframe.contentDocument.close();
this.iframe = iframe;
return iframe;
};
IframeSandbox.prototype.execute = function(code, context, callback) {
if (!this.iframe) {
this.createIframe();
}
var self = this;
var executed = false;
// 设置消息监听器
var messageHandler = function(event) {
if (event.source === self.iframe.contentWindow) {
if (event.data.type === 'execution.result') {
executed = true;
window.removeEventListener('message', messageHandler);
if (callback) {
callback(event.data);
}
} else if (event.data.type === 'console.log') {
console.log.apply(console, ['[Iframe Sandbox]'].concat(event.data.args));
}
}
};
window.addEventListener('message', messageHandler);
// 发送执行命令
this.iframe.contentWindow.postMessage({
type: 'execute',
code: code,
context: context || {}
}, '*');
// 超时处理
setTimeout(function() {
if (!executed) {
window.removeEventListener('message', messageHandler);
if (callback) {
callback({
success: false,
error: '执行超时'
});
}
}
}, 5000);
};
IframeSandbox.prototype.destroy = function() {
if (this.iframe && this.iframe.parentNode) {
this.iframe.parentNode.removeChild(this.iframe);
this.iframe = null;
}
};
return {
BasicSandbox: BasicSandbox,
AdvancedSandbox: AdvancedSandbox,
IframeSandbox: IframeSandbox
};
})();
// Web Worker 沙箱实现
var WorkerSandbox = (function() {
function WorkerSandbox() {
this.workers = [];
this.messageId = 0;
this.pendingMessages = {};
}
WorkerSandbox.prototype.createWorker = function() {
// 创建内联 Worker
var workerScript = `
var sandbox = {
console: {
log: function() {
postMessage({
type: 'console.log',
args: Array.prototype.slice.call(arguments)
});
}
},
Math: Math,
JSON: JSON,
parseInt: parseInt,
parseFloat: parseFloat,
isNaN: isNaN,
isFinite: isFinite
};
self.addEventListener('message', function(e) {
var data = e.data;
if (data.type === 'execute') {
try {
var func = new Function('sandbox',
'with(sandbox) { return (function() { "use strict"; ' + data.code + ' })(); }');
var result = func(sandbox);
postMessage({
type: 'result',
messageId: data.messageId,
success: true,
result: result
});
} catch (error) {
postMessage({
type: 'result',
messageId: data.messageId,
success: false,
error: error.message
});
}
}
});
`;
var blob = new Blob([workerScript], { type: 'application/javascript' });
var workerUrl = URL.createObjectURL(blob);
var worker = new Worker(workerUrl);
var self = this;
worker.addEventListener('message', function(e) {
var data = e.data;
if (data.type === 'result' && self.pendingMessages[data.messageId]) {
var callback = self.pendingMessages[data.messageId];
delete self.pendingMessages[data.messageId];
callback(data);
} else if (data.type === 'console.log') {
console.log.apply(console, ['[Worker Sandbox]'].concat(data.args));
}
});
this.workers.push(worker);
return worker;
};
WorkerSandbox.prototype.execute = function(code, callback) {
var worker = this.createWorker();
var messageId = ++this.messageId;
if (callback) {
this.pendingMessages[messageId] = callback;
}
worker.postMessage({
type: 'execute',
messageId: messageId,
code: code
});
// 超时处理
setTimeout(function() {
if (this.pendingMessages[messageId]) {
delete this.pendingMessages[messageId];
if (callback) {
callback({
success: false,
error: '执行超时'
});
}
}
worker.terminate();
}.bind(this), 5000);
};
WorkerSandbox.prototype.destroy = function() {
for (var i = 0; i < this.workers.length; i++) {
this.workers[i].terminate();
}
this.workers = [];
this.pendingMessages = {};
};
return WorkerSandbox;
})();
// 代码分析器 - 安全性检查
var CodeAnalyzer = (function() {
function SecurityAnalyzer() {
this.dangerousPatterns = [
{ pattern: /eval\s*\(/g, risk: 'high', message: '使用了 eval 函数' },
{ pattern: /Function\s*\(/g, risk: 'high', message: '使用了 Function 构造函数' },
{ pattern: /setTimeout\s*\(/g, risk: 'medium', message: '使用了 setTimeout' },
{ pattern: /setInterval\s*\(/g, risk: 'medium', message: '使用了 setInterval' },
{ pattern: /XMLHttpRequest/g, risk: 'medium', message: '使用了 XMLHttpRequest' },
{ pattern: /fetch\s*\(/g, risk: 'medium', message: '使用了 fetch API' },
{ pattern: /document\./g, risk: 'medium', message: '访问了 document 对象' },
{ pattern: /window\./g, risk: 'medium', message: '访问了 window 对象' },
{ pattern: /location\./g, risk: 'high', message: '访问了 location 对象' },
{ pattern: /localStorage/g, risk: 'medium', message: '使用了 localStorage' },
{ pattern: /sessionStorage/g, risk: 'medium', message: '使用了 sessionStorage' },
{ pattern: /cookie/gi, risk: 'medium', message: '访问了 cookie' },
{ pattern: /__proto__/g, risk: 'high', message: '使用了 __proto__' },
{ pattern: /constructor/g, risk: 'medium', message: '访问了 constructor 属性' }
];
}
SecurityAnalyzer.prototype.analyze = function(code) {
var risks = [];
var riskLevel = 'low';
for (var i = 0; i < this.dangerousPatterns.length; i++) {
var pattern = this.dangerousPatterns[i];
var matches = code.match(pattern.pattern);
if (matches) {
risks.push({
pattern: pattern.pattern,
risk: pattern.risk,
message: pattern.message,
matches: matches.length,
locations: this.findPatternLocations(code, pattern.pattern)
});
if (pattern.risk === 'high') {
riskLevel = 'high';
} else if (pattern.risk === 'medium' && riskLevel !== 'high') {
riskLevel = 'medium';
}
}
}
return {
riskLevel: riskLevel,
risks: risks,
safe: risks.length === 0,
score: this.calculateSecurityScore(risks)
};
};
SecurityAnalyzer.prototype.findPatternLocations = function(code, pattern) {
var locations = [];
var lines = code.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var matches = line.match(pattern);
if (matches) {
locations.push({
line: i + 1,
column: line.search(pattern),
content: line.trim()
});
}
}
return locations;
};
SecurityAnalyzer.prototype.calculateSecurityScore = function(risks) {
var score = 100;
for (var i = 0; i < risks.length; i++) {
var risk = risks[i];
var penalty = 0;
switch (risk.risk) {
case 'high':
penalty = 30;
break;
case 'medium':
penalty = 15;
break;
case 'low':
penalty = 5;
break;
}
score -= penalty * risk.matches;
}
return Math.max(0, score);
};
return {
SecurityAnalyzer: SecurityAnalyzer
};
})();
// 使用示例和测试
console.log('=== JavaScript 沙箱演示 ===');
// 1. 基础沙箱测试
console.log('=== 基础沙箱测试 ===');
var basicSandbox = new JavaScriptSandbox.BasicSandbox();
var safeCode = 'console.log("Hello from sandbox!"); Math.random() * 100;';
console.log('安全代码执行结果:');
console.log(basicSandbox.execute(safeCode));
// 2. 高级沙箱测试
console.log('\n=== 高级沙箱测试 ===');
var advancedSandbox = new JavaScriptSandbox.AdvancedSandbox({
maxExecutionTime: 3000,
blockedPatterns: [/eval/g, /Function/g, /setTimeout/g]
});
var complexCode = `
var result = [];
for (var i = 0; i < 10; i++) {
result.push(Math.pow(i, 2));
}
console.log('计算结果:', result);
return result.reduce(function(sum, num) { return sum + num; }, 0);
`;
console.log('复杂代码执行结果:');
console.log(advancedSandbox.execute(complexCode));
// 测试危险代码
var dangerousCode = 'eval("alert(\\"危险代码\\")");';
console.log('危险代码执行结果:');
console.log(advancedSandbox.execute(dangerousCode));
// 3. 代码安全分析
console.log('\n=== 代码安全分析 ===');
var analyzer = new CodeAnalyzer.SecurityAnalyzer();
var testCodes = [
'var x = 5; console.log(x * 2);',
'eval("console.log(\\"hello\\")");',
'window.location.href = "http://malicious.com";',
'document.getElementById("test").innerHTML = userInput;'
];
testCodes.forEach(function(code, index) {
console.log('代码 ' + (index + 1) + ':', code);
var analysis = analyzer.analyze(code);
console.log('安全分析:', analysis);
console.log('---');
});
// 4. iframe 沙箱测试(需要在浏览器环境中运行)
if (typeof document !== 'undefined') {
console.log('\n=== iframe 沙箱测试 ===');
var iframeSandbox = new JavaScriptSandbox.IframeSandbox();
var iframeCode = 'console.log("Hello from iframe!"); return "iframe result";';
iframeSandbox.execute(iframeCode, {}, function(result) {
console.log('iframe 执行结果:', result);
// 清理
setTimeout(function() {
iframeSandbox.destroy();
}, 1000);
});
}
// 5. Worker 沙箱测试(需要支持 Web Workers 的环境)
if (typeof Worker !== 'undefined') {
console.log('\n=== Worker 沙箱测试 ===');
var workerSandbox = new WorkerSandbox();
var workerCode = `
console.log("Hello from worker!");
var sum = 0;
for (var i = 1; i <= 100; i++) {
sum += i;
}
return sum;
`;
workerSandbox.execute(workerCode, function(result) {
console.log('Worker 执行结果:', result);
// 清理
setTimeout(function() {
workerSandbox.destroy();
}, 1000);
});
}
console.log('\nJavaScript 沙箱演示完成!');
沙箱安全策略和最佳实践:
// 沙箱安全策略管理器
var SecurityPolicyManager = (function() {
function PolicyManager() {
this.policies = {};
this.defaultPolicy = {
allowEval: false,
allowFunction: false,
allowDOM: false,
allowNetwork: false,
allowTimers: false,
allowStorage: false,
maxExecutionTime: 5000,
maxMemoryUsage: 10 * 1024 * 1024,
allowedGlobals: ['Math', 'JSON', 'console'],
blockedPatterns: [
/eval/g,
/Function/g,
/setTimeout/g,
/setInterval/g,
/XMLHttpRequest/g,
/fetch/g,
/document/g,
/window/g,
/location/g,
/navigator/g
]
};
}
PolicyManager.prototype.createPolicy = function(name, config) {
this.policies[name] = Object.assign({}, this.defaultPolicy, config);
return this.policies[name];
};
PolicyManager.prototype.getPolicy = function(name) {
return this.policies[name] || this.defaultPolicy;
};
PolicyManager.prototype.validateCode = function(code, policyName) {
var policy = this.getPolicy(policyName);
var violations = [];
// 检查代码长度
if (code.length > 100000) {
violations.push('代码长度超过限制');
}
// 检查被禁模式
for (var i = 0; i < policy.blockedPatterns.length; i++) {
var pattern = policy.blockedPatterns[i];
if (pattern.test(code)) {
violations.push('代码匹配被禁模式: ' + pattern);
}
}
// 检查特定功能
if (!policy.allowEval && /eval\s*\(/.test(code)) {
violations.push('策略禁止使用 eval');
}
if (!policy.allowFunction && /Function\s*\(/.test(code)) {
violations.push('策略禁止使用 Function 构造函数');
}
if (!policy.allowDOM && /document\./.test(code)) {
violations.push('策略禁止访问 DOM');
}
return {
valid: violations.length === 0,
violations: violations
};
};
// 资源监控器
function ResourceMonitor() {
this.startTime = 0;
this.startMemory = 0;
this.limits = {
maxTime: 5000,
maxMemory: 10 * 1024 * 1024
};
}
ResourceMonitor.prototype.start = function() {
this.startTime = Date.now();
if (window.performance && window.performance.memory) {
this.startMemory = window.performance.memory.usedJSHeapSize;
}
};
ResourceMonitor.prototype.check = function() {
var currentTime = Date.now();
var elapsedTime = currentTime - this.startTime;
if (elapsedTime > this.limits.maxTime) {
throw new Error('执行时间超过限制: ' + elapsedTime + 'ms');
}
if (window.performance && window.performance.memory) {
var currentMemory = window.performance.memory.usedJSHeapSize;
var memoryDiff = currentMemory - this.startMemory;
if (memoryDiff > this.limits.maxMemory) {
throw new Error('内存使用超过限制: ' + memoryDiff + ' bytes');
}
}
return {
elapsedTime: elapsedTime,
memoryUsage: this.startMemory ? currentMemory - this.startMemory : 0
};
};
return {
PolicyManager: PolicyManager,
ResourceMonitor: ResourceMonitor
};
})();
// 使用示例
console.log('=== 安全策略管理演示 ===');
var policyManager = new SecurityPolicyManager.PolicyManager();
// 创建不同级别的安全策略
policyManager.createPolicy('strict', {
allowEval: false,
allowFunction: false,
allowDOM: false,
allowNetwork: false,
allowTimers: false,
maxExecutionTime: 1000
});
policyManager.createPolicy('moderate', {
allowEval: false,
allowFunction: false,
allowDOM: true,
allowNetwork: false,
allowTimers: true,
maxExecutionTime: 3000
});
policyManager.createPolicy('permissive', {
allowEval: true,
allowFunction: true,
allowDOM: true,
allowNetwork: true,
allowTimers: true,
maxExecutionTime: 10000
});
// 测试代码验证
var testCodes = [
'console.log("安全代码");',
'eval("console.log(\\"eval代码\\")");',
'document.getElementById("test");',
'setTimeout(function() {}, 1000);'
];
['strict', 'moderate', 'permissive'].forEach(function(policyName) {
console.log('\n策略:', policyName);
testCodes.forEach(function(code, index) {
var validation = policyManager.validateCode(code, policyName);
console.log('代码 ' + (index + 1) + ':', validation.valid ? 'PASS' : 'FAIL');
if (!validation.valid) {
console.log(' 违规:', validation.violations);
}
});
});
console.log('\n沙箱安全策略演示完成!');
JavaScript 沙箱总结:
核心技术:
实现方式:
安全级别:
应用场景:
安全考虑:
沙箱技术是现代 Web 应用安全的重要组成部分,为执行不受信任的代码提供了多层防护机制。
How to detect and handle circular references?
How to detect and handle circular references?
考察点:复杂数据结构处理和算法设计。
答案:
循环引用是指对象之间相互引用形成闭环的情况,这可能导致内存泄漏、JSON 序列化错误和无限递归等问题。在 ES5 中,我们需要实现专门的检测和处理机制来解决这些问题。
循环引用的常见场景:
完整的循环引用检测和处理实现:
// 循环引用检测器
var CircularReferenceDetector = (function() {
// 1. 基础循环引用检测
function BasicDetector() {
this.visited = [];
this.path = [];
}
BasicDetector.prototype.detect = function(obj, current) {
current = current || 'root';
// 检查基础类型
if (obj === null || typeof obj !== 'object') {
return false;
}
// 检查是否已访问过此对象
for (var i = 0; i < this.visited.length; i++) {
if (this.visited[i] === obj) {
return {
circular: true,
path: this.path.concat([current]),
target: current,
source: this.path[i] || 'root'
};
}
}
// 添加到访问列表
this.visited.push(obj);
this.path.push(current);
// 递归检查属性
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var result = this.detect(obj[key], key);
if (result) {
return result;
}
}
}
// 回溯
this.visited.pop();
this.path.pop();
return false;
};
BasicDetector.prototype.reset = function() {
this.visited = [];
this.path = [];
};
// 2. 高级循环引用分析器
function AdvancedAnalyzer() {
this.visitMap = new WeakMap ? new WeakMap() : {};
this.circularPaths = [];
this.statistics = {
totalNodes: 0,
circularNodes: 0,
maxDepth: 0,
circularCount: 0
};
}
AdvancedAnalyzer.prototype.analyze = function(obj, options) {
options = options || {};
this.maxDepth = options.maxDepth || 100;
this.ignoreFunctions = options.ignoreFunctions !== false;
this.ignoreArrays = options.ignoreArrays === true;
this.reset();
var result = this.analyzeRecursive(obj, 'root', [], 0);
return {
hasCircular: this.circularPaths.length > 0,
circularPaths: this.circularPaths,
statistics: this.statistics,
details: result
};
};
AdvancedAnalyzer.prototype.analyzeRecursive = function(obj, path, visited, depth) {
// 更新统计信息
this.statistics.totalNodes++;
this.statistics.maxDepth = Math.max(this.statistics.maxDepth, depth);
// 检查深度限制
if (depth > this.maxDepth) {
return { type: 'max-depth-exceeded', path: path };
}
// 检查基础类型
if (obj === null || obj === undefined) {
return { type: 'primitive', value: obj, path: path };
}
if (typeof obj !== 'object') {
return { type: 'primitive', value: typeof obj, path: path };
}
// 跳过函数
if (this.ignoreFunctions && typeof obj === 'function') {
return { type: 'function', path: path };
}
// 跳过数组
if (this.ignoreArrays && Array.isArray(obj)) {
return { type: 'array', length: obj.length, path: path };
}
// 检查循环引用
for (var i = 0; i < visited.length; i++) {
if (visited[i] === obj) {
var circularPath = {
current: path,
target: visited[i]._path || 'unknown',
depth: depth,
visitedIndex: i
};
this.circularPaths.push(circularPath);
this.statistics.circularNodes++;
this.statistics.circularCount++;
return {
type: 'circular',
path: path,
targetPath: circularPath.target,
circular: true
};
}
}
// 标记当前对象
obj._path = path;
visited.push(obj);
var result = {
type: 'object',
path: path,
properties: {},
isArray: Array.isArray(obj),
constructor: obj.constructor ? obj.constructor.name : 'Object'
};
// 遍历属性
for (var key in obj) {
if (obj.hasOwnProperty(key) && key !== '_path') {
var propertyPath = path + '.' + key;
result.properties[key] = this.analyzeRecursive(
obj[key],
propertyPath,
visited.slice(), // 创建副本
depth + 1
);
}
}
// 清理标记
delete obj._path;
visited.pop();
return result;
};
AdvancedAnalyzer.prototype.reset = function() {
this.circularPaths = [];
this.statistics = {
totalNodes: 0,
circularNodes: 0,
maxDepth: 0,
circularCount: 0
};
};
// 3. 循环引用处理器
function CircularHandler() {
this.strategies = {
'ignore': this.ignoreStrategy,
'replace': this.replaceStrategy,
'clone': this.cloneStrategy,
'serialize': this.serializeStrategy
};
}
CircularHandler.prototype.handle = function(obj, strategy, options) {
strategy = strategy || 'replace';
options = options || {};
var handler = this.strategies[strategy];
if (!handler) {
throw new Error('未知的处理策略: ' + strategy);
}
return handler.call(this, obj, options);
};
// 忽略策略 - 跳过循环引用的属性
CircularHandler.prototype.ignoreStrategy = function(obj, options) {
var visited = [];
var self = this;
function processObject(current, path) {
if (current === null || typeof current !== 'object') {
return current;
}
// 检查循环引用
if (visited.indexOf(current) !== -1) {
return options.placeholder || '[Circular Reference]';
}
visited.push(current);
var result = Array.isArray(current) ? [] : {};
for (var key in current) {
if (current.hasOwnProperty(key)) {
result[key] = processObject(current[key], path + '.' + key);
}
}
visited.pop();
return result;
}
return processObject(obj, 'root');
};
// 替换策略 - 用引用路径替换循环引用
CircularHandler.prototype.replaceStrategy = function(obj, options) {
var visited = [];
var paths = [];
function processObject(current, path) {
if (current === null || typeof current !== 'object') {
return current;
}
// 检查循环引用
var visitedIndex = visited.indexOf(current);
if (visitedIndex !== -1) {
return {
$ref: paths[visitedIndex],
$circular: true
};
}
visited.push(current);
paths.push(path);
var result = Array.isArray(current) ? [] : {};
for (var key in current) {
if (current.hasOwnProperty(key)) {
result[key] = processObject(current[key], path + '.' + key);
}
}
visited.pop();
paths.pop();
return result;
}
return processObject(obj, 'root');
};
// 克隆策略 - 深拷贝但打断循环引用
CircularHandler.prototype.cloneStrategy = function(obj, options) {
var visited = new WeakMap ? new WeakMap() : {};
var self = this;
function cloneObject(current) {
if (current === null || typeof current !== 'object') {
return current;
}
// 检查是否已经克隆过
if (visited instanceof WeakMap) {
if (visited.has(current)) {
return visited.get(current);
}
} else {
// Fallback for environments without WeakMap
for (var key in visited) {
if (visited[key] === current) {
return visited[key + '_clone'];
}
}
}
var clone = Array.isArray(current) ? [] : {};
// 先设置映射,防止无限递归
if (visited instanceof WeakMap) {
visited.set(current, clone);
} else {
visited[Object.keys(visited).length] = current;
visited[Object.keys(visited).length - 1 + '_clone'] = clone;
}
// 复制属性
for (var prop in current) {
if (current.hasOwnProperty(prop)) {
clone[prop] = cloneObject(current[prop]);
}
}
return clone;
}
return cloneObject(obj);
};
// 序列化策略 - 安全的 JSON 序列化
CircularHandler.prototype.serializeStrategy = function(obj, options) {
var visited = [];
var self = this;
return JSON.stringify(obj, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (visited.indexOf(value) !== -1) {
return options.circularValue || '[Circular]';
}
visited.push(value);
}
return value;
}, options.space || 2);
};
return {
BasicDetector: BasicDetector,
AdvancedAnalyzer: AdvancedAnalyzer,
CircularHandler: CircularHandler
};
})();
// JSON 循环引用安全处理
var SafeJSON = (function() {
function SafeStringifier() {
this.seen = [];
}
SafeStringifier.prototype.stringify = function(obj, replacer, space) {
var self = this;
this.seen = [];
return JSON.stringify(obj, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (self.seen.indexOf(value) !== -1) {
return '[Circular *' + key + ']';
}
self.seen.push(value);
}
return replacer ? replacer(key, value) : value;
}, space);
};
SafeStringifier.prototype.parse = function(text, reviver) {
return JSON.parse(text, function(key, value) {
// 处理循环引用标记
if (typeof value === 'string' && value.match(/^\[Circular \*.*\]$/)) {
return { $circular: value };
}
return reviver ? reviver(key, value) : value;
});
};
// 高级安全序列化
function AdvancedSerializer() {
this.referenceMap = {};
this.referenceCounter = 0;
}
AdvancedSerializer.prototype.serialize = function(obj, options) {
options = options || {};
this.referenceMap = {};
this.referenceCounter = 0;
this.maxDepth = options.maxDepth || 50;
this.includeTypes = options.includeTypes !== false;
var serialized = this.serializeRecursive(obj, 0);
return {
data: serialized,
references: this.referenceMap,
metadata: {
totalReferences: this.referenceCounter,
maxDepth: this.maxDepth,
includeTypes: this.includeTypes
}
};
};
AdvancedSerializer.prototype.serializeRecursive = function(obj, depth) {
// 检查深度限制
if (depth > this.maxDepth) {
return { $error: 'Max depth exceeded' };
}
// 处理基础类型
if (obj === null || obj === undefined) {
return obj;
}
if (typeof obj !== 'object') {
return this.includeTypes ? { $value: obj, $type: typeof obj } : obj;
}
// 处理函数
if (typeof obj === 'function') {
return this.includeTypes ? {
$type: 'function',
$name: obj.name,
$length: obj.length
} : '[Function]';
}
// 检查循环引用
for (var refId in this.referenceMap) {
if (this.referenceMap[refId] === obj) {
return { $ref: parseInt(refId, 10) };
}
}
// 创建新引用
var currentRefId = this.referenceCounter++;
this.referenceMap[currentRefId] = obj;
var result = {
$id: currentRefId
};
if (this.includeTypes) {
result.$type = Array.isArray(obj) ? 'array' : 'object';
if (obj.constructor && obj.constructor.name !== 'Object') {
result.$constructor = obj.constructor.name;
}
}
// 序列化属性
var data = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
data[key] = this.serializeRecursive(obj[key], depth + 1);
}
}
result.$data = data;
return result;
};
AdvancedSerializer.prototype.deserialize = function(serialized) {
if (!serialized || !serialized.data) {
throw new Error('无效的序列化数据');
}
this.objectMap = {};
return this.deserializeRecursive(serialized.data);
};
AdvancedSerializer.prototype.deserializeRecursive = function(data) {
// 处理基础类型
if (data === null || data === undefined) {
return data;
}
if (typeof data !== 'object') {
return data;
}
// 处理类型包装
if (data.$value !== undefined && data.$type) {
return data.$value;
}
// 处理引用
if (data.$ref !== undefined) {
var refId = data.$ref;
if (this.objectMap[refId]) {
return this.objectMap[refId];
} else {
throw new Error('找不到引用 ID: ' + refId);
}
}
// 处理对象
if (data.$id !== undefined && data.$data !== undefined) {
var obj = data.$type === 'array' ? [] : {};
// 先注册对象,防止循环引用问题
this.objectMap[data.$id] = obj;
// 反序列化属性
for (var key in data.$data) {
if (data.$data.hasOwnProperty(key)) {
obj[key] = this.deserializeRecursive(data.$data[key]);
}
}
return obj;
}
// 普通对象递归处理
var result = Array.isArray(data) ? [] : {};
for (var prop in data) {
if (data.hasOwnProperty(prop)) {
result[prop] = this.deserializeRecursive(data[prop]);
}
}
return result;
};
return {
SafeStringifier: SafeStringifier,
AdvancedSerializer: AdvancedSerializer
};
})();
// 优化版循环引用检测器
var OptimizedCircularDetector = (function() {
// 使用 Set 和 Map 优化性能(ES5 环境的兼容实现)
function FastDetector() {
// ES5 环境下的 Set/Map 模拟
this.visitedSet = this.createSet();
this.pathMap = this.createMap();
}
FastDetector.prototype.createSet = function() {
if (typeof Set !== 'undefined') {
return new Set();
}
// ES5 Set 模拟
return {
items: [],
has: function(item) {
return this.items.indexOf(item) !== -1;
},
add: function(item) {
if (!this.has(item)) {
this.items.push(item);
}
},
delete: function(item) {
var index = this.items.indexOf(item);
if (index !== -1) {
this.items.splice(index, 1);
}
},
clear: function() {
this.items = [];
}
};
};
FastDetector.prototype.createMap = function() {
if (typeof Map !== 'undefined') {
return new Map();
}
// ES5 Map 模拟
return {
keys: [],
values: [],
has: function(key) {
return this.keys.indexOf(key) !== -1;
},
get: function(key) {
var index = this.keys.indexOf(key);
return index !== -1 ? this.values[index] : undefined;
},
set: function(key, value) {
var index = this.keys.indexOf(key);
if (index !== -1) {
this.values[index] = value;
} else {
this.keys.push(key);
this.values.push(value);
}
},
delete: function(key) {
var index = this.keys.indexOf(key);
if (index !== -1) {
this.keys.splice(index, 1);
this.values.splice(index, 1);
}
},
clear: function() {
this.keys = [];
this.values = [];
}
};
};
FastDetector.prototype.detectAll = function(obj) {
this.visitedSet.clear();
this.pathMap.clear();
var circularRefs = [];
this.detectRecursive(obj, 'root', circularRefs);
return {
hasCircular: circularRefs.length > 0,
circularReferences: circularRefs,
visitedCount: this.visitedSet.items ? this.visitedSet.items.length : this.visitedSet.size
};
};
FastDetector.prototype.detectRecursive = function(obj, path, circularRefs) {
if (obj === null || typeof obj !== 'object') {
return;
}
// 检查是否已访问
if (this.visitedSet.has(obj)) {
var originalPath = this.pathMap.get(obj);
circularRefs.push({
object: obj,
currentPath: path,
originalPath: originalPath,
type: this.getObjectType(obj)
});
return;
}
// 标记为已访问
this.visitedSet.add(obj);
this.pathMap.set(obj, path);
// 递归检查属性
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
this.detectRecursive(obj[key], path + '.' + key, circularRefs);
}
}
};
FastDetector.prototype.getObjectType = function(obj) {
if (Array.isArray(obj)) return 'Array';
if (obj instanceof Date) return 'Date';
if (obj instanceof RegExp) return 'RegExp';
if (typeof obj === 'function') return 'Function';
return 'Object';
};
// 内存高效的检测器
function MemoryEfficientDetector() {
this.maxDepth = 100;
this.objectCount = 0;
}
MemoryEfficientDetector.prototype.detect = function(obj, maxDepth) {
this.maxDepth = maxDepth || 100;
this.objectCount = 0;
return this.detectWithPath(obj, [], 'root', 0);
};
MemoryEfficientDetector.prototype.detectWithPath = function(obj, ancestors, path, depth) {
// 深度限制
if (depth > this.maxDepth) {
return {
error: 'Max depth exceeded',
path: path,
depth: depth
};
}
// 基础类型检查
if (obj === null || typeof obj !== 'object') {
return null;
}
this.objectCount++;
// 检查祖先链中的循环引用
for (var i = 0; i < ancestors.length; i++) {
if (ancestors[i].obj === obj) {
return {
circular: true,
currentPath: path,
ancestorPath: ancestors[i].path,
depth: depth,
ancestorDepth: ancestors[i].depth
};
}
}
// 添加当前对象到祖先链
var currentAncestor = { obj: obj, path: path, depth: depth };
var newAncestors = ancestors.concat([currentAncestor]);
// 递归检查属性
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
var result = this.detectWithPath(
obj[key],
newAncestors,
path + '.' + key,
depth + 1
);
if (result) {
return result;
}
}
}
return null;
};
return {
FastDetector: FastDetector,
MemoryEfficientDetector: MemoryEfficientDetector
};
})();
// 使用示例和测试
console.log('=== 循环引用检测和处理演示 ===');
// 创建测试对象 - 包含循环引用
var createTestObject = function() {
var obj1 = { name: 'object1', value: 100 };
var obj2 = { name: 'object2', value: 200 };
var obj3 = { name: 'object3', value: 300 };
// 创建循环引用
obj1.ref = obj2;
obj2.ref = obj3;
obj3.ref = obj1; // 循环引用
// 自引用
obj1.self = obj1;
return {
root: obj1,
list: [obj1, obj2, obj3],
map: {
first: obj1,
second: obj2,
third: obj3
}
};
};
// 1. 基础检测器测试
console.log('=== 基础循环引用检测 ===');
var testObj = createTestObject();
var basicDetector = new CircularReferenceDetector.BasicDetector();
console.log('检测结果:');
var detection = basicDetector.detect(testObj);
console.log(detection);
// 2. 高级分析器测试
console.log('\n=== 高级循环引用分析 ===');
var analyzer = new CircularReferenceDetector.AdvancedAnalyzer();
var analysis = analyzer.analyze(testObj, {
maxDepth: 10,
ignoreFunctions: true
});
console.log('分析结果:');
console.log('是否存在循环引用:', analysis.hasCircular);
console.log('循环路径数量:', analysis.circularPaths.length);
console.log('统计信息:', analysis.statistics);
if (analysis.hasCircular) {
console.log('循环路径详情:');
analysis.circularPaths.forEach(function(path, index) {
console.log('路径 ' + (index + 1) + ':', path.current, '->', path.target);
});
}
// 3. 循环引用处理测试
console.log('\n=== 循环引用处理策略 ===');
var handler = new CircularReferenceDetector.CircularHandler();
// 测试不同策略
var strategies = ['ignore', 'replace', 'clone', 'serialize'];
strategies.forEach(function(strategy) {
console.log('\n--- ' + strategy.toUpperCase() + ' 策略 ---');
try {
var result = handler.handle(testObj, strategy, {
placeholder: '[CIRCULAR]',
circularValue: '[Circular Reference]',
space: 2
});
if (strategy === 'serialize') {
console.log('序列化结果:');
console.log(result);
} else {
console.log('处理结果类型:', typeof result);
console.log('是否为对象:', typeof result === 'object');
// 验证处理后的对象没有循环引用
var detector = new CircularReferenceDetector.BasicDetector();
var hasCircular = detector.detect(result);
console.log('处理后是否还有循环引用:', !!hasCircular);
}
} catch (error) {
console.log('策略执行错误:', error.message);
}
});
// 4. 安全 JSON 处理测试
console.log('\n=== 安全 JSON 处理 ===');
var safeJSON = new SafeJSON.SafeStringifier();
console.log('--- 安全字符串化 ---');
try {
var jsonString = safeJSON.stringify(testObj, null, 2);
console.log('JSON 字符串化成功');
console.log('长度:', jsonString.length);
console.log('前 200 字符:', jsonString.substring(0, 200));
} catch (error) {
console.log('JSON 字符串化失败:', error.message);
}
// 5. 高级序列化测试
console.log('\n=== 高级序列化测试 ===');
var serializer = new SafeJSON.AdvancedSerializer();
console.log('--- 序列化 ---');
var serialized = serializer.serialize(testObj, {
maxDepth: 20,
includeTypes: true
});
console.log('序列化成功');
console.log('引用数量:', serialized.metadata.totalReferences);
console.log('数据结构:', typeof serialized.data);
console.log('--- 反序列化 ---');
try {
var deserialized = serializer.deserialize(serialized);
console.log('反序列化成功');
console.log('对象类型:', typeof deserialized);
// 验证反序列化后的循环引用
if (deserialized.root && deserialized.root.ref && deserialized.root.ref.ref) {
console.log('循环引用恢复正确:',
deserialized.root === deserialized.root.ref.ref.ref);
}
} catch (error) {
console.log('反序列化失败:', error.message);
}
// 6. 优化版检测器测试
console.log('\n=== 优化版循环引用检测 ===');
var fastDetector = new OptimizedCircularDetector.FastDetector();
var memoryDetector = new OptimizedCircularDetector.MemoryEfficientDetector();
// 快速检测器测试
console.log('--- 快速检测器 ---');
var fastResult = fastDetector.detectAll(testObj);
console.log('检测结果:', fastResult);
// 内存高效检测器测试
console.log('--- 内存高效检测器 ---');
var memoryResult = memoryDetector.detect(testObj, 50);
console.log('检测结果:', memoryResult);
// 7. 性能测试
console.log('\n=== 性能测试 ===');
function createDeepObject(depth, circularAt) {
var root = { level: 0 };
var current = root;
for (var i = 1; i < depth; i++) {
current.child = { level: i, parent: current };
current = current.child;
// 在指定深度创建循环引用
if (i === circularAt) {
current.circular = root;
}
}
return root;
}
var deepObj = createDeepObject(100, 50);
var startTime = Date.now();
var deepAnalysis = analyzer.analyze(deepObj, { maxDepth: 150 });
var endTime = Date.now();
console.log('深层对象分析:');
console.log('深度:', deepAnalysis.statistics.maxDepth);
console.log('节点数:', deepAnalysis.statistics.totalNodes);
console.log('循环引用数:', deepAnalysis.statistics.circularCount);
console.log('分析时间:', (endTime - startTime) + 'ms');
console.log('\n循环引用检测和处理演示完成!');
循环引用处理总结:
检测方法:
处理策略:
性能优化:
应用场景:
循环引用检测和处理是 JavaScript 开发中的重要技能,正确处理可以避免内存泄漏和程序崩溃。
What is the Proxy pattern? How to simulate it in ES5?
What is the Proxy pattern? How to simulate it in ES5?
考察点:设计模式和对象行为控制。
答案:
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。在 ES5 中,虽然没有内置的 Proxy 对象,但我们可以通过多种技术手段模拟实现代理功能。
代理模式核心概念:
完整的 ES5 代理模式实现:
// ES5 代理模式实现
var ProxyPattern = (function() {
// 1. 基础属性代理
function PropertyProxy(target, options) {
this.target = target;
this.options = options || {};
this.interceptors = {
get: [],
set: [],
has: [],
delete: []
};
this.proxyObject = this.createProxy();
}
PropertyProxy.prototype.createProxy = function() {
var self = this;
var proxy = {};
// 复制目标对象的属性
for (var key in this.target) {
if (this.target.hasOwnProperty(key)) {
this.defineProxyProperty(proxy, key);
}
}
// 添加代理方法
proxy.__getTarget = function() {
return self.target;
};
proxy.__addInterceptor = function(type, handler) {
if (self.interceptors[type]) {
self.interceptors[type].push(handler);
}
};
proxy.__removeInterceptor = function(type, handler) {
if (self.interceptors[type]) {
var index = self.interceptors[type].indexOf(handler);
if (index !== -1) {
self.interceptors[type].splice(index, 1);
}
}
};
return proxy;
};
PropertyProxy.prototype.defineProxyProperty = function(proxy, key) {
var self = this;
Object.defineProperty(proxy, key, {
get: function() {
// 执行 get 拦截器
for (var i = 0; i < self.interceptors.get.length; i++) {
var handler = self.interceptors.get[i];
var result = handler(self.target, key);
if (result !== undefined) {
return result;
}
}
return self.target[key];
},
set: function(value) {
// 执行 set 拦截器
var allowed = true;
for (var i = 0; i < self.interceptors.set.length; i++) {
var handler = self.interceptors.set[i];
var result = handler(self.target, key, value);
if (result === false) {
allowed = false;
break;
}
}
if (allowed) {
self.target[key] = value;
}
},
enumerable: true,
configurable: true
});
};
PropertyProxy.prototype.addProperty = function(key, value) {
if (!(key in this.target)) {
this.target[key] = value;
this.defineProxyProperty(this.proxyObject, key);
}
};
PropertyProxy.prototype.removeProperty = function(key) {
delete this.target[key];
delete this.proxyObject[key];
};
PropertyProxy.prototype.getProxy = function() {
return this.proxyObject;
};
// 2. 方法代理
function MethodProxy(target, options) {
this.target = target;
this.options = options || {};
this.methodInterceptors = {};
this.proxyObject = this.createMethodProxy();
}
MethodProxy.prototype.createMethodProxy = function() {
var self = this;
var proxy = Object.create(this.target);
// 代理所有方法
for (var key in this.target) {
if (typeof this.target[key] === 'function') {
this.proxyMethod(proxy, key);
} else {
proxy[key] = this.target[key];
}
}
// 添加代理控制方法
proxy.__interceptMethod = function(methodName, interceptor) {
if (!self.methodInterceptors[methodName]) {
self.methodInterceptors[methodName] = [];
}
self.methodInterceptors[methodName].push(interceptor);
};
proxy.__getTarget = function() {
return self.target;
};
return proxy;
};
MethodProxy.prototype.proxyMethod = function(proxy, methodName) {
var self = this;
var originalMethod = this.target[methodName];
proxy[methodName] = function() {
var args = Array.prototype.slice.call(arguments);
var context = this;
// 执行前置拦截器
var interceptors = self.methodInterceptors[methodName] || [];
for (var i = 0; i < interceptors.length; i++) {
var interceptor = interceptors[i];
if (interceptor.before) {
var beforeResult = interceptor.before.call(context, args, methodName);
if (beforeResult === false) {
return; // 阻止方法执行
}
if (beforeResult && beforeResult.args) {
args = beforeResult.args; // 修改参数
}
}
}
// 执行原始方法
var result;
var error = null;
try {
result = originalMethod.apply(self.target, args);
} catch (e) {
error = e;
}
// 执行后置拦截器
for (var j = 0; j < interceptors.length; j++) {
var postInterceptor = interceptors[j];
if (postInterceptor.after) {
var afterResult = postInterceptor.after.call(context, result, args, methodName, error);
if (afterResult !== undefined) {
result = afterResult; // 修改返回值
}
}
}
if (error) {
throw error;
}
return result;
};
};
MethodProxy.prototype.getProxy = function() {
return this.proxyObject;
};
// 3. 虚拟代理 - 延迟加载
function VirtualProxy(factory, options) {
this.factory = factory;
this.options = options || {};
this.realObject = null;
this.loading = false;
this.proxyObject = this.createVirtualProxy();
}
VirtualProxy.prototype.createVirtualProxy = function() {
var self = this;
return {
__isProxy: true,
__isLoaded: function() {
return self.realObject !== null;
},
__load: function(callback) {
if (self.realObject) {
if (callback) callback(null, self.realObject);
return self.realObject;
}
if (self.loading) {
// 正在加载中,等待
setTimeout(function() {
self.__load(callback);
}, 10);
return;
}
self.loading = true;
try {
var result = self.factory();
// 处理异步工厂
if (result && typeof result.then === 'function') {
result.then(function(obj) {
self.realObject = obj;
self.loading = false;
if (callback) callback(null, obj);
}).catch(function(error) {
self.loading = false;
if (callback) callback(error);
});
} else {
self.realObject = result;
self.loading = false;
if (callback) callback(null, result);
return result;
}
} catch (error) {
self.loading = false;
if (callback) callback(error);
throw error;
}
},
__getRealObject: function() {
if (!self.realObject) {
this.__load();
}
return self.realObject;
}
};
};
VirtualProxy.prototype.getProxy = function() {
return this.proxyObject;
};
// 4. 保护代理 - 访问控制
function ProtectionProxy(target, accessControl) {
this.target = target;
this.accessControl = accessControl || {};
this.proxyObject = this.createProtectionProxy();
}
ProtectionProxy.prototype.createProtectionProxy = function() {
var self = this;
var proxy = {};
// 复制属性和方法,添加访问控制
for (var key in this.target) {
this.createProtectedAccess(proxy, key);
}
// 添加权限检查方法
proxy.__checkPermission = function(action, key, context) {
return self.checkPermission(action, key, context);
};
proxy.__setAccessControl = function(key, permissions) {
self.accessControl[key] = permissions;
};
return proxy;
};
ProtectionProxy.prototype.createProtectedAccess = function(proxy, key) {
var self = this;
var target = this.target;
if (typeof target[key] === 'function') {
// 方法代理
proxy[key] = function() {
if (!self.checkPermission('execute', key)) {
throw new Error('权限不足,无法执行方法: ' + key);
}
return target[key].apply(target, arguments);
};
} else {
// 属性代理
Object.defineProperty(proxy, key, {
get: function() {
if (!self.checkPermission('read', key)) {
throw new Error('权限不足,无法读取属性: ' + key);
}
return target[key];
},
set: function(value) {
if (!self.checkPermission('write', key)) {
throw new Error('权限不足,无法写入属性: ' + key);
}
target[key] = value;
},
enumerable: true,
configurable: true
});
}
};
ProtectionProxy.prototype.checkPermission = function(action, key, context) {
var permissions = this.accessControl[key];
if (!permissions) {
return true; // 默认允许
}
if (typeof permissions === 'function') {
return permissions(action, key, context);
}
if (Array.isArray(permissions)) {
return permissions.indexOf(action) !== -1;
}
if (typeof permissions === 'object') {
return permissions[action] === true;
}
return false;
};
ProtectionProxy.prototype.getProxy = function() {
return this.proxyObject;
};
// 5. 缓存代理
function CacheProxy(target, options) {
this.target = target;
this.options = options || {};
this.cache = {};
this.maxCacheSize = this.options.maxCacheSize || 100;
this.ttl = this.options.ttl || 0; // Time to live in milliseconds
this.proxyObject = this.createCacheProxy();
}
CacheProxy.prototype.createCacheProxy = function() {
var self = this;
var proxy = {};
// 代理所有方法
for (var key in this.target) {
if (typeof this.target[key] === 'function') {
this.createCachedMethod(proxy, key);
} else {
proxy[key] = this.target[key];
}
}
// 缓存管理方法
proxy.__clearCache = function(methodName) {
if (methodName) {
delete self.cache[methodName];
} else {
self.cache = {};
}
};
proxy.__getCacheStats = function() {
var stats = { total: 0, methods: {} };
for (var method in self.cache) {
var methodCache = self.cache[method];
var count = Object.keys(methodCache).length;
stats.methods[method] = count;
stats.total += count;
}
return stats;
};
return proxy;
};
CacheProxy.prototype.createCachedMethod = function(proxy, methodName) {
var self = this;
var originalMethod = this.target[methodName];
proxy[methodName] = function() {
var args = Array.prototype.slice.call(arguments);
var cacheKey = self.generateCacheKey(methodName, args);
// 检查缓存
if (self.cache[methodName] && self.cache[methodName][cacheKey]) {
var cacheEntry = self.cache[methodName][cacheKey];
// 检查 TTL
if (self.ttl === 0 || (Date.now() - cacheEntry.timestamp) < self.ttl) {
return cacheEntry.value;
} else {
// 缓存过期,删除
delete self.cache[methodName][cacheKey];
}
}
// 执行方法
var result = originalMethod.apply(self.target, args);
// 缓存结果
self.cacheResult(methodName, cacheKey, result);
return result;
};
};
CacheProxy.prototype.generateCacheKey = function(methodName, args) {
try {
return methodName + ':' + JSON.stringify(args);
} catch (error) {
// 如果无法序列化参数,使用简单的 key
return methodName + ':' + args.length + ':' + typeof args[0];
}
};
CacheProxy.prototype.cacheResult = function(methodName, cacheKey, result) {
if (!this.cache[methodName]) {
this.cache[methodName] = {};
}
// 检查缓存大小限制
var methodCache = this.cache[methodName];
var cacheKeys = Object.keys(methodCache);
if (cacheKeys.length >= this.maxCacheSize) {
// 删除最旧的缓存项 (简单的 FIFO 策略)
var oldestKey = cacheKeys[0];
delete methodCache[oldestKey];
}
methodCache[cacheKey] = {
value: result,
timestamp: Date.now()
};
};
CacheProxy.prototype.getProxy = function() {
return this.proxyObject;
};
return {
PropertyProxy: PropertyProxy,
MethodProxy: MethodProxy,
VirtualProxy: VirtualProxy,
ProtectionProxy: ProtectionProxy,
CacheProxy: CacheProxy
};
})();
// 代理工厂 - 统一创建接口
var ProxyFactory = (function() {
function ProxyFactory() {
this.proxyTypes = {
'property': ProxyPattern.PropertyProxy,
'method': ProxyPattern.MethodProxy,
'virtual': ProxyPattern.VirtualProxy,
'protection': ProxyPattern.ProtectionProxy,
'cache': ProxyPattern.CacheProxy
};
}
ProxyFactory.prototype.createProxy = function(type, target, options) {
var ProxyClass = this.proxyTypes[type];
if (!ProxyClass) {
throw new Error('未知的代理类型: ' + type);
}
var proxy = new ProxyClass(target, options);
return proxy.getProxy();
};
ProxyFactory.prototype.createCompositeProxy = function(target, configs) {
var currentProxy = target;
for (var i = 0; i < configs.length; i++) {
var config = configs[i];
var ProxyClass = this.proxyTypes[config.type];
if (ProxyClass) {
var proxyInstance = new ProxyClass(currentProxy, config.options);
currentProxy = proxyInstance.getProxy();
}
}
return currentProxy;
};
return ProxyFactory;
})();
// 使用示例和测试
console.log('=== ES5 代理模式演示 ===');
// 1. 属性代理测试
console.log('=== 属性代理测试 ===');
var originalObject = {
name: 'Original Object',
value: 100,
secret: 'top secret'
};
var propertyProxy = new ProxyPattern.PropertyProxy(originalObject);
var proxyObj = propertyProxy.getProxy();
// 添加拦截器
proxyObj.__addInterceptor('get', function(target, key) {
console.log('访问属性:', key);
if (key === 'secret') {
return '[HIDDEN]';
}
});
proxyObj.__addInterceptor('set', function(target, key, value) {
console.log('设置属性:', key, '=', value);
if (key === 'secret') {
console.log('禁止修改 secret 属性');
return false; // 阻止设置
}
});
console.log('读取 name:', proxyObj.name);
console.log('读取 secret:', proxyObj.secret);
proxyObj.value = 200;
proxyObj.secret = 'new secret'; // 应该被阻止
console.log('修改后的 value:', proxyObj.value);
console.log('原始对象的 value:', originalObject.value);
// 2. 方法代理测试
console.log('\n=== 方法代理测试 ===');
function Calculator() {
this.result = 0;
}
Calculator.prototype.add = function(num) {
this.result += num;
return this;
};
Calculator.prototype.multiply = function(num) {
this.result *= num;
return this;
};
Calculator.prototype.getResult = function() {
return this.result;
};
var calculator = new Calculator();
var methodProxy = new ProxyPattern.MethodProxy(calculator);
var proxyCalculator = methodProxy.getProxy();
// 添加方法拦截器
proxyCalculator.__interceptMethod('add', {
before: function(args, methodName) {
console.log('准备执行 add,参数:', args);
if (args[0] < 0) {
console.log('不允许添加负数');
return false; // 阻止执行
}
},
after: function(result, args, methodName) {
console.log('add 执行完成,结果:', result.getResult());
}
});
proxyCalculator.__interceptMethod('multiply', {
before: function(args, methodName) {
console.log('准备执行 multiply,参数:', args);
},
after: function(result, args, methodName) {
console.log('multiply 执行完成');
}
});
proxyCalculator.add(10).multiply(2);
console.log('最终结果:', proxyCalculator.getResult());
proxyCalculator.add(-5); // 应该被阻止
// 3. 虚拟代理测试
console.log('\n=== 虚拟代理测试 ===');
var virtualProxy = new ProxyPattern.VirtualProxy(function() {
console.log('创建重型对象...');
// 模拟重型对象创建
return {
data: new Array(1000).fill(0).map(function(_, i) { return i; }),
process: function() {
console.log('处理数据...');
return this.data.reduce(function(sum, num) { return sum + num; }, 0);
}
};
});
var proxyHeavyObject = virtualProxy.getProxy();
console.log('代理已创建,但实际对象未创建');
console.log('是否已加载:', proxyHeavyObject.__isLoaded());
// 延迟加载
proxyHeavyObject.__load(function(error, realObject) {
if (error) {
console.error('加载失败:', error);
} else {
console.log('对象加载成功');
console.log('处理结果:', realObject.process());
}
});
// 4. 保护代理测试
console.log('\n=== 保护代理测试 ===');
var sensitiveObject = {
publicData: '公开数据',
privateData: '私有数据',
adminData: '管理员数据',
publicMethod: function() {
return '公开方法执行';
},
adminMethod: function() {
return '管理员方法执行';
}
};
var protectionProxy = new ProxyPattern.ProtectionProxy(sensitiveObject, {
privateData: ['read'], // 只允许读取
adminData: function(action, key, context) {
// 自定义权限检查
return context && context.role === 'admin';
},
adminMethod: function(action, key, context) {
return context && context.role === 'admin';
}
});
var protectedObj = protectionProxy.getProxy();
console.log('访问公开数据:', protectedObj.publicData);
try {
console.log('访问私有数据:', protectedObj.privateData);
} catch (error) {
console.log('访问失败:', error.message);
}
try {
console.log('访问管理员数据:', protectedObj.adminData);
} catch (error) {
console.log('访问失败:', error.message);
}
// 设置权限上下文(这里只是演示,实际应用中权限上下文会从认证系统获取)
var adminContext = { role: 'admin' };
// 5. 缓存代理测试
console.log('\n=== 缓存代理测试 ===');
function DataService() {}
DataService.prototype.expensiveCalculation = function(n) {
console.log('执行昂贵的计算,参数:', n);
// 模拟耗时计算
var result = 0;
for (var i = 1; i <= n; i++) {
result += i;
}
return result;
};
DataService.prototype.fetchData = function(id) {
console.log('获取数据,ID:', id);
return { id: id, data: 'data_' + id, timestamp: Date.now() };
};
var dataService = new DataService();
var cacheProxy = new ProxyPattern.CacheProxy(dataService, {
maxCacheSize: 5,
ttl: 1000 // 1秒过期
});
var cachedService = cacheProxy.getProxy();
// 第一次调用
console.log('第一次计算:', cachedService.expensiveCalculation(100));
// 第二次调用(从缓存获取)
console.log('第二次计算:', cachedService.expensiveCalculation(100));
// 不同参数的调用
console.log('不同参数计算:', cachedService.expensiveCalculation(200));
console.log('缓存统计:', cachedService.__getCacheStats());
// 6. 组合代理测试
console.log('\n=== 组合代理测试 ===');
var factory = new ProxyFactory();
var targetObj = {
data: [],
add: function(item) {
this.data.push(item);
return this.data.length;
},
get: function(index) {
return this.data[index];
},
clear: function() {
this.data = [];
}
};
// 创建组合代理:缓存 + 方法拦截
var compositeProxy = factory.createCompositeProxy(targetObj, [
{
type: 'cache',
options: { maxCacheSize: 10 }
},
{
type: 'method',
options: {}
}
]);
// 添加拦截器
compositeProxy.__interceptMethod('add', {
before: function(args, methodName) {
console.log('添加项目:', args[0]);
},
after: function(result, args, methodName) {
console.log('当前数组长度:', result);
}
});
compositeProxy.add('item1');
compositeProxy.add('item2');
console.log('获取第一项:', compositeProxy.get(0));
console.log('再次获取第一项(缓存):', compositeProxy.get(0));
// 7. 性能对比测试
console.log('\n=== 性能对比测试 ===');
function performanceTest() {
var iterations = 10000;
// 原始对象测试
var startTime = Date.now();
for (var i = 0; i < iterations; i++) {
calculator.add(1).getResult();
}
var originalTime = Date.now() - startTime;
// 代理对象测试
calculator.result = 0; // 重置
startTime = Date.now();
for (var j = 0; j < iterations; j++) {
proxyCalculator.add(1).getResult();
}
var proxyTime = Date.now() - startTime;
console.log('原始对象执行时间:', originalTime + 'ms');
console.log('代理对象执行时间:', proxyTime + 'ms');
console.log('性能开销:', ((proxyTime - originalTime) / originalTime * 100).toFixed(2) + '%');
}
performanceTest();
console.log('\nES5 代理模式演示完成!');
ES5 代理模式的高级应用:
// 响应式代理 - 模拟 Vue.js 的响应式系统
var ReactiveProxy = (function() {
function ReactiveProxy(data) {
this.data = data;
this.watchers = {};
this.proxyData = this.createReactiveProxy();
}
ReactiveProxy.prototype.createReactiveProxy = function() {
var self = this;
var proxy = {};
for (var key in this.data) {
if (this.data.hasOwnProperty(key)) {
this.defineReactive(proxy, key, this.data[key]);
}
}
// 添加观察方法
proxy.$watch = function(key, callback) {
if (!self.watchers[key]) {
self.watchers[key] = [];
}
self.watchers[key].push(callback);
};
proxy.$unwatch = function(key, callback) {
if (self.watchers[key]) {
var index = self.watchers[key].indexOf(callback);
if (index !== -1) {
self.watchers[key].splice(index, 1);
}
}
};
return proxy;
};
ReactiveProxy.prototype.defineReactive = function(proxy, key, value) {
var self = this;
Object.defineProperty(proxy, key, {
get: function() {
return self.data[key];
},
set: function(newValue) {
var oldValue = self.data[key];
if (newValue !== oldValue) {
self.data[key] = newValue;
self.notify(key, newValue, oldValue);
}
},
enumerable: true,
configurable: true
});
};
ReactiveProxy.prototype.notify = function(key, newValue, oldValue) {
var watchers = this.watchers[key];
if (watchers) {
for (var i = 0; i < watchers.length; i++) {
try {
watchers[i](newValue, oldValue);
} catch (error) {
console.error('Watcher 执行错误:', error);
}
}
}
};
ReactiveProxy.prototype.getProxy = function() {
return this.proxyData;
};
return ReactiveProxy;
})();
// 使用示例
console.log('\n=== 响应式代理演示 ===');
var reactiveData = new ReactiveProxy({
message: 'Hello',
count: 0
});
var reactive = reactiveData.getProxy();
// 添加观察者
reactive.$watch('message', function(newVal, oldVal) {
console.log('message 变化:', oldVal, '->', newVal);
});
reactive.$watch('count', function(newVal, oldVal) {
console.log('count 变化:', oldVal, '->', newVal);
});
reactive.message = 'Hello World';
reactive.count = 42;
reactive.count = 100;
console.log('\n响应式代理演示完成!');
ES5 代理模式总结:
实现技术:
代理类型:
应用场景:
优势:
注意事项:
ES5 代理模式为对象行为控制提供了强大而灵活的解决方案,是现代 JavaScript 框架的重要基础技术。
What new primitive data type was added in ES6? What are its characteristics?
What new primitive data type was added in ES6? What are its characteristics?
考察点:基础知识的掌握。
答案:
ES6 新增了 Symbol 这个基本数据类型。Symbol 是一种原始数据类型,表示独一无二的值。每个通过 Symbol() 创建的值都是唯一的,即使传入相同的描述参数。
主要特点:
唯一性:每个 Symbol 值都是独一无二的
const sym1 = Symbol('desc');
const sym2 = Symbol('desc');
console.log(sym1 === sym2); // false
不可枚举性:Symbol 属性不会出现在 for…in、Object.keys() 等遍历中
const obj = {
name: 'test',
[Symbol('hidden')]: 'secret'
};
console.log(Object.keys(obj)); // ['name']
类型转换限制:Symbol 不能与其他类型进行隐式转换
const sym = Symbol('test');
// String(sym) 或 sym.toString() 可以显式转换
// sym + '' 会报错
适用场景:
What are the differences between let, const and var?
What are the differences between let, const and var?
考察点:变量声明方式和作用域。
答案:
let、const 和 var 是 JavaScript 中三种变量声明方式,它们在作用域、变量提升、重复声明和赋值方面有显著区别。
主要区别:
作用域范围:
// var: 函数作用域或全局作用域
function test1() {
if (true) {
var a = 1;
}
console.log(a); // 1,可以访问
}
// let/const: 块级作用域
function test2() {
if (true) {
let b = 1;
const c = 2;
}
// console.log(b); // ReferenceError
// console.log(c); // ReferenceError
}
变量提升表现:
console.log(a); // undefined (var声明被提升)
// console.log(b); // ReferenceError (let存在暂时性死区)
var a = 1;
let b = 2;
重复声明:
var a = 1;
var a = 2; // 允许重复声明
let b = 1;
// let b = 2; // SyntaxError: 不允许重复声明
重新赋值:
let a = 1;
a = 2; // 允许重新赋值
const b = 1;
// b = 2; // TypeError: 不允许重新赋值
使用建议:
What is block scope? How does ES6 implement it?
What is block scope? How does ES6 implement it?
考察点:作用域理解与变量生命周期。
答案:
块级作用域是指变量只在一对大括号 {} 内有效的作用域规则。ES6 通过 let 和 const 关键字引入了块级作用域,解决了 var 声明变量时的作用域问题。
ES6 实现方式:
let 声明的块级作用域:
{
let a = 1;
console.log(a); // 1
}
// console.log(a); // ReferenceError: a is not defined
const 声明的块级作用域:
{
const PI = 3.14159;
console.log(PI); // 3.14159
}
// console.log(PI); // ReferenceError: PI is not defined
for 循环中的块级作用域:
// ES5 问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3 3 3
}
// ES6 解决方案
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0 1 2
}
主要特征:
实际应用:
What are the differences between arrow functions and regular functions?
What are the differences between arrow functions and regular functions?
考察点:语法特性与 this 指向。
答案:
箭头函数是 ES6 引入的函数简写语法,与普通函数在语法、this 指向、arguments 对象等方面存在显著区别。
主要区别:
语法差异:
// 普通函数
function add(a, b) {
return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
// 单参数可省略括号
const double = x => x * 2;
// 无参数必须保留括号
const getRandom = () => Math.random();
this 指向:
const obj = {
name: 'Alice',
// 普通函数:this 指向调用对象
getName: function() {
return this.name; // 'Alice'
},
// 箭头函数:this 继承外层作用域
getNameArrow: () => {
return this.name; // undefined (全局this)
}
};
arguments 对象:
// 普通函数有 arguments 对象
function normalFunc() {
console.log(arguments); // [1, 2, 3]
}
normalFunc(1, 2, 3);
// 箭头函数没有 arguments 对象
const arrowFunc = () => {
// console.log(arguments); // ReferenceError
};
构造函数使用:
// 普通函数可作为构造函数
function Person(name) {
this.name = name;
}
const person = new Person('Bob');
// 箭头函数不能作为构造函数
const PersonArrow = (name) => {
this.name = name;
};
// const person2 = new PersonArrow('Bob'); // TypeError
适用场景:
What are template strings? What are their uses?
What are template strings? What are their uses?
考察点:字符串拼接与表达式插值。
答案:
模板字符串(Template Literals)是 ES6 引入的新型字符串字面量,使用反引号(`)包围,支持嵌入表达式、多行字符串和标签模板等高级功能。
主要用法:
变量插值:
const name = 'Alice';
const age = 25;
// ES5 方式
const msg1 = 'Hello, ' + name + '. You are ' + age + ' years old.';
// ES6 模板字符串
const msg2 = `Hello, ${name}. You are ${age} years old.`;
表达式计算:
const a = 10;
const b = 20;
const result = `${a} + ${b} = ${a + b}`; // "10 + 20 = 30"
// 函数调用
const getName = () => 'Bob';
const greeting = `Hello, ${getName()}!`; // "Hello, Bob!"
多行字符串:
// ES5 多行字符串
const html1 = '<div>\n' +
' <h1>Title</h1>\n' +
' <p>Content</p>\n' +
'</div>';
// ES6 模板字符串
const html2 = `
<div>
<h1>Title</h1>
<p>Content</p>
</div>`;
标签模板:
function highlight(strings, ...values) {
return strings.reduce((result, string, i) => {
return result + string + (values[i] ? `<mark>${values[i]}</mark>` : '');
}, '');
}
const name = 'Alice';
const score = 95;
const message = highlight`Student ${name} scored ${score} points!`;
// "Student <mark>Alice</mark> scored <mark>95</mark> points!"
实际应用:
What is destructuring assignment? What data types can it be used with?
What is destructuring assignment? What data types can it be used with?
考察点:数组、对象解构语法。
答案:
解构赋值是 ES6 引入的语法,允许从数组或对象中提取值并赋给变量,使代码更简洁易读。它是一种模式匹配的赋值方式。
适用数据类型:
数组解构:
// 基本用法
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3
// 跳过元素
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3
// 剩余参数
const [head, ...tail] = [1, 2, 3, 4];
console.log(head, tail); // 1 [2, 3, 4]
对象解构:
// 基本用法
const {name, age} = {name: 'Alice', age: 25, city: 'Beijing'};
console.log(name, age); // 'Alice' 25
// 重命名
const {name: userName, age: userAge} = {name: 'Bob', age: 30};
console.log(userName, userAge); // 'Bob' 30
// 嵌套解构
const {user: {name, profile: {email}}} = {
user: {
name: 'Charlie',
profile: {email: '[email protected]'}
}
};
字符串解构:
const [a, b, c, d, e] = 'hello';
console.log(a, b, c, d, e); // 'h' 'e' 'l' 'l' 'o'
// 获取字符串长度属性
const {length} = 'hello';
console.log(length); // 5
函数参数解构:
// 对象参数解构
function greet({name, age = 18}) {
return `Hello ${name}, you are ${age} years old`;
}
greet({name: 'Alice', age: 25}); // "Hello Alice, you are 25 years old"
// 数组参数解构
function sum([a, b]) {
return a + b;
}
sum([10, 20]); // 30
默认值设置:
// 数组默认值
const [x = 1, y = 2] = [undefined, null];
console.log(x, y); // 1 null
// 对象默认值
const {name = 'Anonymous', age = 0} = {age: 25};
console.log(name, age); // 'Anonymous' 25
实际应用:
How does ES6 declare default parameters? How is it different from ES5?
How does ES6 declare default parameters? How is it different from ES5?
考察点:函数参数默认值。
答案:
ES6 提供了直接在函数参数中声明默认值的语法,相比 ES5 的实现方式更加简洁和安全。
ES6 默认参数语法:
// 基本用法
function greet(name = 'Guest', age = 18) {
return `Hello ${name}, you are ${age} years old`;
}
greet(); // "Hello Guest, you are 18 years old"
greet('Alice'); // "Hello Alice, you are 18 years old"
greet('Bob', 25); // "Hello Bob, you are 25 years old"
与 ES5 的区别:
语法简洁性:
// ES5 方式
function multiply(a, b) {
a = a || 1; // 问题:a为0时也会使用默认值
b = typeof b !== 'undefined' ? b : 1; // 正确但冗长
return a * b;
}
// ES6 方式
function multiply(a = 1, b = 1) {
return a * b;
}
undefined 处理:
// ES6 只有 undefined 才触发默认值
function test(x = 10) {
return x;
}
test(0); // 0 (不会使用默认值)
test(null); // null (不会使用默认值)
test(undefined); // 10 (使用默认值)
test(); // 10 (使用默认值)
表达式和函数调用:
// 默认值可以是表达式或函数调用
function createId(prefix = 'id', suffix = Date.now()) {
return `${prefix}-${suffix}`;
}
// 可以引用前面的参数
function greet(name = 'Guest', message = `Hello ${name}`) {
return message;
}
与解构赋值结合:
// ES6 支持复杂的默认值设定
function config({
host = 'localhost',
port = 3000,
protocol = 'http'
} = {}) {
return `${protocol}://${host}:${port}`;
}
config(); // "http://localhost:3000"
config({host: 'example.com'}); // "http://example.com:3000"
优势对比:
What is the spread operator (...)? What are its common use cases?
What is the spread operator (…)? What are its common use cases?
考察点:数组、对象、函数参数等。
答案:
扩展运算符(Spread Operator)用三个点(…)表示,可以将可迭代对象展开为独立的元素。它在数组、对象、函数调用等场景中有广泛应用。
常见应用场景:
数组操作:
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
// 数组复制(浅拷贝)
const original = [1, 2, 3];
const copy = [...original]; // [1, 2, 3]
// 数组转换
const str = 'hello';
const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']
函数参数传递:
// 将数组元素作为参数传递
const numbers = [1, 2, 3, 4, 5];
const max = Math.max(...numbers); // 等同于 Math.max(1, 2, 3, 4, 5)
// 替代 apply 方法
function sum(a, b, c) {
return a + b + c;
}
const args = [1, 2, 3];
const result = sum(...args); // 6
对象操作:
// 对象展开(ES2018+)
const obj1 = {a: 1, b: 2};
const obj2 = {c: 3, d: 4};
const merged = {...obj1, ...obj2}; // {a: 1, b: 2, c: 3, d: 4}
// 对象复制和属性覆盖
const user = {name: 'Alice', age: 25};
const updatedUser = {...user, age: 26}; // {name: 'Alice', age: 26}
剩余参数(Rest Parameters):
// 函数剩余参数
function sum(first, ...rest) {
return first + rest.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// 数组解构中的剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
DOM 操作:
// NodeList 转数组
const nodeList = document.querySelectorAll('.item');
const elementsArray = [...nodeList];
// 数组方法应用
[...nodeList].forEach(el => el.classList.add('active'));
实际应用:
How does ES6 declare multi-line strings?
How does ES6 declare multi-line strings?
考察点:模板字符串语法。
答案:
ES6 通过模板字符串(Template Literals)提供了声明多行字符串的简洁方式,使用反引号(`)包围字符串,可以直接在字符串中换行。
语法格式:
// ES6 模板字符串多行声明
const multilineString = `
这是第一行
这是第二行
这是第三行
`;
与 ES5 对比:
ES5 实现方式:
// 方式一:字符串拼接
const html = '<div>' +
' <h1>标题</h1>' +
' <p>内容</p>' +
'</div>';
// 方式二:转义字符
const text = '第一行\n第二行\n第三行';
// 方式三:数组 join
const lines = [
'第一行',
'第二行',
'第三行'
].join('\n');
ES6 模板字符串:
const html = `
<div>
<h1>标题</h1>
<p>内容</p>
</div>`;
const text = `
第一行
第二行
第三行`;
实际应用示例:
HTML 模板:
const createCard = (title, content) => `
<div class="card">
<h2 class="card-title">${title}</h2>
<div class="card-content">
${content}
</div>
</div>`;
SQL 查询:
const query = `
SELECT users.name, orders.total
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.status = 'completed'
ORDER BY orders.created_at DESC`;
配置文件内容:
const configContent = `
{
"name": "${packageName}",
"version": "${version}",
"description": "${description}"
}`;
注意事项:
trim() 方法${} 使用优势:
What are the differences between for...of and for...in?
What are the differences between for…of and for…in?
考察点:遍历对象与数组的方式。
答案:
for…of 和 for…in 是两种不同的循环语句,主要区别在于遍历的内容和适用的数据类型。
主要区别:
遍历内容不同:
const array = ['a', 'b', 'c'];
// for...in 遍历索引/键名
for (let index in array) {
console.log(index); // "0", "1", "2" (字符串类型)
}
// for...of 遍历值
for (let value of array) {
console.log(value); // "a", "b", "c"
}
适用数据类型:
// for...in 适用于对象
const obj = {name: 'Alice', age: 25, city: 'Beijing'};
for (let key in obj) {
console.log(key, obj[key]); // name Alice, age 25, city Beijing
}
// for...of 适用于可迭代对象
const string = 'hello';
for (let char of string) {
console.log(char); // h, e, l, l, o
}
原型链属性:
Array.prototype.customMethod = function() {};
const arr = [1, 2, 3];
// for...in 会遍历原型链上的可枚举属性
for (let key in arr) {
console.log(key); // "0", "1", "2", "customMethod"
}
// for...of 只遍历自身的值
for (let value of arr) {
console.log(value); // 1, 2, 3
}
支持的数据结构对比:
for…in 支持:
for…of 支持:
使用建议:
// 推荐用法
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}];
// 遍历对象属性 - 使用 for...in
for (let key in users[0]) {
console.log(key, users[0][key]);
}
// 遍历数组元素 - 使用 for...of
for (let user of users) {
console.log(user.name);
}
// 需要索引时 - 使用 entries()
for (let [index, user] of users.entries()) {
console.log(index, user.name);
}
注意事项:
What are Map and Set? How do they differ from Object and Array?
What are Map and Set? How do they differ from Object and Array?
考察点:新数据结构的理解。
答案:
Map 和 Set 是 ES6 引入的两种新数据结构。Map 是键值对集合,Set 是值的集合,它们在功能和性能上相比传统的 Object 和 Array 有显著优势。
Map 与 Object 的区别:
键的类型:
// Object 的键只能是字符串或 Symbol
const obj = {
'name': 'Alice',
1: 'number key' // 1 会被转为字符串 "1"
};
// Map 的键可以是任何类型
const map = new Map();
map.set('string', '字符串键');
map.set(1, '数字键');
map.set(true, '布尔键');
map.set({id: 1}, '对象键');
大小获取:
// Object 需要手动计算
const obj = {a: 1, b: 2, c: 3};
const objSize = Object.keys(obj).length; // 3
// Map 直接获取
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
const mapSize = map.size; // 3
遍历方式:
// Object 遍历
for (let key in obj) {
console.log(key, obj[key]);
}
// Map 遍历(保证插入顺序)
for (let [key, value] of map) {
console.log(key, value);
}
Set 与 Array 的区别:
唯一性:
// Array 允许重复元素
const arr = [1, 2, 2, 3, 3, 3];
console.log(arr); // [1, 2, 2, 3, 3, 3]
// Set 自动去重
const set = new Set([1, 2, 2, 3, 3, 3]);
console.log(set); // Set(3) {1, 2, 3}
操作方法:
// Array 基于索引操作
const arr = [1, 2, 3];
arr.push(4);
arr[1] = 'modified';
// Set 基于值操作
const set = new Set([1, 2, 3]);
set.add(4);
set.delete(2);
console.log(set.has(3)); // true
API 对比:
// Map 常用方法
const map = new Map();
map.set(key, value); // 设置键值对
map.get(key); // 获取值
map.has(key); // 检查键是否存在
map.delete(key); // 删除键值对
map.clear(); // 清空所有键值对
// Set 常用方法
const set = new Set();
set.add(value); // 添加值
set.has(value); // 检查值是否存在
set.delete(value); // 删除值
set.clear(); // 清空所有值
适用场景:
性能优势:
How does ES6 simplify object property and method declarations?
How does ES6 simplify object property and method declarations?
考察点:对象字面量增强写法。
答案:
ES6 通过对象字面量增强(Enhanced Object Literals)提供了多种简化对象属性和方法声明的语法,使代码更加简洁易读。
主要简化方式:
属性简写:
// ES5 写法
const name = 'Alice';
const age = 25;
const user1 = {
name: name,
age: age
};
// ES6 简写(属性名与变量名相同时)
const user2 = {
name,
age
};
方法简写:
// ES5 写法
const obj1 = {
sayHello: function() {
return 'Hello!';
},
calculate: function(a, b) {
return a + b;
}
};
// ES6 简写
const obj2 = {
sayHello() {
return 'Hello!';
},
calculate(a, b) {
return a + b;
}
};
计算属性名:
// ES5 需要分步骤
const prefix = 'user';
const obj1 = {};
obj1[prefix + 'Name'] = 'Alice';
obj1[prefix + 'Age'] = 25;
// ES6 动态属性名
const obj2 = {
[`${prefix}Name`]: 'Alice',
[`${prefix}Age`]: 25,
[Symbol.iterator]: function() { /* ... */ }
};
生成器方法:
const obj = {
* generator() {
yield 1;
yield 2;
yield 3;
}
};
const gen = obj.generator();
console.log([...gen]); // [1, 2, 3]
async/await 方法:
const api = {
async fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
},
async* asyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
}
};
综合应用示例:
const createUser = (name, age, role) => {
const prefix = 'get';
const createdAt = Date.now();
return {
// 属性简写
name,
age,
role,
createdAt,
// 方法简写
greet() {
return `Hello, I'm ${this.name}`;
},
// 计算属性名
[`${prefix}Info`]() {
return {name: this.name, age: this.age, role: this.role};
},
// async 方法
async save() {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(this.getInfo())
});
return response.json();
},
// 生成器方法
* getFields() {
yield ['name', this.name];
yield ['age', this.age];
yield ['role', this.role];
}
};
};
使用优势:
注意事项:
What is Symbol? What are its application scenarios?
What is Symbol? What are its application scenarios?
考察点:唯一性、隐藏属性等。
答案:
Symbol 是 ES6 引入的第七种基本数据类型,表示独一无二的值。主要用于创建唯一标识符,避免属性名冲突,实现对象的私有属性等。
应用场景:
避免属性名冲突:
// 多个库或模块可能使用同名属性
const PRIVATE_KEY = Symbol('private');
const myObject = {
publicProperty: 'visible',
[PRIVATE_KEY]: 'hidden from normal enumeration'
};
console.log(Object.keys(myObject)); // ['publicProperty']
实现私有属性/方法:
const _counter = Symbol('counter');
const _increment = Symbol('increment');
class Counter {
constructor() {
this[_counter] = 0;
}
[_increment]() {
this[_counter]++;
}
get value() {
return this[_counter];
}
next() {
this[_increment]();
return this.value;
}
}
定义对象的元编程接口:
// 自定义迭代器
const myIterable = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
}
return { done: true };
}
};
}
};
for (let value of myIterable) {
console.log(value); // 1, 2, 3
}
创建常量值:
// 替代魔法字符串常量
const STATUS = {
LOADING: Symbol('loading'),
SUCCESS: Symbol('success'),
ERROR: Symbol('error')
};
function handleStatus(status) {
switch (status) {
case STATUS.LOADING:
return 'Loading...';
case STATUS.SUCCESS:
return 'Success!';
case STATUS.ERROR:
return 'Error occurred';
}
}
实现单例模式:
const singleton = Symbol('singleton');
class Database {
constructor() {
if (Database[singleton]) {
return Database[singleton];
}
Database[singleton] = this;
}
}
扩展内置对象行为:
// 自定义 toPrimitive 行为
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 42;
}
if (hint === 'string') {
return 'custom string';
}
return 'default';
}
};
console.log(+obj); // 42
console.log(`${obj}`); // 'custom string'
Well-known Symbols:
ES6 预定义了多个内置 Symbol:
Symbol.iterator:定义对象默认迭代器Symbol.toStringTag:定义对象的字符串描述Symbol.toPrimitive:定义对象转换为原始值的方法Symbol.hasInstance:定义 instanceof 运算符行为Symbol.species:定义创建衍生对象时使用的构造函数实际开发优势:
How does ES6 implement classes? How is it different from ES5 constructor functions?
How does ES6 implement classes? How is it different from ES5 constructor functions?
考察点:面向对象语法。
答案:
ES6 引入了 class 语法糖,提供了更接近传统面向对象语言的类定义方式,但底层仍基于原型链机制。
ES6 类实现:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
return `Hello, I'm ${this.name}`;
}
// 静态方法
static species() {
return 'Homo sapiens';
}
// getter/setter
get fullInfo() {
return `${this.name}, ${this.age} years old`;
}
set updateAge(newAge) {
this.age = newAge;
}
}
// 继承
class Student extends Person {
constructor(name, age, grade) {
super(name, age); // 调用父类构造函数
this.grade = grade;
}
greet() {
return `${super.greet()}, I'm a student`;
}
}
与 ES5 构造函数的区别:
语法简洁性:
// ES5 方式
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return 'Hello, I\'m ' + this.name;
};
Person.species = function() {
return 'Homo sapiens';
};
// ES6 方式更简洁清晰
class Person {
constructor(name, age) { /* ... */ }
greet() { /* ... */ }
static species() { /* ... */ }
}
严格模式:
// ES6 类自动运行在严格模式下
class MyClass {
constructor() {
// 自动严格模式,this 不会指向全局对象
}
}
不可提升:
// ES5 构造函数可以提升
const p1 = new Person(); // 可以工作
function Person() {}
// ES6 类不会提升
// const p2 = new MyClass(); // ReferenceError
class MyClass {}
继承机制:
// ES5 继承较复杂
function Student(name, age, grade) {
Person.call(this, name, age);
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
// ES6 继承简单明了
class Student extends Person {
constructor(name, age, grade) {
super(name, age);
this.grade = grade;
}
}
主要优势:
注意事项:
What is modularization? How does ES6 import and export modules?
What is modularization? How does ES6 import and export modules?
考察点:import/export 语法。
答案:
模块化是将程序拆分成独立的、可重用的代码单元的编程方式。ES6 引入了原生模块系统,提供了 import 和 export 语法来实现模块的导入和导出。
导出语法:
命名导出(Named Exports):
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
// 或者统一导出
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;
export { subtract, multiply };
// 重命名导出
export { multiply as times };
默认导出(Default Export):
// calculator.js
class Calculator {
add(a, b) { return a + b; }
}
export default Calculator;
// 或者直接导出
export default function(a, b) {
return a * b;
}
导入语法:
命名导入:
// 基本导入
import { PI, add } from './math.js';
// 重命名导入
import { add as sum, subtract as minus } from './math.js';
// 导入所有命名导出
import * as MathUtils from './math.js';
console.log(MathUtils.PI, MathUtils.add(1, 2));
默认导入:
import Calculator from './calculator.js';
// 可以任意命名
import Calc from './calculator.js';
混合导入:
// 同时导入默认和命名导出
import Calculator, { PI, add } from './math-calculator.js';
动态导入:
// 条件导入
if (condition) {
import('./feature.js').then(module => {
module.default(); // 使用默认导出
});
}
// async/await 方式
async function loadModule() {
const module = await import('./heavy-feature.js');
return module.heavyFunction();
}
模块化优势:
与其他模块系统对比:
What is a Promise? What are its three states?
What is a Promise? What are its three states?
考察点:异步编程基础概念。
答案:
Promise 是 ES6 引入的异步编程解决方案,用于处理异步操作的结果。它代表一个异步操作的最终完成(或失败)及其结果值。
三种状态:
// Promise 状态转换示例
const promise = new Promise((resolve, reject) => {
// 初始状态:pending
console.log('Promise created'); // 同步执行
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('Success!'); // 状态变为 fulfilled
} else {
reject('Failed!'); // 状态变为 rejected
}
}, 1000);
});
// 处理 Promise 结果
promise
.then(result => {
console.log('Fulfilled:', result);
})
.catch(error => {
console.log('Rejected:', error);
});
状态特点:
What new common methods did ES6 add to Array?
What new common methods did ES6 add to Array?
考察点:数组操作API。
答案:
ES6 为 Array 新增了多个实用方法,主要用于查找、填充、转换等操作:
主要新增方法:
Array.from() - 将类数组对象或可迭代对象转换为数组:
Array.from('hello'); // ['h','e','l','l','o']
Array.from(new Set([1,2,3])); // [1,2,3]
Array.from({0:'a', 1:'b', length:2}); // ['a','b']
Array.of() - 创建包含所有参数的新数组:
Array.of(7); // [7]
Array.of(1,2,3); // [1,2,3]
Array(7); // [empty × 7] - 对比Array构造函数
find() - 返回第一个满足条件的元素:
[1,2,3,4].find(x => x > 2); // 3
findIndex() - 返回第一个满足条件元素的索引:
[1,2,3,4].findIndex(x => x > 2); // 2
fill() - 用指定值填充数组:
new Array(3).fill(0); // [0,0,0]
[1,2,3].fill('a', 1, 2); // [1,'a',3]
copyWithin() - 浅复制数组的一部分到同一数组中的另一个位置:
[1,2,3,4,5].copyWithin(0, 3); // [4,5,3,4,5]
What new common methods did ES6 add to String?
What new common methods did ES6 add to String?
考察点:字符串处理API。
答案:
ES6 为 String 新增了多个便于字符串处理的方法:
主要新增方法:
startsWith() - 判断字符串是否以指定子字符串开头:
'hello world'.startsWith('hello'); // true
'hello world'.startsWith('world', 6); // true
endsWith() - 判断字符串是否以指定子字符串结尾:
'hello world'.endsWith('world'); // true
'hello world'.endsWith('hello', 5); // true
includes() - 判断字符串是否包含指定子字符串:
'hello world'.includes('lo w'); // true
'hello world'.includes('hi'); // false
repeat() - 重复字符串指定次数:
'abc'.repeat(3); // 'abcabcabc'
'hello'.repeat(0); // ''
padStart() 和 padEnd() - 字符串填充:
'5'.padStart(3, '0'); // '005'
'hello'.padEnd(10, '!'); // 'hello!!!!!'
Are variables declared with const really immutable? Please give examples.
考察点:const 的特性和限制。
答案:
const 声明的变量并不是完全不可变的。const 只保证变量标识符不能被重新赋值,但对于引用类型(对象、数组)的内部内容是可以修改的。
基本类型 - 不可变:
const num = 42;
// num = 43; // TypeError: Assignment to constant variable
const str = 'hello';
// str = 'world'; // TypeError: Assignment to constant variable
引用类型 - 内部可变:
对象属性可以修改:
const obj = { name: 'Alice', age: 25 };
// 可以修改属性
obj.name = 'Bob';
obj.city = 'Beijing';
console.log(obj); // { name: 'Bob', age: 25, city: 'Beijing' }
// 但不能重新赋值整个对象
// obj = {}; // TypeError
数组元素可以修改:
const arr = [1, 2, 3];
// 可以修改数组内容
arr.push(4);
arr[0] = 10;
console.log(arr); // [10, 2, 3, 4]
// 但不能重新赋值整个数组
// arr = []; // TypeError
嵌套对象也可以修改:
const user = {
profile: {
name: 'Alice',
settings: {
theme: 'dark'
}
}
};
user.profile.name = 'Bob';
user.profile.settings.theme = 'light';
// 完全有效
实现真正不可变的方法:
Object.freeze() - 浅冻结:
const obj = Object.freeze({ name: 'Alice', age: 25 });
// obj.name = 'Bob'; // 严格模式下报错,非严格模式静默失败
深冻结实现:
function deepFreeze(obj) {
Object.getOwnPropertyNames(obj).forEach(name => {
const value = obj[name];
if (value && typeof value === 'object') {
deepFreeze(value);
}
});
return Object.freeze(obj);
}
const frozenObj = deepFreeze({
user: { name: 'Alice' }
});
使用 Immutable.js 等库:
const { Map } = require('immutable');
const map1 = Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50); // 返回新对象,不修改原对象
const 的实际作用:
Please explain the ‘this’ binding rules of arrow functions.
考察点:词法作用域与上下文绑定。
答案:
箭头函数的 this 指向规则与普通函数完全不同。箭头函数没有自己的 this,而是捕获其定义时所在上下文的 this 值,这称为词法绑定或静态绑定。
核心规则:
继承外层作用域的 this:
const obj = {
name: 'Alice',
regularFunction: function() {
console.log('Regular function this:', this.name); // 'Alice'
const arrowFunction = () => {
console.log('Arrow function this:', this.name); // 'Alice'
};
arrowFunction();
}
};
obj.regularFunction();
不受调用方式影响:
const obj = {
name: 'Alice',
arrowMethod: () => {
console.log(this.name); // undefined (全局this)
}
};
// 无论如何调用,this 都不会改变
obj.arrowMethod(); // undefined
obj.arrowMethod.call({name: 'Bob'}); // undefined
obj.arrowMethod.bind({name: 'Charlie'})(); // undefined
与普通函数对比:
function Person(name) {
this.name = name;
// 普通函数:this 取决于调用方式
this.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
// 箭头函数:this 继承构造函数的 this
this.sayHi = () => {
console.log(`Hi, I'm ${this.name}`);
};
}
const person = new Person('Alice');
const sayHello = person.sayHello;
const sayHi = person.sayHi;
// 普通函数丢失 this 绑定
sayHello(); // "Hello, I'm undefined"
// 箭头函数保持 this 绑定
sayHi(); // "Hi, I'm Alice"
实际应用场景:
事件处理器:
class Counter {
constructor() {
this.count = 0;
// 使用箭头函数避免 this 丢失
document.getElementById('btn').addEventListener('click', () => {
this.increment(); // this 正确指向 Counter 实例
});
}
increment() {
this.count++;
console.log(this.count);
}
}
异步操作:
class DataService {
constructor() {
this.data = [];
}
async fetchData() {
try {
const response = await fetch('/api/data');
const result = await response.json();
// 箭头函数确保 this 指向正确
result.forEach(item => {
this.data.push(item); // this 指向 DataService 实例
});
} catch (error) {
console.error(error);
}
}
}
注意事项:
What is Promise? What problems does it solve?
What is Promise? What problems does it solve?
考察点:异步编程与回调地狱。
答案:
Promise 是 ES6 引入的异步编程解决方案,代表一个异步操作的最终完成或失败。它主要解决了回调函数的嵌套问题,提供了更好的异步流程控制。
Promise 解决的核心问题:
回调地狱(Callback Hell):
// ES5 回调地狱
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
// 嵌套层级过深,难以维护
});
});
});
});
// Promise 链式调用
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => getMoreData(c))
.then(d => {
// 扁平化的异步流程
});
错误处理困难:
// 回调方式:每层都需要错误处理
getData(function(err, a) {
if (err) handleError(err);
getMoreData(a, function(err, b) {
if (err) handleError(err);
// 重复的错误处理逻辑
});
});
// Promise 方式:统一错误处理
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.catch(err => handleError(err)); // 统一捕获错误
Promise 的核心特性:
状态管理:
const promise = new Promise((resolve, reject) => {
// pending 状态
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('成功'); // 变为 fulfilled
} else {
reject('失败'); // 变为 rejected
}
}, 1000);
});
链式调用:
promise
.then(result => {
console.log(result);
return result + ' 处理完成';
})
.then(newResult => {
console.log(newResult);
})
.catch(error => {
console.error(error);
});
实际应用优势:
并发控制:
// 并行执行多个异步操作
Promise.all([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
]).then(responses => {
// 所有请求完成后处理
});
// 竞速执行,取最快的结果
Promise.race([
fetch('/api/server1'),
fetch('/api/server2')
]).then(fastestResponse => {
// 处理最快返回的结果
});
更好的代码组织:
// 可组合的异步操作
function authenticateUser(credentials) {
return validateCredentials(credentials)
.then(user => generateToken(user))
.then(token => saveSession(token))
.then(session => ({ user: session.user, token: session.token }));
}
解决的问题总结:
How to implement a simple Promise?
How to implement a simple Promise?
考察点:状态机与异步控制。
答案:
实现一个简单的 Promise 需要理解其状态机制、回调队列管理和链式调用原理。以下是一个基础的 Promise 实现:
基础 Promise 实现:
class SimplePromise {
constructor(executor) {
// 三种状态
this.PENDING = 'pending';
this.FULFILLED = 'fulfilled';
this.REJECTED = 'rejected';
// 初始状态
this.state = this.PENDING;
this.value = undefined;
this.reason = undefined;
// 回调队列
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// resolve 函数
const resolve = (value) => {
if (this.state === this.PENDING) {
this.state = this.FULFILLED;
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
// reject 函数
const reject = (reason) => {
if (this.state === this.PENDING) {
this.state = this.REJECTED;
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(callback => callback());
}
};
// 执行传入的函数
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// then 方法实现
then(onFulfilled, onRejected) {
// 返回新的 Promise 支持链式调用
return new SimplePromise((resolve, reject) => {
// 处理 fulfilled 状态
const handleFulfilled = () => {
try {
if (typeof onFulfilled === 'function') {
const result = onFulfilled(this.value);
resolve(result);
} else {
resolve(this.value);
}
} catch (error) {
reject(error);
}
};
// 处理 rejected 状态
const handleRejected = () => {
try {
if (typeof onRejected === 'function') {
const result = onRejected(this.reason);
resolve(result);
} else {
reject(this.reason);
}
} catch (error) {
reject(error);
}
};
// 根据当前状态执行相应逻辑
if (this.state === this.FULFILLED) {
// 异步执行保证回调顺序
setTimeout(handleFulfilled, 0);
} else if (this.state === this.REJECTED) {
setTimeout(handleRejected, 0);
} else if (this.state === this.PENDING) {
// 状态未确定,加入回调队列
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
}
// catch 方法实现
catch(onRejected) {
return this.then(null, onRejected);
}
}
使用示例:
// 基本使用
const promise = new SimplePromise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('成功!');
} else {
reject('失败!');
}
}, 1000);
});
promise
.then(result => {
console.log('第一个then:', result);
return result + ' -> 处理完成';
})
.then(result => {
console.log('第二个then:', result);
})
.catch(error => {
console.error('捕获错误:', error);
});
核心实现要点:
状态管理:
回调队列:
链式调用:
异步执行:
静态方法扩展:
// Promise.resolve 静态方法
SimplePromise.resolve = function(value) {
return new SimplePromise(resolve => resolve(value));
};
// Promise.reject 静态方法
SimplePromise.reject = function(reason) {
return new SimplePromise((resolve, reject) => reject(reason));
};
// Promise.all 静态方法
SimplePromise.all = function(promises) {
return new SimplePromise((resolve, reject) => {
const results = [];
let completed = 0;
promises.forEach((promise, index) => {
promise.then(value => {
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
}, reject);
});
});
};
这个简单的 Promise 实现展示了 Promise 的核心机制:状态管理、回调队列和链式调用,为理解更复杂的异步流程控制奠定了基础。
What is a Generator? How to use it?
What is a Generator? How to use it?
考察点:函数暂停与迭代器协议。
答案:
Generator(生成器)是 ES6 引入的特殊函数,能够暂停执行并在需要时恢复。它通过 function* 语法定义,使用 yield 关键字来暂停执行并产生值。
基本语法和使用:
Generator 函数定义:
function* simpleGenerator() {
console.log('开始执行');
yield 1;
console.log('第一次恢复');
yield 2;
console.log('第二次恢复');
return 3;
}
// 调用 Generator 函数返回迭代器对象
const gen = simpleGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
yield 的双向通信:
function* communicateGenerator() {
const a = yield '请输入第一个数';
const b = yield '请输入第二个数';
return a + b;
}
const gen = communicateGenerator();
console.log(gen.next()); // { value: '请输入第一个数', done: false }
console.log(gen.next(10)); // { value: '请输入第二个数', done: false }
console.log(gen.next(20)); // { value: 30, done: true }
实际应用场景:
实现迭代器:
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
// 使用 for...of 遍历
for (const num of range(0, 10, 2)) {
console.log(num); // 0, 2, 4, 6, 8
}
// 转换为数组
const numbers = [...range(1, 6)]; // [1, 2, 3, 4, 5]
异步流程控制:
function* fetchUserData() {
try {
const user = yield fetch('/api/user');
const posts = yield fetch(`/api/users/${user.id}/posts`);
const comments = yield fetch(`/api/posts/${posts[0].id}/comments`);
return { user, posts, comments };
} catch (error) {
console.error('获取数据失败:', error);
}
}
// 执行器函数
function runGenerator(genFunc) {
const gen = genFunc();
function step(value) {
const result = gen.next(value);
if (!result.done) {
return result.value.then(step);
}
return result.value;
}
return step();
}
无限序列生成:
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
const first10 = [];
for (let i = 0; i < 10; i++) {
first10.push(fib.next().value);
}
console.log(first10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
状态机实现:
function* trafficLight() {
while (true) {
yield 'red';
yield 'yellow';
yield 'green';
}
}
const light = trafficLight();
console.log(light.next().value); // 'red'
console.log(light.next().value); // 'yellow'
console.log(light.next().value); // 'green'
console.log(light.next().value); // 'red' (循环)
Generator 对象方法:
function* errorHandlingGen() {
try {
const a = yield 'step 1';
const b = yield 'step 2';
yield a + b;
} catch (error) {
yield 'error: ' + error.message;
}
}
const gen = errorHandlingGen();
console.log(gen.next()); // { value: 'step 1', done: false }
console.log(gen.throw(new Error('出错了'))); // { value: 'error: 出错了', done: false }
与 async/await 的关系:
Generator 是 async/await 的基础实现原理:
// Generator 版本
function* asyncGenerator() {
const user = yield fetch('/api/user');
const posts = yield fetch('/api/posts');
return { user, posts };
}
// 等价的 async/await 版本
async function asyncFunction() {
const user = await fetch('/api/user');
const posts = await fetch('/api/posts');
return { user, posts };
}
优势和特点:
How is class inheritance implemented in ES6? What is the role of the super keyword?
How is class inheritance implemented in ES6? What is the role of the super keyword?
考察点:原型链与构造函数继承。
答案:
ES6 的类继承通过 extends 关键字实现,底层仍基于原型链机制。super 关键字用于访问和调用父类的构造函数和方法,是实现继承的核心。
类继承基本语法:
// 父类定义
class Animal {
constructor(name, species) {
this.name = name;
this.species = species;
}
speak() {
return `${this.name} makes a sound`;
}
static getKingdom() {
return 'Animalia';
}
}
// 子类继承
class Dog extends Animal {
constructor(name, breed) {
super(name, 'Canine'); // 调用父类构造函数
this.breed = breed;
}
speak() {
return `${super.speak()} - Woof!`; // 调用父类方法
}
wagTail() {
return `${this.name} is wagging its tail`;
}
}
super 关键字的作用:
在构造函数中调用父类构造函数:
class Vehicle {
constructor(brand, model) {
this.brand = brand;
this.model = model;
}
}
class Car extends Vehicle {
constructor(brand, model, doors) {
super(brand, model); // 必须在使用 this 之前调用
this.doors = doors;
}
}
const myCar = new Car('Toyota', 'Camry', 4);
在方法中调用父类方法:
class Shape {
getArea() {
return 0;
}
describe() {
return `This shape has area: ${this.getArea()}`;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
describe() {
return `Rectangle: ${super.describe()}`;
}
}
在静态方法中调用父类静态方法:
class MathUtils {
static multiply(a, b) {
return a * b;
}
}
class AdvancedMath extends MathUtils {
static power(base, exponent) {
return Math.pow(base, exponent);
}
static multiplyAndPower(a, b, exp) {
const product = super.multiply(a, b); // 调用父类静态方法
return this.power(product, exp);
}
}
继承机制的底层实现原理:
// ES6 类继承等价的 ES5 实现
function Animal(name, species) {
this.name = name;
this.species = species;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
function Dog(name, breed) {
Animal.call(this, name, 'Canine'); // 相当于 super()
this.breed = breed;
}
// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
return Animal.prototype.speak.call(this) + ' - Woof!'; // 相当于 super.speak()
};
继承的特殊情况处理:
多层继承:
class Animal {
constructor(name) { this.name = name; }
move() { return `${this.name} is moving`; }
}
class Mammal extends Animal {
constructor(name, furColor) {
super(name);
this.furColor = furColor;
}
breathe() { return `${this.name} is breathing`; }
}
class Cat extends Mammal {
constructor(name, furColor, breed) {
super(name, furColor);
this.breed = breed;
}
purr() {
return `${super.move()} and purring`; // 可以调用祖先类方法
}
}
抽象类模式:
class AbstractShape {
constructor() {
if (new.target === AbstractShape) {
throw new Error('Abstract class cannot be instantiated');
}
}
getArea() {
throw new Error('Must implement getArea method');
}
}
class Circle extends AbstractShape {
constructor(radius) {
super();
this.radius = radius;
}
getArea() {
return Math.PI * this.radius * this.radius;
}
}
注意事项:
super() 才能使用 thissuper 只能在类的方法中使用,不能在普通函数中使用实际应用优势:
super 关键字的便捷调用What is Proxy? What functions can it implement?
What is Proxy? What functions can it implement?
考察点:对象代理与元编程。
答案:
Proxy 是 ES6 引入的强大特性,用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等)。它创建一个对象的代理,从而实现对该对象基本操作的拦截和自定义。
基本语法:
const proxy = new Proxy(target, handler);
常用的拦截操作(traps):
属性访问拦截(get/set):
const user = {
name: 'Alice',
age: 25
};
const userProxy = new Proxy(user, {
get(target, property) {
console.log(`访问属性: ${property}`);
return target[property];
},
set(target, property, value) {
if (property === 'age' && value < 0) {
throw new Error('年龄不能为负数');
}
console.log(`设置属性: ${property} = ${value}`);
target[property] = value;
return true;
}
});
console.log(userProxy.name); // "访问属性: name" -> "Alice"
userProxy.age = 30; // "设置属性: age = 30"
函数调用拦截(apply):
function sum(a, b) {
return a + b;
}
const sumProxy = new Proxy(sum, {
apply(target, thisArg, argumentsList) {
console.log(`调用函数,参数:`, argumentsList);
const result = target.apply(thisArg, argumentsList);
console.log(`返回结果:`, result);
return result;
}
});
sumProxy(1, 2); // 输出调用信息和结果
实际应用场景:
数据验证和格式化:
function createValidatedUser(userData) {
return new Proxy(userData, {
set(target, property, value) {
if (property === 'email' && !value.includes('@')) {
throw new Error('无效的邮箱格式');
}
if (property === 'age') {
value = parseInt(value); // 自动转换类型
}
target[property] = value;
return true;
}
});
}
const user = createValidatedUser({});
user.email = '[email protected]'; // 正常
user.age = '25'; // 自动转换为数字
响应式数据系统:
function createReactive(data, callback) {
return new Proxy(data, {
set(target, property, value) {
const oldValue = target[property];
target[property] = value;
callback(property, value, oldValue);
return true;
}
});
}
const state = createReactive(
{ count: 0 },
(prop, newVal, oldVal) => {
console.log(`${prop} 从 ${oldVal} 变为 ${newVal}`);
}
);
state.count = 1; // "count 从 0 变为 1"
API 包装和缓存:
function createCachedAPI(apiObject) {
const cache = new Map();
return new Proxy(apiObject, {
get(target, property) {
if (typeof target[property] === 'function') {
return function(...args) {
const cacheKey = `${property}_${JSON.stringify(args)}`;
if (cache.has(cacheKey)) {
console.log('从缓存返回:', cacheKey);
return cache.get(cacheKey);
}
const result = target[property].apply(target, args);
cache.set(cacheKey, result);
return result;
};
}
return target[property];
}
});
}
虚拟对象和默认值:
const defaultConfig = new Proxy({}, {
get(target, property) {
if (property in target) {
return target[property];
}
// 提供默认值
const defaults = {
theme: 'light',
language: 'en',
timeout: 5000
};
return defaults[property];
}
});
console.log(defaultConfig.theme); // 'light'
console.log(defaultConfig.customProp); // undefined
数组负索引访问:
function createArrayWithNegativeIndex(arr) {
return new Proxy(arr, {
get(target, property) {
if (typeof property === 'string' && /^-\d+$/.test(property)) {
const index = target.length + parseInt(property);
return target[index];
}
return target[property];
}
});
}
const arr = createArrayWithNegativeIndex([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5
console.log(arr[-2]); // 4
常用的 Handler 方法:
get(target, property, receiver):属性读取拦截set(target, property, value, receiver):属性设置拦截has(target, property):in 操作符拦截deleteProperty(target, property):delete 操作拦截apply(target, thisArg, argumentsList):函数调用拦截construct(target, argumentsList, newTarget):new 操作拦截ownKeys(target):Object.keys() 等操作拦截Proxy 的优势:
What is an Iterator? How to customize an iterator?
What is an Iterator? How to customize an iterator?
考察点:遍历协议与 Symbol.iterator。
答案:
迭代器(Iterator)是 ES6 引入的遍历机制,定义了一种标准的方式来产生一系列值。迭代器是一个对象,实现了迭代器协议,即具有 next() 方法。
迭代器协议:
// 迭代器对象必须实现 next() 方法
const iterator = {
next() {
return {
value: someValue, // 当前值
done: boolean // 是否完成
};
}
};
自定义迭代器实现:
简单的数字迭代器:
function createRangeIterator(start, end) {
let current = start;
return {
next() {
if (current < end) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
const iterator = createRangeIterator(1, 4);
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()); // { done: true }
实现可迭代对象:
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
// 实现 Symbol.iterator 方法
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current < end) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
}
const range = new Range(1, 5);
// 可以使用 for...of 遍历
for (const num of range) {
console.log(num); // 1, 2, 3, 4
}
// 可以转换为数组
const numbers = [...range]; // [1, 2, 3, 4]
复杂的自定义迭代器:
树结构深度优先遍历:
class TreeNode {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
*[Symbol.iterator]() {
yield this.value;
for (const child of this.children) {
yield* child; // 委托给子节点的迭代器
}
}
}
const tree = new TreeNode(1, [
new TreeNode(2, [
new TreeNode(4),
new TreeNode(5)
]),
new TreeNode(3, [
new TreeNode(6)
])
]);
for (const value of tree) {
console.log(value); // 1, 2, 4, 5, 3, 6
}
斐波那契数列迭代器:
class FibonacciIterator {
constructor(limit = Infinity) {
this.limit = limit;
this.count = 0;
this.current = 0;
this.next = 1;
}
[Symbol.iterator]() {
return this;
}
next() {
if (this.count < this.limit) {
const value = this.current;
[this.current, this.next] = [this.next, this.current + this.next];
this.count++;
return { value, done: false };
}
return { done: true };
}
}
const fib = new FibonacciIterator(10);
console.log([...fib]); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
异步迭代器:
class AsyncDataIterator {
constructor(urls) {
this.urls = urls;
this.index = 0;
}
[Symbol.asyncIterator]() {
return this;
}
async next() {
if (this.index < this.urls.length) {
try {
const response = await fetch(this.urls[this.index++]);
const data = await response.json();
return { value: data, done: false };
} catch (error) {
return { value: error, done: false };
}
}
return { done: true };
}
}
// 使用 for await...of 遍历
async function processData() {
const iterator = new AsyncDataIterator(['/api/data1', '/api/data2']);
for await (const data of iterator) {
console.log(data);
}
}
迭代器的实用工具:
// 迭代器工具函数
class IteratorUtils {
static map(iterable, fn) {
return {
*[Symbol.iterator]() {
for (const item of iterable) {
yield fn(item);
}
}
};
}
static filter(iterable, predicate) {
return {
*[Symbol.iterator]() {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
};
}
static take(iterable, count) {
return {
*[Symbol.iterator]() {
let taken = 0;
for (const item of iterable) {
if (taken >= count) break;
yield item;
taken++;
}
}
};
}
}
// 使用示例
const numbers = new Range(1, 100);
const evenNumbers = IteratorUtils.filter(numbers, x => x % 2 === 0);
const firstTen = IteratorUtils.take(evenNumbers, 10);
console.log([...firstTen]); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
迭代器的应用优势:
What are the common methods of Set and Map? How to iterate through them?
What are the common methods of Set and Map? How to iterate through them?
考察点:API 熟悉度。
答案:
Set 和 Map 是 ES6 引入的两种新的数据结构,提供了丰富的 API 用于操作和遍历数据。
Set 常用方法:
基本操作方法:
const mySet = new Set();
// 添加值
mySet.add(1);
mySet.add(2);
mySet.add(2); // 重复值会被忽略
// 检查是否存在
console.log(mySet.has(1)); // true
console.log(mySet.has(3)); // false
// 删除值
mySet.delete(2); // 返回 true
mySet.delete(3); // 返回 false(不存在)
// 获取大小
console.log(mySet.size); // 1
// 清空所有值
mySet.clear();
Set 遍历方法:
const colors = new Set(['red', 'green', 'blue']);
// for...of 遍历
for (const color of colors) {
console.log(color);
}
// forEach 遍历
colors.forEach(color => console.log(color));
// 使用迭代器方法
for (const color of colors.values()) {
console.log(color);
}
// keys() 和 values() 在 Set 中是相同的
for (const color of colors.keys()) {
console.log(color);
}
// entries() 返回 [value, value] 对
for (const [key, value] of colors.entries()) {
console.log(key, value); // key 和 value 相同
}
Map 常用方法:
基本操作方法:
const myMap = new Map();
// 设置键值对
myMap.set('name', 'Alice');
myMap.set('age', 25);
myMap.set(1, 'number key');
// 获取值
console.log(myMap.get('name')); // 'Alice'
console.log(myMap.get('unknown')); // undefined
// 检查键是否存在
console.log(myMap.has('age')); // true
// 删除键值对
myMap.delete('age'); // 返回 true
// 获取大小
console.log(myMap.size); // 2
// 清空所有键值对
myMap.clear();
Map 遍历方法:
const userMap = new Map([
['name', 'Alice'],
['age', 25],
['city', 'Beijing']
]);
// for...of 遍历键值对
for (const [key, value] of userMap) {
console.log(`${key}: ${value}`);
}
// forEach 遍历
userMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 遍历键
for (const key of userMap.keys()) {
console.log('Key:', key);
}
// 遍历值
for (const value of userMap.values()) {
console.log('Value:', value);
}
// 遍历键值对
for (const [key, value] of userMap.entries()) {
console.log(`${key} => ${value}`);
}
实际应用示例:
Set 用于数据去重:
// 数组去重
const numbers = [1, 2, 2, 3, 3, 4, 5];
const uniqueNumbers = [...new Set(numbers)]; // [1, 2, 3, 4, 5]
// 字符串去重
const text = 'hello';
const uniqueChars = [...new Set(text)]; // ['h', 'e', 'l', 'o']
// 对象数组去重(需要自定义逻辑)
function uniqueBy(array, key) {
const seen = new Set();
return array.filter(item => {
const value = item[key];
if (seen.has(value)) {
return false;
}
seen.add(value);
return true;
});
}
Map 用于数据缓存:
class Cache {
constructor() {
this.cache = new Map();
}
get(key) {
return this.cache.get(key);
}
set(key, value, ttl = 60000) { // 默认1分钟过期
const expireTime = Date.now() + ttl;
this.cache.set(key, { value, expireTime });
}
has(key) {
const item = this.cache.get(key);
if (item && item.expireTime > Date.now()) {
return true;
}
this.cache.delete(key); // 清理过期数据
return false;
}
clear() {
this.cache.clear();
}
}
性能特点:
转换操作:
// Set 与 Array 转换
const array = [1, 2, 3];
const set = new Set(array);
const backToArray = [...set];
// Map 与 Object 转换
const obj = { a: 1, b: 2 };
const map = new Map(Object.entries(obj));
const backToObj = Object.fromEntries(map);
// Map 与 Array 转换
const mapArray = [...map]; // [['a', 1], ['b', 2]]
const mapFromArray = new Map(mapArray);
What are WeakMap and WeakSet? What are their application scenarios?
What are WeakMap and WeakSet? What are their application scenarios?
考察点:内存管理与垃圾回收。
答案:
WeakMap 和 WeakSet 是 Map 和 Set 的"弱引用"版本,它们的键或值是弱引用,不会阻止垃圾回收器回收对象,主要用于避免内存泄漏。
WeakMap 特点和使用:
基本特性:
const wm = new WeakMap();
// 键必须是对象(不能是原始值)
const obj1 = {};
const obj2 = {};
wm.set(obj1, 'value1');
wm.set(obj2, 'value2');
console.log(wm.get(obj1)); // 'value1'
console.log(wm.has(obj2)); // true
// 当对象被垃圾回收时,对应的键值对也会自动删除
// 无法遍历 WeakMap(没有 keys()、values()、entries() 方法)
// 无法获取大小(没有 size 属性)
私有数据存储:
const privateData = new WeakMap();
class Person {
constructor(name, age) {
// 使用对象实例作为键存储私有数据
privateData.set(this, { name, age });
}
getName() {
return privateData.get(this).name;
}
setAge(age) {
privateData.get(this).age = age;
}
getAge() {
return privateData.get(this).age;
}
}
const person = new Person('Alice', 25);
console.log(person.getName()); // 'Alice'
// 无法直接访问私有数据
DOM 元素关联数据:
const elementData = new WeakMap();
function attachData(element, data) {
elementData.set(element, data);
}
function getData(element) {
return elementData.get(element);
}
// 当 DOM 元素被删除时,相关数据也会被自动清理
const button = document.getElementById('myButton');
attachData(button, { clickCount: 0, lastClicked: null });
WeakSet 特点和使用:
基本特性:
const ws = new WeakSet();
const obj1 = {};
const obj2 = {};
ws.add(obj1);
ws.add(obj2);
console.log(ws.has(obj1)); // true
ws.delete(obj2);
// 值必须是对象,不能遍历,没有 size 属性
对象标记和状态跟踪:
const processedObjects = new WeakSet();
function processObject(obj) {
if (processedObjects.has(obj)) {
console.log('对象已处理过');
return;
}
// 处理对象逻辑
console.log('处理对象中...');
// 标记为已处理
processedObjects.add(obj);
}
const data = { id: 1, value: 'test' };
processObject(data); // "处理对象中..."
processObject(data); // "对象已处理过"
应用场景对比:
WeakMap 应用场景:
// 场景1:对象metadata存储
const metadata = new WeakMap();
class Component {
constructor(props) {
metadata.set(this, {
createdAt: Date.now(),
props: { ...props },
renderCount: 0
});
}
render() {
const meta = metadata.get(this);
meta.renderCount++;
meta.lastRendered = Date.now();
}
}
// 场景2:缓存计算结果
const cache = new WeakMap();
function expensiveOperation(obj) {
if (cache.has(obj)) {
return cache.get(obj);
}
const result = performComplexCalculation(obj);
cache.set(obj, result);
return result;
}
WeakSet 应用场景:
// 场景1:循环引用检测
function stringify(obj, seen = new WeakSet()) {
if (seen.has(obj)) {
return '[Circular Reference]';
}
seen.add(obj);
if (typeof obj === 'object' && obj !== null) {
const result = {};
for (const key in obj) {
result[key] = stringify(obj[key], seen);
}
return result;
}
return obj;
}
// 场景2:权限控制
const authorizedUsers = new WeakSet();
function authorize(user) {
authorizedUsers.add(user);
}
function isAuthorized(user) {
return authorizedUsers.has(user);
}
function performAction(user, action) {
if (!isAuthorized(user)) {
throw new Error('未授权访问');
}
// 执行操作
}
与 Map/Set 的区别:
| 特性 | Map/Set | WeakMap/WeakSet |
|---|---|---|
| 键/值类型 | 任意类型 | 只能是对象 |
| 垃圾回收 | 阻止回收 | 不阻止回收 |
| 可遍历性 | 可遍历 | 不可遍历 |
| size 属性 | 有 | 无 |
| 内存泄漏风险 | 有风险 | 避免泄漏 |
使用建议:
How does ES6 implement shallow copy and deep copy of objects?
How does ES6 implement shallow copy and deep copy of objects?
考察点:扩展运算符、Object.assign、递归。
答案:
ES6 提供了多种实现对象拷贝的方法,包括浅拷贝和深拷贝。理解两者的区别和适用场景对于避免意外的数据修改非常重要。
浅拷贝实现方法:
扩展运算符(…):
const original = { name: 'Alice', age: 25, hobbies: ['reading', 'music'] };
const shallowCopy = { ...original };
shallowCopy.name = 'Bob'; // 不影响原对象
shallowCopy.hobbies.push('sports'); // 影响原对象(引用类型)
console.log(original.hobbies); // ['reading', 'music', 'sports']
Object.assign():
const original = { name: 'Alice', profile: { age: 25, city: 'Beijing' } };
const shallowCopy = Object.assign({}, original);
shallowCopy.name = 'Bob'; // 不影响原对象
shallowCopy.profile.age = 30; // 影响原对象(嵌套对象)
console.log(original.profile.age); // 30
数组浅拷贝:
const originalArray = [1, 2, { name: 'Alice' }];
// 方法1:扩展运算符
const copy1 = [...originalArray];
// 方法2:Array.from()
const copy2 = Array.from(originalArray);
// 方法3:slice()
const copy3 = originalArray.slice();
copy1[2].name = 'Bob'; // 影响原数组(对象引用)
深拷贝实现方法:
JSON 序列化(简单场景):
const original = {
name: 'Alice',
age: 25,
profile: {
city: 'Beijing',
hobbies: ['reading', 'music']
}
};
// 简单深拷贝(有限制)
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.profile.city = 'Shanghai';
console.log(original.profile.city); // 'Beijing'(不受影响)
// 限制:无法处理函数、undefined、Symbol、Date 等
递归深拷贝实现:
function deepClone(obj, visited = new WeakMap()) {
// 处理 null 和基本类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (visited.has(obj)) {
return visited.get(obj);
}
// 处理日期对象
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则表达式
if (obj instanceof RegExp) {
return new RegExp(obj);
}
// 处理数组
if (Array.isArray(obj)) {
const cloned = [];
visited.set(obj, cloned);
for (let i = 0; i < obj.length; i++) {
cloned[i] = deepClone(obj[i], visited);
}
return cloned;
}
// 处理普通对象
const cloned = {};
visited.set(obj, cloned);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key], visited);
}
}
// 处理 Symbol 属性
const symbolKeys = Object.getOwnPropertySymbols(obj);
for (const symKey of symbolKeys) {
cloned[symKey] = deepClone(obj[symKey], visited);
}
return cloned;
}
// 使用示例
const complexObj = {
name: 'Alice',
date: new Date(),
regex: /test/g,
nested: { value: 42 },
arr: [1, 2, { deep: 'value' }]
};
const deepCopy = deepClone(complexObj);
使用 Lodash 库(生产环境推荐):
const _ = require('lodash');
const original = { /* 复杂对象 */ };
const deepCopy = _.cloneDeep(original);
实用的拷贝工具函数:
class CopyUtils {
// 智能拷贝:根据需要选择浅拷贝或深拷贝
static smartCopy(obj, deep = false) {
if (deep) {
return this.deepClone(obj);
}
if (Array.isArray(obj)) {
return [...obj];
}
if (obj && typeof obj === 'object') {
return { ...obj };
}
return obj;
}
// 合并对象(支持深度合并)
static merge(target, source, deep = false) {
const result = deep ? this.deepClone(target) : { ...target };
for (const key in source) {
if (deep &&
result[key] &&
typeof result[key] === 'object' &&
typeof source[key] === 'object') {
result[key] = this.merge(result[key], source[key], true);
} else {
result[key] = deep ? this.deepClone(source[key]) : source[key];
}
}
return result;
}
static deepClone(obj) {
// 这里使用前面实现的 deepClone 函数
return deepClone(obj);
}
}
// 使用示例
const original = { a: 1, b: { c: 2 } };
const shallow = CopyUtils.smartCopy(original, false);
const deep = CopyUtils.smartCopy(original, true);
性能对比和选择建议:
| 方法 | 性能 | 适用场景 | 限制 |
|---|---|---|---|
| 扩展运算符 | 高 | 简单对象浅拷贝 | 只能浅拷贝 |
| Object.assign | 高 | 对象合并和浅拷贝 | 只能浅拷贝 |
| JSON 方法 | 中等 | 简单数据深拷贝 | 不支持函数、Date等 |
| 递归实现 | 较低 | 复杂对象深拷贝 | 实现复杂 |
| Lodash | 中等 | 生产环境深拷贝 | 需要引入库 |
使用建议:
What is async/await? What is its relationship with Promise?
What is async/await? What is its relationship with Promise?
考察点:异步语法糖与错误处理。
答案:
async/await 是 ES2017(ES8)引入的异步编程语法糖,基于 Promise 构建,提供了更接近同步代码风格的异步操作写法,使异步代码更易读和维护。
与 Promise 的关系:
async/await 本质上就是 Promise:
// Promise 写法
function fetchUserData() {
return fetch('/api/user')
.then(response => response.json())
.then(user => {
return fetch(`/api/users/${user.id}/posts`);
})
.then(response => response.json())
.catch(error => {
console.error('获取数据失败:', error);
});
}
// async/await 写法(等价)
async function fetchUserData() {
try {
const response = await fetch('/api/user');
const user = await response.json();
const postsResponse = await fetch(`/api/users/${user.id}/posts`);
const posts = await postsResponse.json();
return posts;
} catch (error) {
console.error('获取数据失败:', error);
}
}
async 函数返回 Promise:
async function getName() {
return 'Alice'; // 等价于 return Promise.resolve('Alice')
}
// 调用 async 函数
getName().then(name => console.log(name)); // 'Alice'
// 或者在另一个 async 函数中
async function greet() {
const name = await getName();
console.log(`Hello, ${name}!`);
}
基本语法和用法:
async 函数声明:
// 函数声明
async function fetchData() {
// 异步操作
}
// 函数表达式
const fetchData = async function() {
// 异步操作
};
// 箭头函数
const fetchData = async () => {
// 异步操作
};
// 对象方法
const api = {
async getData() {
// 异步操作
}
};
// 类方法
class DataService {
async fetchData() {
// 异步操作
}
}
await 表达式:
async function processData() {
// await 只能在 async 函数内使用
const data = await fetchData(); // 等待 Promise 完成
const processed = await processStep(data);
return processed;
}
// 错误用法:await 不能在普通函数中使用
function normalFunction() {
// const data = await fetchData(); // SyntaxError
}
错误处理:
try-catch 处理:
async function handleErrors() {
try {
const response = await fetch('/api/data');
const data = await response.json();
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return data;
} catch (error) {
console.error('操作失败:', error);
throw error; // 重新抛出或处理
}
}
混合错误处理:
async function mixedErrorHandling() {
try {
const data = await fetchData().catch(err => {
console.log('fetch 错误:', err);
return null; // 提供默认值
});
if (data) {
return await processData(data);
}
return 'default data';
} catch (error) {
console.error('处理错误:', error);
}
}
并发处理:
async function parallelOperations() {
// 错误做法:串行执行
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();
// 正确做法:并行执行
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return { user, posts, comments };
}
// 使用 Promise.allSettled 处理部分失败
async function robustParallelOperations() {
const results = await Promise.allSettled([
fetchUser(),
fetchPosts(),
fetchComments()
]);
const [userResult, postsResult, commentsResult] = results;
return {
user: userResult.status === 'fulfilled' ? userResult.value : null,
posts: postsResult.status === 'fulfilled' ? postsResult.value : [],
comments: commentsResult.status === 'fulfilled' ? commentsResult.value : []
};
}
实际应用示例:
API 调用链:
class UserService {
async getUserProfile(userId) {
try {
// 获取用户基本信息
const user = await this.fetchUser(userId);
// 并行获取相关数据
const [posts, followers, following] = await Promise.all([
this.fetchUserPosts(userId),
this.fetchFollowers(userId),
this.fetchFollowing(userId)
]);
return {
...user,
postsCount: posts.length,
followersCount: followers.length,
followingCount: following.length,
recentPosts: posts.slice(0, 5)
};
} catch (error) {
console.error('获取用户资料失败:', error);
throw new Error('无法加载用户资料');
}
}
async fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('用户不存在');
return response.json();
}
}
文件处理:
async function processFiles(fileList) {
const results = [];
for (const file of fileList) {
try {
const content = await readFile(file);
const processed = await processContent(content);
const saved = await saveResult(processed);
results.push({ file: file.name, success: true, id: saved.id });
} catch (error) {
results.push({ file: file.name, success: false, error: error.message });
}
}
return results;
}
性能优化技巧:
// 避免不必要的串行等待
async function optimizedFlow() {
// 立即启动所有异步操作
const userPromise = fetchUser();
const configPromise = fetchConfig();
// 需要用户数据的操作
const user = await userPromise;
const postsPromise = fetchUserPosts(user.id);
// 等待所有操作完成
const [config, posts] = await Promise.all([
configPromise,
postsPromise
]);
return { user, config, posts };
}
async/await 的优势:
What is dynamic import of modules? How to use it?
What is dynamic import of modules? How to use it?
考察点:import() 语法与懒加载。
答案:
动态导入是 ES2020 引入的特性,允许在运行时按需加载模块。与静态导入不同,动态导入使用 import() 函数,返回一个 Promise,支持条件加载和懒加载。
基本语法:
// 动态导入语法
import(moduleSpecifier)
.then(module => {
// 使用模块
})
.catch(error => {
// 处理加载错误
});
// 使用 async/await
async function loadModule() {
try {
const module = await import('./module.js');
return module;
} catch (error) {
console.error('模块加载失败:', error);
}
}
主要使用场景:
条件加载:
async function loadFeature(featureName) {
let module;
switch (featureName) {
case 'chart':
module = await import('./charts/ChartComponent.js');
break;
case 'editor':
module = await import('./editor/EditorComponent.js');
break;
case 'calendar':
module = await import('./calendar/CalendarComponent.js');
break;
default:
throw new Error(`未知功能: ${featureName}`);
}
return module.default;
}
// 根据用户选择加载不同功能
document.getElementById('loadChart').addEventListener('click', async () => {
const ChartComponent = await loadFeature('chart');
new ChartComponent().render();
});
路由懒加载:
// React Router 懒加载示例
const routes = [
{
path: '/home',
component: () => import('./components/Home.js')
},
{
path: '/about',
component: () => import('./components/About.js')
},
{
path: '/contact',
component: () => import('./components/Contact.js')
}
];
// Vue Router 懒加载
const router = new VueRouter({
routes: [
{
path: '/user',
component: () => import('./views/User.vue')
}
]
});
功能检测和降级:
async function loadPolyfill() {
// 检测浏览器是否支持某个特性
if (!window.IntersectionObserver) {
await import('./polyfills/intersection-observer.js');
}
if (!window.fetch) {
await import('./polyfills/fetch.js');
}
}
// 根据环境加载不同实现
async function loadStorage() {
if (typeof window !== 'undefined') {
// 浏览器环境
return import('./storage/browser-storage.js');
} else {
// Node.js 环境
return import('./storage/node-storage.js');
}
}
代码分割和性能优化:
class App {
async loadHeavyFeature() {
// 显示加载指示器
this.showLoading();
try {
// 动态加载大型功能模块
const heavyModule = await import('./heavy-feature.js');
const feature = new heavyModule.HeavyFeature();
// 初始化功能
await feature.initialize();
this.hideLoading();
return feature;
} catch (error) {
this.hideLoading();
this.showError('功能加载失败');
throw error;
}
}
}
与静态导入的对比:
// 静态导入 - 编译时确定
import { utils } from './utils.js';
import defaultExport from './module.js';
// 动态导入 - 运行时加载
const utils = await import('./utils.js');
const defaultExport = (await import('./module.js')).default;
// 静态导入的限制
if (condition) {
// import './module.js'; // 语法错误:不能在块级作用域中使用
}
// 动态导入的灵活性
if (condition) {
const module = await import('./module.js'); // 完全可以
}
高级用法:
批量动态导入:
async function loadPlugins(pluginNames) {
const plugins = await Promise.all(
pluginNames.map(name =>
import(`./plugins/${name}.js`)
.catch(err => {
console.warn(`插件 ${name} 加载失败:`, err);
return null;
})
)
);
return plugins.filter(plugin => plugin !== null);
}
缓存机制:
const moduleCache = new Map();
async function importWithCache(moduleSpecifier) {
if (moduleCache.has(moduleSpecifier)) {
return moduleCache.get(moduleSpecifier);
}
const modulePromise = import(moduleSpecifier);
moduleCache.set(moduleSpecifier, modulePromise);
try {
return await modulePromise;
} catch (error) {
// 如果加载失败,从缓存中移除
moduleCache.delete(moduleSpecifier);
throw error;
}
}
模块预加载:
class ModuleManager {
constructor() {
this.preloadPromises = new Map();
}
// 预加载模块但不执行
preload(moduleSpecifier) {
if (!this.preloadPromises.has(moduleSpecifier)) {
const link = document.createElement('link');
link.rel = 'modulepreload';
link.href = moduleSpecifier;
document.head.appendChild(link);
this.preloadPromises.set(
moduleSpecifier,
import(moduleSpecifier)
);
}
return this.preloadPromises.get(moduleSpecifier);
}
// 使用预加载的模块
async use(moduleSpecifier) {
return await this.preload(moduleSpecifier);
}
}
注意事项:
浏览器兼容性和打包工具支持:
How does ES6 implement rest parameters and spread parameters in function parameters?
How does ES6 implement rest parameters and spread parameters in function parameters?
考察点:…rest 与 …spread。
答案:
ES6 通过三个点(…)语法提供了剩余参数(Rest Parameters)和展开参数(Spread Parameters)功能。虽然使用相同的语法,但在不同位置有不同的含义和作用。
剩余参数(Rest Parameters):
收集多余的参数:
// 基本用法:收集剩余参数为数组
function sum(first, ...rest) {
console.log('first:', first); // 第一个参数
console.log('rest:', rest); // 剩余参数组成的数组
return first + rest.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4, 5);
// first: 1
// rest: [2, 3, 4, 5]
// 返回: 15
替代 arguments 对象:
// ES5 使用 arguments(类数组对象)
function oldSum() {
var args = Array.prototype.slice.call(arguments);
return args.reduce(function(a, b) { return a + b; }, 0);
}
// ES6 使用剩余参数(真正的数组)
function newSum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
// 剩余参数具有数组的所有方法
function processNumbers(...nums) {
return nums
.filter(n => n > 0) // 过滤正数
.map(n => n * 2) // 乘以2
.sort((a, b) => a - b); // 排序
}
与普通参数结合:
function greet(greeting, ...names) {
return `${greeting} ${names.join(', ')}!`;
}
greet('Hello', 'Alice', 'Bob', 'Charlie');
// "Hello Alice, Bob, Charlie!"
// 剩余参数必须是最后一个参数
function invalid(first, ...rest, last) {} // 语法错误
展开参数(Spread Parameters):
展开数组作为函数参数:
function multiply(a, b, c) {
return a * b * c;
}
const numbers = [2, 3, 4];
// ES5 使用 apply
var result1 = multiply.apply(null, numbers);
// ES6 使用展开语法
const result2 = multiply(...numbers);
console.log(result2); // 24 (2 * 3 * 4)
数学运算中的应用:
const scores = [85, 92, 78, 96, 88];
// 找出最大值和最小值
const max = Math.max(...scores); // 96
const min = Math.min(...scores); // 78
// 组合多个数组
const moreScores = [91, 87];
const allScores = [...scores, ...moreScores]; // [85, 92, 78, 96, 88, 91, 87]
高级应用场景:
函数重载模拟:
function createUser(name, ...options) {
const config = {
name,
age: 18,
active: true,
roles: ['user']
};
// 处理不同类型的参数
options.forEach(option => {
if (typeof option === 'number') {
config.age = option;
} else if (typeof option === 'boolean') {
config.active = option;
} else if (Array.isArray(option)) {
config.roles = option;
} else if (typeof option === 'object') {
Object.assign(config, option);
}
});
return config;
}
// 各种调用方式
createUser('Alice'); // 默认配置
createUser('Bob', 25); // 指定年龄
createUser('Charlie', 30, false); // 指定年龄和状态
createUser('David', ['admin', 'user']); // 指定角色
createUser('Eve', 28, true, ['moderator'], {city: 'Beijing'}); // 混合参数
柯里化函数实现:
function curry(fn, ...fixedArgs) {
return function(...newArgs) {
const allArgs = [...fixedArgs, ...newArgs];
if (allArgs.length >= fn.length) {
return fn(...allArgs);
} else {
return curry(fn, ...allArgs);
}
};
}
// 使用示例
function add(a, b, c) {
return a + b + c;
}
const addCurried = curry(add);
const add5 = addCurried(5);
const add5And10 = add5(10);
console.log(add5And10(15)); // 30
事件处理器工厂:
function createEventHandler(baseHandler, ...middlewares) {
return function(event, ...extraArgs) {
// 执行中间件
for (const middleware of middlewares) {
const result = middleware(event, ...extraArgs);
if (result === false) {
return; // 中断执行
}
}
// 执行基础处理器
return baseHandler(event, ...extraArgs);
};
}
// 中间件函数
const logger = (event) => console.log('Event:', event.type);
const validator = (event) => event.target.checkValidity();
// 创建带中间件的事件处理器
const submitHandler = createEventHandler(
(event, formData) => {
console.log('提交表单:', formData);
},
logger,
validator
);
参数解构与剩余参数结合:
function processConfig({host, port, ...settings}, ...plugins) {
console.log('连接信息:', { host, port });
console.log('其他设置:', settings);
console.log('插件列表:', plugins);
return {
connection: { host, port },
settings,
plugins: plugins.map(plugin => plugin.name || plugin)
};
}
processConfig(
{
host: 'localhost',
port: 3000,
debug: true,
timeout: 5000
},
{ name: 'auth', version: '1.0' },
{ name: 'cors', version: '2.0' },
'logger'
);
与箭头函数的使用:
// 剩余参数在箭头函数中
const sum = (...numbers) => numbers.reduce((a, b) => a + b, 0);
// 展开参数在箭头函数调用中
const numbers = [1, 2, 3, 4, 5];
const result = sum(...numbers);
// 高阶函数应用
const pipe = (...functions) => (value) =>
functions.reduce((acc, fn) => fn(acc), value);
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const transform = pipe(addOne, double, square);
console.log(transform(3)); // ((3 + 1) * 2)² = 64
注意事项和最佳实践:
What are default values in destructuring assignment? What should be noted?
What are default values in destructuring assignment? What should be noted?
考察点:语法细节与边界情况。
答案:
解构赋值中的默认值是当解构目标为 undefined 时使用的备用值。它提供了一种优雅的方式来处理可能缺失的数据,避免程序出错。
基本语法和使用:
数组解构的默认值:
// 基本默认值
const [a = 1, b = 2, c = 3] = [10, 20];
console.log(a, b, c); // 10, 20, 3
// 只有 undefined 才触发默认值
const [x = 5, y = 6] = [0, null];
console.log(x, y); // 0, null (0和null不会触发默认值)
// undefined 触发默认值
const [m = 100, n = 200] = [undefined, undefined];
console.log(m, n); // 100, 200
对象解构的默认值:
// 基本对象解构默认值
const {name = 'Anonymous', age = 18, city = 'Unknown'} = {
name: 'Alice',
age: 25
};
console.log(name, age, city); // 'Alice', 25, 'Unknown'
// 重命名与默认值结合
const {name: userName = 'Guest', score: userScore = 0} = {name: 'Bob'};
console.log(userName, userScore); // 'Bob', 0
默认值的计算时机:
// 默认值是惰性计算的,只有在需要时才执行
function getDefault() {
console.log('计算默认值');
return 'default';
}
const [a = getDefault()] = ['actual'];
// 不会输出 "计算默认值",因为 a 有值
const [b = getDefault()] = [undefined];
// 输出 "计算默认值",因为 b 是 undefined
复杂场景的默认值:
嵌套解构的默认值:
// 嵌套对象解构
const {
user: {
name = 'Unknown User',
profile: {
age = 0,
email = 'no-email'
} = {}
} = {}
} = {
user: {
name: 'Alice'
// profile 缺失,使用默认值 {}
}
};
console.log(name, age, email); // 'Alice', 0, 'no-email'
函数参数解构的默认值:
// 参数解构与默认值
function createUser({
name = 'Anonymous',
age = 18,
roles = ['user'],
settings = {theme: 'light', lang: 'en'}
} = {}) {
return { name, age, roles, settings };
}
// 各种调用方式
console.log(createUser()); // 全部使用默认值
console.log(createUser({name: 'Alice'})); // 部分使用默认值
console.log(createUser({age: 25, roles: ['admin']})); // 混合使用
数组解构中的对象默认值:
const [
first = {id: 1, name: 'First'},
second = {id: 2, name: 'Second'}
] = [
{id: 10, name: 'Actual First'}
// second 使用默认值
];
console.log(first); // {id: 10, name: 'Actual First'}
console.log(second); // {id: 2, name: 'Second'}
重要注意事项:
undefined vs null 的区别:
// 只有 undefined 触发默认值
const {a = 10, b = 20} = {a: null, b: undefined};
console.log(a, b); // null, 20
// 0, false, '', NaN 都不会触发默认值
const {x = 1, y = 2, z = 3} = {x: 0, y: false, z: ''};
console.log(x, y, z); // 0, false, ''
默认值可以引用其他变量:
// 默认值可以是表达式
let defaultAge = 18;
const {name, age = defaultAge * 2} = {name: 'Alice'};
console.log(age); // 36
// 默认值可以引用前面解构的变量
const {first = 'default', second = first + '2'} = {first: 'test'};
console.log(first, second); // 'test', 'test2'
// 但不能引用后面的变量(TDZ - 暂时性死区)
const {a = b, b = 1} = {}; // ReferenceError
默认值与剩余参数结合:
function processItems([first = 'default', ...rest] = []) {
console.log('First:', first);
console.log('Rest:', rest);
}
processItems(); // First: default, Rest: []
processItems(['item1']); // First: item1, Rest: []
processItems(['a', 'b', 'c']); // First: a, Rest: ['b', 'c']
实际应用场景:
API 响应处理:
async function fetchUserProfile(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// 使用默认值处理可能缺失的字段
const {
name = 'Unknown User',
email = '[email protected]',
avatar = '/default-avatar.png',
preferences: {
theme = 'light',
language = 'en',
notifications = true
} = {},
stats: {
posts = 0,
followers = 0,
following = 0
} = {}
} = data;
return {
name, email, avatar,
preferences: { theme, language, notifications },
stats: { posts, followers, following }
};
} catch (error) {
// 返回完全的默认用户对象
return {
name: 'Unknown User',
email: '[email protected]',
avatar: '/default-avatar.png',
preferences: { theme: 'light', language: 'en', notifications: true },
stats: { posts: 0, followers: 0, following: 0 }
};
}
}
配置对象处理:
function initializeApp(config = {}) {
const {
apiUrl = 'http://localhost:3000',
timeout = 5000,
retries = 3,
cache: {
enabled = true,
ttl = 300000 // 5分钟
} = {},
features: {
darkMode = false,
analytics = false,
notifications = true
} = {},
endpoints: {
auth = '/auth',
users = '/users',
data = '/data'
} = {}
} = config;
return {
api: { url: apiUrl, timeout, retries },
cache: { enabled, ttl },
features: { darkMode, analytics, notifications },
endpoints: { auth, users, data }
};
}
性能考虑和最佳实践:
What common methods did ES6 add to Object? Please give examples.
What common methods did ES6 add to Object? Please give examples.
考察点:对象操作API的掌握。
答案:
ES6 为 Object 新增了多个实用的静态方法,这些方法提供了更强大和便捷的对象操作能力,涵盖对象创建、属性操作、比较等方面。
主要新增方法:
Object.assign() - 对象合并和拷贝:
// 基本用法:浅拷贝和合并
const target = { a: 1, b: 2 };
const source1 = { b: 3, c: 4 };
const source2 = { c: 5, d: 6 };
const result = Object.assign(target, source1, source2);
console.log(result); // { a: 1, b: 3, c: 5, d: 6 }
console.log(target === result); // true (target 被修改)
// 创建新对象(不修改原对象)
const newObj = Object.assign({}, target, source1);
// 克隆对象
const clone = Object.assign({}, original);
Object.is() - 严格相等比较:
// 与 === 的区别
console.log(Object.is(NaN, NaN)); // true (=== 返回 false)
console.log(Object.is(+0, -0)); // false (=== 返回 true)
console.log(Object.is(5, 5)); // true
console.log(Object.is('hello', 'hello')); // true
// 实际应用
function safeComparison(a, b) {
return Object.is(a, b);
}
// React 等框架用于判断状态是否真正改变
if (!Object.is(prevState, nextState)) {
// 状态确实改变了
}
Object.setPrototypeOf() 和 Object.getPrototypeOf():
// 设置对象原型
const animal = {
makeSound() {
console.log('Some generic sound');
}
};
const dog = {
name: 'Buddy'
};
Object.setPrototypeOf(dog, animal);
dog.makeSound(); // 'Some generic sound'
// 获取对象原型
console.log(Object.getPrototypeOf(dog) === animal); // true
// 检查原型链
console.log(animal.isPrototypeOf(dog)); // true
Object.keys(), Object.values(), Object.entries():
const person = {
name: 'Alice',
age: 30,
city: 'Beijing'
};
// 获取所有键
const keys = Object.keys(person);
console.log(keys); // ['name', 'age', 'city']
// 获取所有值
const values = Object.values(person);
console.log(values); // ['Alice', 30, 'Beijing']
// 获取键值对数组
const entries = Object.entries(person);
console.log(entries); // [['name', 'Alice'], ['age', 30], ['city', 'Beijing']]
// 实际应用:对象转换
const doubled = Object.fromEntries(
Object.entries(person).map(([key, value]) => [
key,
typeof value === 'number' ? value * 2 : value
])
);
ES6+ 后续新增的重要方法:
Object.fromEntries() - 从键值对创建对象(ES2019):
// 从 Map 创建对象
const map = new Map([
['name', 'Alice'],
['age', 25]
]);
const obj = Object.fromEntries(map);
console.log(obj); // { name: 'Alice', age: 25 }
// 从数组创建对象
const pairs = [['a', 1], ['b', 2], ['c', 3]];
const result = Object.fromEntries(pairs);
console.log(result); // { a: 1, b: 2, c: 3 }
// 与 Object.entries() 配合使用
const original = { x: 1, y: 2, z: 3 };
const transformed = Object.fromEntries(
Object.entries(original).map(([key, value]) => [key.toUpperCase(), value * 2])
);
console.log(transformed); // { X: 2, Y: 4, Z: 6 }
Object.getOwnPropertyDescriptors() - 获取所有属性描述符:
const obj = {
name: 'Alice',
get fullInfo() {
return `${this.name} - ${this.age}`;
}
};
const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
/*
{
name: { value: 'Alice', writable: true, enumerable: true, configurable: true },
fullInfo: { get: [Function: get fullInfo], set: undefined, enumerable: true, configurable: true }
}
*/
// 完整克隆对象(包括属性描述符)
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
实际应用场景:
配置对象合并:
function createConfig(userConfig = {}) {
const defaultConfig = {
apiUrl: 'http://localhost:3000',
timeout: 5000,
retries: 3,
cache: true
};
// 合并配置,用户配置优先
return Object.assign({}, defaultConfig, userConfig);
}
const config = createConfig({ timeout: 8000, debug: true });
// { apiUrl: 'http://localhost:3000', timeout: 8000, retries: 3, cache: true, debug: true }
对象转换和过滤:
function filterObject(obj, predicate) {
return Object.fromEntries(
Object.entries(obj).filter(([key, value]) => predicate(key, value))
);
}
function mapObject(obj, mapper) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [key, mapper(value, key)])
);
}
const data = { a: 1, b: 2, c: 3, d: 4 };
// 过滤偶数值
const evenValues = filterObject(data, (key, value) => value % 2 === 0);
console.log(evenValues); // { b: 2, d: 4 }
// 将所有值乘以2
const doubled = mapObject(data, value => value * 2);
console.log(doubled); // { a: 2, b: 4, c: 6, d: 8 }
深度合并工具:
function deepMerge(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
deepMerge(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return deepMerge(target, ...sources);
}
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
对象属性统计:
function analyzeObject(obj) {
const entries = Object.entries(obj);
return {
keyCount: Object.keys(obj).length,
valueTypes: entries.reduce((acc, [key, value]) => {
const type = typeof value;
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {}),
hasGetters: Object.getOwnPropertyDescriptors(obj),
isEmpty: Object.keys(obj).length === 0
};
}
const sample = {
name: 'test',
count: 42,
active: true,
data: null,
get computed() { return this.count * 2; }
};
console.log(analyzeObject(sample));
性能和使用建议:
How to use destructuring assignment in function parameters? What are the advanced uses?
How to use destructuring assignment in function parameters? What are the advanced uses?
考察点:函数参数解构的灵活应用。
答案:
函数参数解构是 ES6 解构赋值的重要应用场景,它让函数调用更加灵活和直观,特别适合处理配置对象、选项参数等复杂场景。
基础对象参数解构:
简单对象参数解构:
// 传统方式
function createUser(options) {
const name = options.name;
const age = options.age;
const email = options.email || '[email protected]';
}
// 解构方式
function createUser({ name, age, email = '[email protected]' }) {
console.log(`Creating user: ${name}, ${age}, ${email}`);
return { name, age, email, id: Date.now() };
}
// 调用
createUser({ name: 'Alice', age: 25 });
// Creating user: Alice, 25, [email protected]
嵌套对象解构:
function updateProfile({
user: { name, id },
settings: { theme = 'light', notifications = true } = {},
metadata: { lastLogin, createdAt } = {}
}) {
return {
userId: id,
displayName: name,
preferences: { theme, notifications },
timestamps: { lastLogin, createdAt }
};
}
// 调用
const result = updateProfile({
user: { name: 'Bob', id: 123 },
settings: { theme: 'dark' },
metadata: { lastLogin: new Date() }
});
数组参数解构:
// 处理坐标点
function calculateDistance([x1, y1], [x2, y2]) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
const pointA = [0, 0];
const pointB = [3, 4];
console.log(calculateDistance(pointA, pointB)); // 5
// 处理多个数值
function sum([first, second, ...rest]) {
return first + second + rest.reduce((acc, num) => acc + num, 0);
}
console.log(sum([1, 2, 3, 4, 5])); // 15
高级用法和模式:
默认参数与解构结合:
// 复杂的默认配置
function apiRequest({
url,
method = 'GET',
headers = {},
timeout = 5000,
retries = 3,
cache = true,
auth: { token, type = 'Bearer' } = {}
} = {}) {
const config = {
url,
method,
headers: {
'Content-Type': 'application/json',
...headers,
...(token && { Authorization: `${type} ${token}` })
},
timeout,
retries,
cache
};
return fetch(url, config);
}
// 各种调用方式
apiRequest(); // 使用全部默认值
apiRequest({ url: '/api/users' }); // 只指定 URL
apiRequest({
url: '/api/users',
method: 'POST',
auth: { token: 'abc123' }
}); // 指定部分参数
重命名和计算属性解构:
function processData({
user_name: userName, // 重命名
user_age: age,
'user-email': email, // 特殊键名
[Symbol.iterator]: iterator, // Symbol 属性
...otherProps // 剩余属性
}) {
return {
profile: { userName, age, email },
metadata: otherProps,
hasIterator: !!iterator
};
}
const data = {
'user_name': 'Charlie',
'user_age': 28,
'user-email': '[email protected]',
department: 'Engineering',
level: 'Senior'
};
console.log(processData(data));
混合解构模式:
// 同时解构对象和数组
function analyzeUserActivity({
userId,
actions: [firstAction, ...restActions],
timestamps: { start, end },
metadata = {}
}) {
return {
user: userId,
summary: {
totalActions: restActions.length + 1,
firstAction,
duration: end - start,
metadata
}
};
}
const activityData = {
userId: 456,
actions: ['login', 'browse', 'purchase', 'logout'],
timestamps: { start: 1000, end: 5000 },
metadata: { browser: 'Chrome', os: 'Windows' }
};
console.log(analyzeUserActivity(activityData));
实际应用场景:
React 组件风格的函数:
// 类似 React 组件的 props 处理
function Button({
children,
onClick = () => {},
disabled = false,
variant = 'primary',
size = 'medium',
icon = null,
...htmlProps
}) {
const className = `btn btn-${variant} btn-${size} ${disabled ? 'disabled' : ''}`;
return {
element: 'button',
props: {
className,
disabled,
onClick,
...htmlProps
},
children: [icon, children].filter(Boolean)
};
}
// 使用
const button = Button({
children: 'Click me',
onClick: () => console.log('Clicked!'),
variant: 'success',
'data-testid': 'submit-btn'
});
配置合并工具:
function mergeConfigs(
baseConfig = {},
{
database: { host, port, name, ...dbConfig } = {},
server: { port: serverPort = 3000, ...serverConfig } = {},
logging: { level = 'info', ...logConfig } = {},
...otherConfig
} = {}
) {
return {
...baseConfig,
database: {
host: host || 'localhost',
port: port || 5432,
name: name || 'app',
...dbConfig
},
server: {
port: serverPort,
...serverConfig
},
logging: {
level,
...logConfig
},
...otherConfig
};
}
const config = mergeConfigs(
{ env: 'development' },
{
database: { host: 'db.example.com', ssl: true },
server: { port: 8080 },
redis: { url: 'redis://localhost:6379' }
}
);
高级技巧和模式:
条件解构和动态属性:
function processRequest({
method,
url,
body,
...config
}, options = {}) {
const { includeAuth = false, includeTiming = false } = options;
const baseConfig = { method, url };
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
baseConfig.body = JSON.stringify(body);
}
if (includeAuth && config.auth) {
const { auth: { token, type = 'Bearer' } } = config;
baseConfig.headers = {
...baseConfig.headers,
Authorization: `${type} ${token}`
};
}
if (includeTiming) {
const { timeout = 5000, retries = 0 } = config;
Object.assign(baseConfig, { timeout, retries });
}
return baseConfig;
}
带验证的解构模式:
function createProduct({
name,
price,
category,
inventory: { quantity, warehouse } = {},
metadata = {}
}) {
// 参数验证
if (!name || typeof name !== 'string') {
throw new Error('Name is required and must be a string');
}
if (!price || typeof price !== 'number' || price < 0) {
throw new Error('Price must be a positive number');
}
return {
id: Date.now(),
name: name.trim(),
price: Number(price.toFixed(2)),
category: category || 'general',
inventory: {
quantity: quantity || 0,
warehouse: warehouse || 'main'
},
metadata: {
createdAt: new Date(),
...metadata
}
};
}
性能和最佳实践:
...rest 应该有明确的用途= {}调试技巧:
// 保留原始参数用于调试
function debuggableFunction(params = {}) {
console.log('Original params:', params);
const {
name,
age,
settings: { theme = 'light' } = {}
} = params;
console.log('Destructured:', { name, age, theme });
// 函数逻辑...
}
What is tail call optimization? How does ES6 support tail call optimization?
What is tail call optimization? How does ES6 support tail call optimization?
考察点:性能优化与函数调用机制。
答案:
尾调用优化(Tail Call Optimization,TCO)是一种重要的编程语言优化技术,ES6 规范中首次在 JavaScript 中引入了尾调用优化的支持,旨在解决递归调用中的堆栈溢出问题。
什么是尾调用:
尾调用是指函数的最后一个操作是调用另一个函数,且该调用的返回值直接作为当前函数的返回值。
尾调用的定义和识别:
// 这是尾调用
function f(x) {
return g(x); // g(x) 是最后一个操作,直接返回结果
}
// 这不是尾调用 - 还有其他操作
function f(x) {
return g(x) + 1; // 调用 g(x) 后还要执行加法
}
function f(x) {
const result = g(x); // 调用后还要返回
return result;
}
// 这也不是尾调用 - 在表达式中间
function f(x) {
return 1 + g(x); // g(x) 不是最后执行的操作
}
严格的尾调用条件:
// ✅ 正确的尾调用
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc); // 直接返回递归调用结果
}
// ❌ 不是尾调用
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 递归调用后还要乘以 n
}
// ✅ 尾调用优化的斐波那契
function fibonacci(n, a = 0, b = 1) {
if (n === 0) return a;
if (n === 1) return b;
return fibonacci(n - 1, b, a + b); // 尾调用
}
// ❌ 非尾调用的斐波那契
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2); // 两次递归调用
}
尾调用优化的原理:
调用栈的问题:
// 传统递归 - 会造成栈溢出
function sum(n) {
if (n === 0) return 0;
return n + sum(n - 1); // 每次调用都要保持栈帧
}
// 调用栈状态示例 sum(3):
// sum(3) -> 3 + sum(2)
// sum(2) -> 2 + sum(1)
// sum(1) -> 1 + sum(0)
// sum(0) -> 0
// 需要保持4个栈帧
console.log(sum(10000)); // RangeError: Maximum call stack size exceeded
尾调用优化后的改进:
// 尾调用优化版本
function sum(n, acc = 0) {
if (n === 0) return acc;
return sum(n - 1, acc + n); // 尾调用,可以复用栈帧
}
// 优化后的调用栈(理论上):
// sum(3, 0) 复用栈帧-> sum(2, 3) 复用栈帧-> sum(1, 5) 复用栈帧-> sum(0, 6) -> 6
// 始终只需要1个栈帧
console.log(sum(10000)); // 50005000 (不会栈溢出)
ES6 尾调用优化的规范:
严格模式要求:
// 尾调用优化只在严格模式下生效
'use strict';
function countdown(n) {
console.log(n);
if (n === 0) return 'Done!';
return countdown(n - 1); // 严格模式下可能被优化
}
// 非严格模式下不会优化
function nonStrictCountdown(n) {
console.log(n);
if (n === 0) return 'Done!';
return arguments.callee(n - 1); // 使用了 arguments,无法优化
}
优化条件的限制:
'use strict';
// ✅ 可以优化的尾调用
function optimizable(n) {
if (n <= 0) return 0;
return optimizable(n - 1);
}
// ❌ 不能优化 - 访问了 arguments
function notOptimizable1(n) {
if (n <= 0) return 0;
console.log(arguments); // 访问 arguments 阻止优化
return notOptimizable1(n - 1);
}
// ❌ 不能优化 - 闭包引用了当前函数
function notOptimizable2(n) {
if (n <= 0) return 0;
const inner = () => notOptimizable2; // 闭包引用阻止优化
return notOptimizable2(n - 1);
}
// ❌ 不能优化 - try/catch 语句
function notOptimizable3(n) {
try {
if (n <= 0) return 0;
return notOptimizable3(n - 1);
} catch (e) {
return -1;
}
}
实际应用示例:
数组处理的尾调用优化:
'use strict';
// 尾递归版本的数组求和
function arraySum(arr, index = 0, acc = 0) {
if (index >= arr.length) return acc;
return arraySum(arr, index + 1, acc + arr[index]);
}
// 尾递归版本的数组过滤
function arrayFilter(arr, predicate, index = 0, result = []) {
if (index >= arr.length) return result;
const newResult = predicate(arr[index])
? [...result, arr[index]]
: result;
return arrayFilter(arr, predicate, index + 1, newResult);
}
// 尾递归版本的数组映射
function arrayMap(arr, mapper, index = 0, result = []) {
if (index >= arr.length) return result;
return arrayMap(
arr,
mapper,
index + 1,
[...result, mapper(arr[index])]
);
}
// 测试
const numbers = Array.from({length: 1000}, (_, i) => i + 1);
console.log(arraySum(numbers)); // 500500
console.log(arrayFilter(numbers, x => x % 2 === 0).length); // 500
树结构遍历的尾调用优化:
'use strict';
// 尾调用优化的树遍历
function traverseTree(node, result = [], stack = []) {
if (!node) {
if (stack.length === 0) return result;
const nextNode = stack.pop();
return traverseTree(nextNode, result, stack);
}
const newResult = [...result, node.value];
const newStack = [...stack];
if (node.right) newStack.push(node.right);
if (node.left) newStack.push(node.left);
return traverseTree(null, newResult, newStack);
}
// 尾调用优化的深度优先搜索
function dfsSearch(graph, start, target, visited = new Set(), path = []) {
if (start === target) {
return [...path, start];
}
if (visited.has(start)) {
return null;
}
const newVisited = new Set([...visited, start]);
const newPath = [...path, start];
const neighbors = graph[start] || [];
return searchNeighbors(neighbors, target, newVisited, newPath, 0);
}
function searchNeighbors(neighbors, target, visited, path, index) {
if (index >= neighbors.length) return null;
const result = dfsSearch(neighbors[index], target, visited, path);
if (result) return result;
return searchNeighbors(neighbors, target, visited, path, index + 1);
}
浏览器支持现状:
// 检测尾调用优化支持
function checkTCOSupport() {
'use strict';
function tcoTest(n) {
if (n <= 0) return true;
return tcoTest(n - 1);
}
try {
return tcoTest(10000);
} catch (e) {
return false;
}
}
console.log('TCO Supported:', checkTCOSupport());
// 大多数现代浏览器的实际情况:
// - Safari:有部分支持
// - Chrome/Firefox:规范支持但实现有限
// - Node.js:有限支持
替代方案和实践建议:
蹦床函数模式:
// 手动实现尾调用优化 - 蹦床函数
function trampoline(fn) {
let result = fn();
while (typeof result === 'function') {
result = result();
}
return result;
}
// 使用蹦床函数的阶乘
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return () => factorial(n - 1, n * acc); // 返回函数而不是直接调用
}
// 安全的深度递归
const result = trampoline(() => factorial(10000));
console.log(result);
迭代替代递归:
// 将递归转换为迭代
function factorialIterative(n) {
let result = 1;
while (n > 1) {
result *= n;
n--;
}
return result;
}
function fibonacciIterative(n) {
if (n <= 1) return n;
let a = 0, b = 1, temp;
for (let i = 2; i <= n; i++) {
temp = a + b;
a = b;
b = temp;
}
return b;
}
// 使用栈模拟递归
function treeTraversalIterative(root) {
if (!root) return [];
const result = [];
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
result.push(node.value);
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
return result;
}
性能和最佳实践:
arguments、闭包引用等调试和测试:
'use strict';
// 测试递归深度限制的工具函数
function testRecursionLimit(fn, maxN = 100000) {
let low = 1, high = maxN, result = 0;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
try {
fn(mid);
result = mid;
low = mid + 1;
} catch (e) {
high = mid - 1;
}
}
return result;
}
// 测试不同递归实现的限制
console.log('Max stack depth:', testRecursionLimit(n => {
function test(x) {
if (x <= 0) return true;
return test(x - 1);
}
return test(n);
}));
Please explain ES6’s event loop mechanism and the execution order of microtasks and macrotasks.
考察点:异步执行模型与优先级。
答案:
事件循环(Event Loop)是 JavaScript 异步编程的核心机制,ES6 引入的 Promise、async/await 等特性进一步丰富了异步执行模型。理解事件循环机制对于编写高质量的异步代码至关重要。
事件循环基础概念:
执行栈、任务队列和事件循环:
// 执行栈示例
function third() {
console.log('3');
}
function second() {
console.log('2');
third();
}
function first() {
console.log('1');
second();
console.log('4');
}
first();
// 输出:1, 2, 3, 4
// 执行栈:first() -> second() -> third() -> 依次弹出
宏任务(Macro Tasks)和微任务(Micro Tasks):
console.log('1'); // 同步代码
setTimeout(() => console.log('2'), 0); // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
console.log('4'); // 同步代码
// 输出顺序:1, 4, 3, 2
// 解释:同步代码先执行 -> 微任务优先于宏任务
宏任务和微任务的分类:
宏任务类型:
// 宏任务的主要来源
// 1. setTimeout/setInterval
setTimeout(() => console.log('setTimeout'), 0);
setInterval(() => console.log('setInterval'), 100);
// 2. setImmediate (Node.js)
setImmediate(() => console.log('setImmediate'));
// 3. I/O 操作
fs.readFile('file.txt', () => console.log('File read'));
// 4. UI 渲染 (浏览器)
requestAnimationFrame(() => console.log('Animation frame'));
// 5. script 标签加载
// <script src="..."></script>
// 6. MessageChannel
const channel = new MessageChannel();
channel.port1.onmessage = () => console.log('MessageChannel');
channel.port2.postMessage('test');
微任务类型:
// 微任务的主要来源
// 1. Promise.then/catch/finally
Promise.resolve().then(() => console.log('Promise.then'));
Promise.reject().catch(() => console.log('Promise.catch'));
Promise.resolve().finally(() => console.log('Promise.finally'));
// 2. async/await
async function asyncFunc() {
console.log('async function');
await Promise.resolve();
console.log('after await'); // 微任务
}
// 3. queueMicrotask
queueMicrotask(() => console.log('queueMicrotask'));
// 4. MutationObserver (浏览器)
const observer = new MutationObserver(() => {
console.log('DOM mutation');
});
事件循环的详细执行流程:
完整的执行顺序:
console.log('=== 事件循环执行顺序演示 ===');
// 1. 同步代码
console.log('1: 同步代码开始');
// 2. 宏任务
setTimeout(() => {
console.log('6: setTimeout 宏任务');
// 宏任务中的微任务
Promise.resolve().then(() => console.log('7: 宏任务中的微任务'));
}, 0);
// 3. 微任务
Promise.resolve().then(() => {
console.log('4: 第一个微任务');
// 微任务中的微任务
Promise.resolve().then(() => console.log('5: 嵌套微任务'));
});
// 4. 更多微任务
queueMicrotask(() => console.log('3: queueMicrotask'));
// 5. 同步代码
console.log('2: 同步代码结束');
// 输出顺序:
// 1: 同步代码开始
// 2: 同步代码结束
// 3: queueMicrotask
// 4: 第一个微任务
// 5: 嵌套微任务
// 6: setTimeout 宏任务
// 7: 宏任务中的微任务
复杂场景分析:
async function complexEventLoop() {
console.log('A: 函数开始');
setTimeout(() => console.log('F: setTimeout1'), 0);
const promise1 = new Promise(resolve => {
console.log('B: Promise 构造函数');
resolve();
});
promise1.then(() => {
console.log('D: Promise.then1');
setTimeout(() => console.log('H: setTimeout2'), 0);
});
await promise1;
console.log('E: await 后续代码');
Promise.resolve().then(() => console.log('G: Promise.then2'));
console.log('C: 函数结束');
}
complexEventLoop();
// 输出分析:
// A: 函数开始 (同步)
// B: Promise 构造函数 (同步)
// C: 函数结束 (同步)
// D: Promise.then1 (微任务)
// E: await 后续代码 (微任务)
// G: Promise.then2 (微任务)
// F: setTimeout1 (宏任务)
// H: setTimeout2 (宏任务)
不同环境下的差异:
浏览器 vs Node.js:
// 浏览器环境
setTimeout(() => console.log('setTimeout1'), 0);
setImmediate(() => console.log('setImmediate1')); // 浏览器不支持
process.nextTick(() => console.log('nextTick1')); // 浏览器不支持
Promise.resolve().then(() => console.log('Promise1'));
// Node.js 环境特有的执行顺序
// Node.js 有更复杂的事件循环阶段:
// 1. Timer 阶段:setTimeout, setInterval
// 2. I/O callbacks 阶段
// 3. idle, prepare 阶段
// 4. Poll 阶段:新的 I/O 事件
// 5. Check 阶段:setImmediate
// 6. Close callbacks 阶段
// Node.js 微任务优先级:process.nextTick > Promise.then
Node.js 特殊情况:
// Node.js 环境
console.log('开始');
process.nextTick(() => {
console.log('nextTick 1');
process.nextTick(() => console.log('nextTick 2'));
});
Promise.resolve().then(() => {
console.log('Promise 1');
Promise.resolve().then(() => console.log('Promise 2'));
});
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));
console.log('结束');
// Node.js 输出:
// 开始
// 结束
// nextTick 1
// nextTick 2
// Promise 1
// Promise 2
// setTimeout 或 setImmediate (顺序可能变化)
实际应用场景:
性能优化场景:
// 使用微任务优化 DOM 操作
function optimizedDOMUpdate() {
const updates = [];
function scheduleUpdate(callback) {
updates.push(callback);
if (updates.length === 1) {
// 使用微任务批量处理 DOM 更新
queueMicrotask(() => {
const currentUpdates = [...updates];
updates.length = 0;
// 批量执行 DOM 操作
currentUpdates.forEach(update => update());
});
}
}
return scheduleUpdate;
}
const scheduleUpdate = optimizedDOMUpdate();
// 多个 DOM 更新会被批量处理
scheduleUpdate(() => document.getElementById('el1').textContent = 'A');
scheduleUpdate(() => document.getElementById('el2').textContent = 'B');
scheduleUpdate(() => document.getElementById('el3').textContent = 'C');
异步流程控制:
// 控制异步操作的执行时机
class AsyncScheduler {
constructor() {
this.tasks = [];
this.running = false;
}
addTask(task) {
this.tasks.push(task);
if (!this.running) {
this.runTasks();
}
}
runTasks() {
if (this.tasks.length === 0) {
this.running = false;
return;
}
this.running = true;
// 使用微任务确保任务按顺序执行
queueMicrotask(() => {
const task = this.tasks.shift();
Promise.resolve(task()).then(() => {
// 任务完成后继续执行下一个
this.runTasks();
}).catch(error => {
console.error('Task failed:', error);
this.runTasks();
});
});
}
}
const scheduler = new AsyncScheduler();
scheduler.addTask(async () => {
console.log('Task 1');
await new Promise(resolve => setTimeout(resolve, 100));
});
scheduler.addTask(async () => {
console.log('Task 2');
await new Promise(resolve => setTimeout(resolve, 50));
});
调试和监控:
// 监控事件循环性能
class EventLoopMonitor {
constructor() {
this.metrics = {
macroTaskCount: 0,
microTaskCount: 0,
averageDelay: 0
};
}
measureMacroTaskDelay() {
const start = performance.now();
setTimeout(() => {
const delay = performance.now() - start;
this.metrics.macroTaskCount++;
this.updateAverageDelay(delay);
console.log(`宏任务延迟: ${delay.toFixed(2)}ms`);
}, 0);
}
measureMicroTaskDelay() {
const start = performance.now();
queueMicrotask(() => {
const delay = performance.now() - start;
this.metrics.microTaskCount++;
console.log(`微任务延迟: ${delay.toFixed(2)}ms`);
});
}
updateAverageDelay(delay) {
const count = this.metrics.macroTaskCount;
this.metrics.averageDelay =
(this.metrics.averageDelay * (count - 1) + delay) / count;
}
getMetrics() {
return { ...this.metrics };
}
}
const monitor = new EventLoopMonitor();
// 测试事件循环性能
setInterval(() => {
monitor.measureMacroTaskDelay();
monitor.measureMicroTaskDelay();
}, 1000);
最佳实践和注意事项:
常见面试陷阱:
// 经典面试题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 输出:3, 3, 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0); // 输出:0, 1, 2
}
// 解决方案
for (var k = 0; k < 3; k++) {
((index) => {
setTimeout(() => console.log(index), 0); // 输出:0, 1, 2
})(k);
}
How to implement a complete Promise/A+ specification compliant Promise?
How to implement a complete Promise/A+ specification compliant Promise?
考察点:状态管理与链式调用。
答案:
Promise/A+ 规范是现代 JavaScript Promise 的标准实现规范。实现一个完整的 Promise 需要深入理解状态管理、链式调用、异常处理等核心概念。
Promise/A+ 规范核心要点:
基础状态和术语:
// Promise 的三种状态
const PENDING = 'pending'; // 待定状态
const FULFILLED = 'fulfilled'; // 已完成状态
const REJECTED = 'rejected'; // 已拒绝状态
// Promise 的基本结构
class MyPromise {
constructor(executor) {
this.state = PENDING; // 当前状态
this.value = undefined; // 完成时的值
this.reason = undefined; // 拒绝时的原因
this.onFulfilledCallbacks = []; // 成功回调队列
this.onRejectedCallbacks = []; // 失败回调队列
// 执行 executor
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
}
状态转换方法:
class MyPromise {
// ... 构造函数
resolve(value) {
// 状态只能从 pending 转换,且只能转换一次
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
// 执行所有成功回调
this.onFulfilledCallbacks.forEach(callback => {
callback(value);
});
}
}
reject(reason) {
// 状态只能从 pending 转换,且只能转换一次
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
// 执行所有失败回调
this.onRejectedCallbacks.forEach(callback => {
callback(reason);
});
}
}
}
完整的 Promise/A+ 实现:
核心 then 方法实现:
class MyPromise {
// ... 前面的代码
then(onFulfilled, onRejected) {
// 参数校验和默认值处理
const realOnFulfilled = typeof onFulfilled === 'function'
? onFulfilled
: value => value;
const realOnRejected = typeof onRejected === 'function'
? onRejected
: reason => { throw reason; };
// 返回新的 Promise 实现链式调用
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
// 使用微任务确保异步执行
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
switch (this.state) {
case FULFILLED:
fulfilledMicrotask();
break;
case REJECTED:
rejectedMicrotask();
break;
case PENDING:
// 添加到回调队列
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
break;
}
});
return promise2;
}
}
Promise Resolution Procedure(关键算法):
// Promise/A+ 规范的核心解析算法
function resolvePromise(promise2, x, resolve, reject) {
// 2.3.1: 如果 promise2 和 x 指向同一个对象,抛出 TypeError
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 2.3.2: 如果 x 是一个 Promise
if (x instanceof MyPromise) {
if (x.state === PENDING) {
x.then(value => {
resolvePromise(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
// 2.3.3: 如果 x 是对象或函数
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let called = false; // 防止重复调用
try {
// 2.3.3.1: 获取 x.then
const then = x.then;
// 2.3.3.3: 如果 then 是函数
if (typeof then === 'function') {
then.call(
x,
// 2.3.3.3.1: resolvePromise
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
// 2.3.3.3.2: rejectPromise
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 2.3.3.4: 如果 then 不是函数,以 x 为值解决
resolve(x);
}
} catch (error) {
// 2.3.3.2: 如果检索属性 x.then 抛出异常
if (called) return;
called = true;
reject(error);
}
} else {
// 2.3.4: 如果 x 不是对象或函数,以 x 为值解决
resolve(x);
}
}
完整的 MyPromise 类:
class MyPromise {
constructor(executor) {
this.state = PENDING;
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
const reject = (reason) => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(callback => callback());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function'
? onFulfilled : value => value;
const realOnRejected = typeof onRejected === 'function'
? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
switch (this.state) {
case FULFILLED:
fulfilledMicrotask();
break;
case REJECTED:
rejectedMicrotask();
break;
case PENDING:
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
break;
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
return this.then(
value => MyPromise.resolve(onFinally()).then(() => value),
reason => MyPromise.resolve(onFinally()).then(() => { throw reason; })
);
}
}
静态方法实现:
Promise.resolve 和 Promise.reject:
MyPromise.resolve = function(value) {
// 如果是 MyPromise 实例,直接返回
if (value instanceof MyPromise) {
return value;
}
return new MyPromise(resolve => resolve(value));
};
MyPromise.reject = function(reason) {
return new MyPromise((resolve, reject) => reject(reason));
};
Promise.all 实现:
MyPromise.all = function(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
const results = [];
let completedCount = 0;
const total = promises.length;
if (total === 0) {
return resolve([]);
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = value;
completedCount++;
if (completedCount === total) {
resolve(results);
}
},
reason => {
reject(reason);
}
);
});
});
};
Promise.race 实现:
MyPromise.race = function(promises) {
return new MyPromise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument must be an array'));
}
promises.forEach(promise => {
MyPromise.resolve(promise).then(resolve, reject);
});
});
};
Promise.allSettled 实现:
MyPromise.allSettled = function(promises) {
return new MyPromise((resolve) => {
if (!Array.isArray(promises)) {
return resolve([]);
}
const results = [];
let completedCount = 0;
const total = promises.length;
if (total === 0) {
return resolve([]);
}
promises.forEach((promise, index) => {
MyPromise.resolve(promise).then(
value => {
results[index] = { status: 'fulfilled', value };
completedCount++;
if (completedCount === total) resolve(results);
},
reason => {
results[index] = { status: 'rejected', reason };
completedCount++;
if (completedCount === total) resolve(results);
}
);
});
});
};
测试和验证:
// 测试基本功能
function testMyPromise() {
console.log('=== MyPromise 测试 ===');
// 1. 基本 resolve
new MyPromise(resolve => {
setTimeout(() => resolve('success'), 100);
}).then(value => {
console.log('Test 1 passed:', value); // success
});
// 2. 基本 reject
new MyPromise((resolve, reject) => {
setTimeout(() => reject('error'), 100);
}).catch(reason => {
console.log('Test 2 passed:', reason); // error
});
// 3. 链式调用
new MyPromise(resolve => resolve(1))
.then(value => value + 1)
.then(value => value * 2)
.then(value => {
console.log('Test 3 passed:', value); // 4
});
// 4. 异常处理
new MyPromise(resolve => resolve(1))
.then(() => {
throw new Error('Test error');
})
.catch(error => {
console.log('Test 4 passed:', error.message); // Test error
});
// 5. Promise.all
MyPromise.all([
MyPromise.resolve(1),
MyPromise.resolve(2),
MyPromise.resolve(3)
]).then(values => {
console.log('Test 5 passed:', values); // [1, 2, 3]
});
// 6. 循环引用检测
const promise = new MyPromise(resolve => resolve());
const chainedPromise = promise.then(() => chainedPromise);
chainedPromise.catch(error => {
console.log('Test 6 passed:', error.message); // Chaining cycle detected
});
}
// 运行测试
testMyPromise();
性能优化和实际考虑:
class OptimizedPromise extends MyPromise {
constructor(executor) {
super(executor);
// 状态确定后清理回调队列,释放内存
const originalResolve = this.resolve;
const originalReject = this.reject;
this.resolve = (value) => {
originalResolve.call(this, value);
this.onFulfilledCallbacks.length = 0;
this.onRejectedCallbacks.length = 0;
};
this.reject = (reason) => {
originalReject.call(this, reason);
this.onFulfilledCallbacks.length = 0;
this.onRejectedCallbacks.length = 0;
};
}
}
与原生 Promise 的兼容性:
queueMicrotask 保证正确的执行顺序What are the underlying principles of Generator and async/await?
What are the underlying principles of Generator and async/await?
考察点:协程、自动化流程控制。
答案:
Generator 和 async/await 是 JavaScript 中处理异步编程的重要工具。Generator 是 ES6 引入的协程机制,而 async/await 是基于 Generator 和 Promise 的语法糖,它们共同解决了回调地狱和异步流程控制的问题。
Generator 的底层原理:
Generator 函数的执行机制:
// Generator 函数定义
function* simpleGenerator() {
console.log('开始执行');
const a = yield 1;
console.log('第一次恢复,接收到:', a);
const b = yield 2;
console.log('第二次恢复,接收到:', b);
return 3;
}
// Generator 对象创建和执行
const gen = simpleGenerator(); // 不会立即执行
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next('hello')); // { value: 2, done: false }
console.log(gen.next('world')); // { value: 3, done: true }
// 执行流程:
// 1. 调用 simpleGenerator() 返回 Generator 对象
// 2. 第一次 next() 执行到第一个 yield,暂停并返回 1
// 3. 第二次 next('hello') 从暂停处恢复,'hello' 作为 yield 1 的返回值
// 4. 执行到第二个 yield,暂停并返回 2
// 5. 第三次 next('world') 恢复执行,'world' 作为 yield 2 的返回值
// 6. 执行到 return,结束并返回 3
Generator 的状态机模型:
// 手动实现 Generator 的状态机逻辑
function createGeneratorStateMachine() {
let state = 0;
let lastValue;
return {
next(value) {
switch (state) {
case 0:
console.log('开始执行');
state = 1;
return { value: 1, done: false };
case 1:
lastValue = value;
console.log('第一次恢复,接收到:', lastValue);
state = 2;
return { value: 2, done: false };
case 2:
lastValue = value;
console.log('第二次恢复,接收到:', lastValue);
state = 3;
return { value: 3, done: true };
default:
return { value: undefined, done: true };
}
},
[Symbol.iterator]() {
return this;
}
};
}
// 使用自定义状态机
const customGen = createGeneratorStateMachine();
console.log(customGen.next()); // { value: 1, done: false }
console.log(customGen.next('hello')); // { value: 2, done: false }
console.log(customGen.next('world')); // { value: 3, done: true }
Generator 的异步应用:
// 使用 Generator 处理异步操作
function* asyncGenerator() {
try {
const data1 = yield fetch('/api/data1');
console.log('第一次数据:', data1);
const data2 = yield fetch(`/api/data2/${data1.id}`);
console.log('第二次数据:', data2);
const result = yield processData(data1, data2);
return result;
} catch (error) {
console.error('异步操作失败:', error);
throw error;
}
}
// 手动执行器(类似 co 库的实现)
function runGenerator(generatorFunction) {
const gen = generatorFunction();
function handle(result) {
if (result.done) {
return Promise.resolve(result.value);
}
return Promise.resolve(result.value)
.then(
value => handle(gen.next(value)),
error => handle(gen.throw(error))
);
}
return handle(gen.next());
}
// 使用
runGenerator(asyncGenerator)
.then(result => console.log('最终结果:', result))
.catch(error => console.error('执行失败:', error));
async/await 的底层原理:
async/await 的 Generator + Promise 实现:
// async/await 的等价 Generator 实现
// 原始 async 函数
async function fetchUserData(userId) {
try {
const user = await fetch(`/api/users/${userId}`);
const profile = await fetch(`/api/profiles/${user.id}`);
return { user, profile };
} catch (error) {
console.error('获取用户数据失败:', error);
throw error;
}
}
// 等价的 Generator 实现
function* fetchUserDataGenerator(userId) {
try {
const user = yield fetch(`/api/users/${userId}`);
const profile = yield fetch(`/api/profiles/${user.id}`);
return { user, profile };
} catch (error) {
console.error('获取用户数据失败:', error);
throw error;
}
}
// 自动执行器(async 函数的核心机制)
function asyncWrapper(generatorFunction) {
return function(...args) {
const gen = generatorFunction.apply(this, args);
return new Promise((resolve, reject) => {
function step(nextF) {
let next;
try {
next = nextF();
} catch (e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
v => step(() => gen.next(v)),
e => step(() => gen.throw(e))
);
}
step(() => gen.next(undefined));
});
};
}
// 将 Generator 函数转换为 async 函数
const fetchUserDataAsync = asyncWrapper(fetchUserDataGenerator);
// 使用效果完全一致
fetchUserDataAsync(123)
.then(result => console.log(result))
.catch(error => console.error(error));
深入理解 await 的执行机制:
// await 的执行步骤分析
async function awaitAnalysis() {
console.log('1: 函数开始执行');
// await 会暂停函数执行,将后续代码包装成微任务
const result = await Promise.resolve('异步结果');
console.log('3: await 后的代码', result);
return '函数完成';
}
console.log('0: 调用 async 函数之前');
const promise = awaitAnalysis();
console.log('2: async 函数调用后,await 之后的代码被暂停');
promise.then(result => {
console.log('4: async 函数完成', result);
});
// 输出顺序:
// 0: 调用 async 函数之前
// 1: 函数开始执行
// 2: async 函数调用后,await 之后的代码被暂停
// 3: await 后的代码 异步结果
// 4: async 函数完成 函数完成
高级应用和模式:
实现自定义的 async/await:
// 完整的 async/await 实现
class AsyncFunction {
constructor(generatorFunction) {
this.generatorFunction = generatorFunction;
}
async execute(...args) {
const generator = this.generatorFunction.apply(this, args);
let result = generator.next();
while (!result.done) {
try {
// 等待当前 yield 的值
const value = await Promise.resolve(result.value);
result = generator.next(value);
} catch (error) {
// 将错误传递给 Generator
result = generator.throw(error);
}
}
return result.value;
}
// 使其可以像 async 函数一样调用
static create(generatorFunction) {
const asyncFn = new AsyncFunction(generatorFunction);
return (...args) => asyncFn.execute(...args);
}
}
// 使用自定义 async/await
const customAsync = AsyncFunction.create(function* (url) {
const response = yield fetch(url);
const data = yield response.json();
return data;
});
customAsync('/api/data')
.then(data => console.log('数据:', data))
.catch(error => console.error('错误:', error));
Generator 的高级模式 - 双向通信:
// Generator 的双向数据流
function* bidirectionalGenerator() {
let received = yield 'first';
console.log('接收到:', received);
let calculated = received * 2;
received = yield calculated;
console.log('再次接收到:', received);
return received + calculated;
}
function runBidirectional() {
const gen = bidirectionalGenerator();
let result = gen.next(); // 开始执行
console.log('第一次输出:', result.value); // 'first'
result = gen.next(10); // 发送 10
console.log('第二次输出:', result.value); // 20
result = gen.next(5); // 发送 5
console.log('最终结果:', result.value); // 25
}
runBidirectional();
协程式的并发控制:
// 使用 Generator 实现协程调度
class CoroutineScheduler {
constructor() {
this.tasks = [];
this.running = false;
}
spawn(generatorFunction, ...args) {
const task = {
id: Date.now() + Math.random(),
generator: generatorFunction(...args),
completed: false
};
this.tasks.push(task);
if (!this.running) {
this.run();
}
return task.id;
}
async run() {
this.running = true;
while (this.tasks.length > 0) {
for (let i = this.tasks.length - 1; i >= 0; i--) {
const task = this.tasks[i];
try {
const result = task.generator.next();
if (result.done) {
console.log(`任务 ${task.id} 完成:`, result.value);
this.tasks.splice(i, 1);
} else if (result.value instanceof Promise) {
// 如果 yield 了一个 Promise,等待它
await result.value;
}
} catch (error) {
console.error(`任务 ${task.id} 错误:`, error);
this.tasks.splice(i, 1);
}
}
// 让出执行权,允许其他代码执行
await new Promise(resolve => setTimeout(resolve, 0));
}
this.running = false;
}
}
// 使用协程调度器
const scheduler = new CoroutineScheduler();
function* task1() {
console.log('任务1: 步骤1');
yield new Promise(resolve => setTimeout(resolve, 100));
console.log('任务1: 步骤2');
yield;
console.log('任务1: 完成');
return 'task1 result';
}
function* task2() {
console.log('任务2: 步骤1');
yield;
console.log('任务2: 步骤2');
yield new Promise(resolve => setTimeout(resolve, 50));
console.log('任务2: 完成');
return 'task2 result';
}
scheduler.spawn(task1);
scheduler.spawn(task2);
性能和内存考虑:
// Generator 的惰性求值和内存效率
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
// 只在需要时计算值,不会占用大量内存
function* fibonacciGenerator() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// 高效的数据处理管道
function* map(iterable, mapper) {
for (const item of iterable) {
yield mapper(item);
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item;
}
}
}
function* take(iterable, count) {
let taken = 0;
for (const item of iterable) {
if (taken >= count) break;
yield item;
taken++;
}
}
// 构建高效的数据处理链
const result = [...take(
filter(
map(fibonacciGenerator(), x => x * 2),
x => x % 4 === 0
),
5
)];
console.log(result); // [0, 4, 16, 40, 104]
调试和错误处理:
// Generator 错误处理
function* generatorWithError() {
try {
const result1 = yield Promise.reject('第一个错误');
} catch (error) {
console.log('捕获到错误:', error);
const result2 = yield Promise.resolve('恢复执行');
return result2;
}
}
// async/await 错误处理
async function asyncWithError() {
try {
const result1 = await Promise.reject('第一个错误');
} catch (error) {
console.log('捕获到错误:', error);
const result2 = await Promise.resolve('恢复执行');
return result2;
}
}
// 两者在错误处理上的行为基本一致
最佳实践和使用建议:
总结:
Generator 是基于协程的底层机制,提供了函数执行的暂停和恢复能力。async/await 是基于 Generator 和 Promise 的高级抽象,提供了更直观的异步编程体验。两者都解决了异步流程控制的问题,但 async/await 因其简洁性和直观性成为了现代 JavaScript 异步编程的首选方案。
What advanced scenarios can Proxy implement? Please give examples.
What advanced scenarios can Proxy implement? Please give examples.
考察点:数据劫持、响应式、AOP。
答案:
Proxy 是 ES6 引入的强大元编程工具,它允许我们拦截并自定义对象的基本操作(如属性查找、赋值、枚举、函数调用等)。Proxy 可以实现许多高级编程模式和框架级功能。
1. 响应式系统实现(Vue3 风格):
// 简化版的 Vue3 响应式系统
class ReactiveSystem {
constructor() {
this.activeEffect = null;
this.targetMap = new WeakMap();
}
// 依赖收集
track(target, key) {
if (!this.activeEffect) return;
let depsMap = this.targetMap.get(target);
if (!depsMap) {
this.targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(this.activeEffect);
}
// 触发更新
trigger(target, key) {
const depsMap = this.targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
// 创建响应式对象
reactive(target) {
return new Proxy(target, {
get(obj, key) {
const result = Reflect.get(obj, key);
this.track(obj, key);
// 如果值是对象,递归创建响应式
if (typeof result === 'object' && result !== null) {
return this.reactive(result);
}
return result;
},
set(obj, key, value) {
const oldValue = obj[key];
const result = Reflect.set(obj, key, value);
if (oldValue !== value) {
this.trigger(obj, key);
}
return result;
}
});
}
// 副作用函数
effect(fn) {
const effect = () => {
this.activeEffect = effect;
try {
return fn();
} finally {
this.activeEffect = null;
}
};
effect();
return effect;
}
}
// 使用响应式系统
const reactiveSystem = new ReactiveSystem();
const state = reactiveSystem.reactive({
count: 0,
user: {
name: 'Alice',
age: 25
}
});
// 创建副作用
reactiveSystem.effect(() => {
console.log(`Count: ${state.count}`);
});
reactiveSystem.effect(() => {
console.log(`User: ${state.user.name}, Age: ${state.user.age}`);
});
// 触发更新
state.count++; // 输出: Count: 1
state.user.name = 'Bob'; // 输出: User: Bob, Age: 25
2. AOP(面向切面编程)实现:
// AOP 切面编程框架
class AOPProxy {
constructor(target) {
this.target = target;
this.beforeHooks = new Map();
this.afterHooks = new Map();
this.aroundHooks = new Map();
return new Proxy(target, {
get: (obj, prop) => {
const value = obj[prop];
if (typeof value === 'function') {
return this.wrapMethod(prop, value);
}
return value;
}
});
}
wrapMethod(methodName, originalMethod) {
return (...args) => {
const context = { methodName, args, target: this.target };
try {
// 执行前置钩子
this.executeHooks(this.beforeHooks, methodName, context);
let result;
const aroundHooks = this.aroundHooks.get(methodName) || [];
if (aroundHooks.length > 0) {
// 使用环绕钩子
result = this.executeAroundHooks(aroundHooks, originalMethod, context);
} else {
// 直接执行原方法
result = originalMethod.apply(this.target, args);
}
context.result = result;
// 执行后置钩子
this.executeHooks(this.afterHooks, methodName, context);
return result;
} catch (error) {
context.error = error;
this.executeHooks(this.afterHooks, methodName, context);
throw error;
}
};
}
executeHooks(hookMap, methodName, context) {
const hooks = hookMap.get(methodName) || [];
hooks.forEach(hook => hook(context));
}
executeAroundHooks(hooks, originalMethod, context) {
let index = 0;
const proceed = () => {
if (index < hooks.length) {
return hooks[index++](context, proceed);
} else {
return originalMethod.apply(context.target, context.args);
}
};
return proceed();
}
// 添加前置钩子
before(methodName, hook) {
if (!this.beforeHooks.has(methodName)) {
this.beforeHooks.set(methodName, []);
}
this.beforeHooks.get(methodName).push(hook);
return this;
}
// 添加后置钩子
after(methodName, hook) {
if (!this.afterHooks.has(methodName)) {
this.afterHooks.set(methodName, []);
}
this.afterHooks.get(methodName).push(hook);
return this;
}
// 添加环绕钩子
around(methodName, hook) {
if (!this.aroundHooks.has(methodName)) {
this.aroundHooks.set(methodName, []);
}
this.aroundHooks.get(methodName).push(hook);
return this;
}
}
// 使用示例
class UserService {
getUser(id) {
console.log(`获取用户 ${id}`);
return { id, name: `User${id}` };
}
updateUser(id, data) {
console.log(`更新用户 ${id}:`, data);
return { id, ...data };
}
}
const userService = new AOPProxy(new UserService())
.before('getUser', (context) => {
console.log('前置: 验证权限');
console.log('前置: 记录日志');
})
.after('getUser', (context) => {
console.log('后置: 缓存结果');
console.log('后置: 性能监控');
})
.around('updateUser', (context, proceed) => {
console.log('环绕: 开始事务');
try {
const result = proceed();
console.log('环绕: 提交事务');
return result;
} catch (error) {
console.log('环绕: 回滚事务');
throw error;
}
});
// 调用被增强的方法
const user = userService.getUser(123);
userService.updateUser(123, { name: 'Updated Name' });
3. 数据校验和类型检查:
// 强类型数据校验代理
class TypedObject {
constructor(schema) {
this.schema = schema;
this.data = {};
return new Proxy(this.data, {
set: (target, key, value) => {
this.validate(key, value);
return Reflect.set(target, key, value);
},
get: (target, key) => {
if (key === 'toJSON') {
return () => ({ ...target });
}
return Reflect.get(target, key);
},
has: (target, key) => {
return key in this.schema || Reflect.has(target, key);
},
ownKeys: (target) => {
return [...Object.keys(this.schema), ...Reflect.ownKeys(target)];
}
});
}
validate(key, value) {
const fieldSchema = this.schema[key];
if (!fieldSchema) {
throw new Error(`未知字段: ${key}`);
}
// 类型检查
if (fieldSchema.type && typeof value !== fieldSchema.type) {
throw new TypeError(`字段 ${key} 期望类型 ${fieldSchema.type},实际类型 ${typeof value}`);
}
// 必填检查
if (fieldSchema.required && (value === undefined || value === null)) {
throw new Error(`字段 ${key} 是必填的`);
}
// 自定义校验器
if (fieldSchema.validator && !fieldSchema.validator(value)) {
throw new Error(`字段 ${key} 校验失败`);
}
// 范围检查
if (fieldSchema.min !== undefined && value < fieldSchema.min) {
throw new Error(`字段 ${key} 不能小于 ${fieldSchema.min}`);
}
if (fieldSchema.max !== undefined && value > fieldSchema.max) {
throw new Error(`字段 ${key} 不能大于 ${fieldSchema.max}`);
}
}
}
// 使用类型化对象
const userSchema = {
id: { type: 'number', required: true },
name: {
type: 'string',
required: true,
validator: (value) => value.length >= 2
},
age: {
type: 'number',
min: 0,
max: 150
},
email: {
type: 'string',
validator: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}
};
const user = new TypedObject(userSchema);
try {
user.id = 123;
user.name = 'Alice';
user.age = 25;
user.email = '[email protected]';
console.log('用户数据:', user.toJSON());
// user.age = 200; // 抛出错误: 字段 age 不能大于 150
// user.email = 'invalid'; // 抛出错误: 字段 email 校验失败
} catch (error) {
console.error(error.message);
}
4. 自动持久化和缓存系统:
// 自动持久化代理
class PersistentObject {
constructor(key, initialData = {}) {
this.storageKey = key;
this.data = this.loadFromStorage() || initialData;
this.debounceTimer = null;
return new Proxy(this.data, {
set: (target, prop, value) => {
const result = Reflect.set(target, prop, value);
this.scheduleSave();
return result;
},
deleteProperty: (target, prop) => {
const result = Reflect.deleteProperty(target, prop);
this.scheduleSave();
return result;
}
});
}
loadFromStorage() {
try {
const stored = localStorage.getItem(this.storageKey);
return stored ? JSON.parse(stored) : null;
} catch (error) {
console.error('加载数据失败:', error);
return null;
}
}
saveToStorage() {
try {
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
console.log(`数据已保存到 ${this.storageKey}`);
} catch (error) {
console.error('保存数据失败:', error);
}
}
scheduleSave() {
// 防抖保存,避免频繁写入
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.saveToStorage();
}, 500);
}
}
// 使用自动持久化对象
const settings = new PersistentObject('app-settings', {
theme: 'light',
language: 'zh-CN',
notifications: true
});
// 自动保存到 localStorage
settings.theme = 'dark';
settings.autoSave = true;
delete settings.notifications;
5. API 代理和模拟:
// API 代理,支持缓存、重试、模拟等功能
class APIProxy {
constructor(baseURL, options = {}) {
this.baseURL = baseURL;
this.cache = new Map();
this.mockData = options.mockData || {};
this.retryCount = options.retryCount || 3;
this.timeout = options.timeout || 5000;
return new Proxy({}, {
get: (target, prop) => {
return this.createAPIMethod(prop);
}
});
}
createAPIMethod(endpoint) {
return async (options = {}) => {
const {
method = 'GET',
params = {},
data = null,
useCache = true,
useMock = false
} = options;
const cacheKey = `${method}:${endpoint}:${JSON.stringify(params)}`;
// 检查缓存
if (useCache && this.cache.has(cacheKey)) {
console.log('从缓存返回:', cacheKey);
return this.cache.get(cacheKey);
}
// 使用模拟数据
if (useMock && this.mockData[endpoint]) {
console.log('使用模拟数据:', endpoint);
const mockResult = typeof this.mockData[endpoint] === 'function'
? this.mockData[endpoint](params, data)
: this.mockData[endpoint];
if (useCache) {
this.cache.set(cacheKey, mockResult);
}
return mockResult;
}
// 实际API调用
return this.makeRequest(endpoint, { method, params, data, cacheKey, useCache });
};
}
async makeRequest(endpoint, { method, params, data, cacheKey, useCache }) {
let attempt = 0;
while (attempt < this.retryCount) {
try {
const url = new URL(endpoint, this.baseURL);
// 添加查询参数
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
const response = await fetch(url, {
method,
body: data ? JSON.stringify(data) : null,
headers: {
'Content-Type': 'application/json'
},
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const result = await response.json();
// 缓存成功结果
if (useCache) {
this.cache.set(cacheKey, result);
}
return result;
} catch (error) {
attempt++;
console.warn(`API调用失败 (尝试 ${attempt}/${this.retryCount}):`, error.message);
if (attempt >= this.retryCount) {
throw error;
}
// 等待后重试
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}
}
// 使用API代理
const api = new APIProxy('https://api.example.com', {
mockData: {
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
],
posts: (params) => ({
data: [`Post ${params.page || 1}`],
total: 100
})
},
retryCount: 2,
timeout: 3000
});
// 使用方式
async function testAPI() {
try {
// 使用模拟数据
const users = await api.users({ useMock: true });
console.log('用户列表:', users);
// 实际API调用(带缓存)
const posts = await api.posts({
params: { page: 1, limit: 10 },
useCache: true
});
console.log('帖子列表:', posts);
} catch (error) {
console.error('API调用失败:', error);
}
}
testAPI();
6. 无限嵌套对象代理:
// 深度代理,支持无限嵌套
class DeepProxy {
constructor(target, handler) {
this.handler = handler;
return this.createProxy(target);
}
createProxy(target) {
return new Proxy(target, {
get: (obj, prop) => {
const value = Reflect.get(obj, prop);
// 调用用户定义的get处理器
if (this.handler.get) {
const result = this.handler.get(obj, prop, value);
if (result !== undefined) return result;
}
// 如果值是对象,递归创建代理
if (value && typeof value === 'object') {
return this.createProxy(value);
}
return value;
},
set: (obj, prop, value) => {
// 调用用户定义的set处理器
if (this.handler.set) {
const result = this.handler.set(obj, prop, value);
if (result === false) return false;
}
return Reflect.set(obj, prop, value);
},
has: (obj, prop) => {
if (this.handler.has) {
return this.handler.has(obj, prop);
}
return Reflect.has(obj, prop);
}
});
}
}
// 使用深度代理实现路径访问
const deepObject = new DeepProxy({}, {
get: (obj, prop, value) => {
// 如果属性不存在,自动创建嵌套对象
if (value === undefined && typeof prop === 'string') {
obj[prop] = {};
return obj[prop];
}
},
set: (obj, prop, value) => {
console.log(`设置 ${prop} = ${value}`);
return true; // 继续默认行为
}
});
// 可以无限嵌套访问
deepObject.user.profile.personal.name = 'Alice';
deepObject.user.profile.settings.theme = 'dark';
console.log('深度对象:', JSON.stringify(deepObject, null, 2));
性能考虑和最佳实践:
// Proxy 性能监控装饰器
function withPerformanceMonitoring(target, options = {}) {
const stats = {
getCount: 0,
setCount: 0,
totalTime: 0
};
return new Proxy(target, {
get(obj, prop) {
if (prop === '__stats__') {
return stats;
}
const start = performance.now();
const result = Reflect.get(obj, prop);
const duration = performance.now() - start;
stats.getCount++;
stats.totalTime += duration;
if (options.logSlowAccess && duration > (options.threshold || 1)) {
console.warn(`慢速属性访问: ${String(prop)} 耗时 ${duration.toFixed(2)}ms`);
}
return result;
},
set(obj, prop, value) {
const start = performance.now();
const result = Reflect.set(obj, prop, value);
const duration = performance.now() - start;
stats.setCount++;
stats.totalTime += duration;
return result;
}
});
}
// 使用性能监控
const monitoredObject = withPerformanceMonitoring({
data: new Array(10000).fill(0).map((_, i) => ({ id: i, value: Math.random() }))
}, {
logSlowAccess: true,
threshold: 0.1
});
// 访问属性
monitoredObject.data.forEach(item => item.value);
console.log('性能统计:', monitoredObject.__stats__);
Proxy 的高级应用场景还包括:数据绑定、ORM 实现、动态接口适配、调试工具、安全沙箱等。它的强大之处在于能够透明地拦截和自定义对象操作,为元编程和框架开发提供了无限可能。
Advanced applications of Symbol: How to use it to implement singleton pattern and custom iterators?
考察点:Symbol的唯一性、隐藏属性、以及在设计模式中的应用。
答案:
Symbol 是 ES6 引入的全新原始数据类型,具有唯一性和不可枚举的特点。这些特性使得 Symbol 在实现高级编程模式、隐藏属性、元编程等场景中非常有用。
1. 使用 Symbol 实现单例模式:
// 基础的 Symbol 单例模式
const SingletonSymbol = Symbol('Singleton');
class DatabaseConnection {
constructor() {
// 检查是否已经有实例
if (DatabaseConnection[SingletonSymbol]) {
return DatabaseConnection[SingletonSymbol];
}
// 初始化实例
this.isConnected = false;
this.connectionCount = 0;
// 将实例保存到 Symbol 属性中
DatabaseConnection[SingletonSymbol] = this;
return this;
}
connect() {
if (!this.isConnected) {
this.isConnected = true;
this.connectionCount++;
console.log(`数据库连接建立 #${this.connectionCount}`);
}
return this;
}
disconnect() {
if (this.isConnected) {
this.isConnected = false;
console.log('数据库连接断开');
}
return this;
}
getStatus() {
return {
connected: this.isConnected,
connectionCount: this.connectionCount
};
}
}
// 测试单例模式
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
console.log(db1 === db2); // true - 同一个实例
db1.connect();
console.log(db2.getStatus()); // { connected: true, connectionCount: 1 }
2. 高级单例模式 - 多实例单例:
// 支持多种类型的单例管理器
class SingletonManager {
constructor() {
this.instances = new Map();
}
// 为每个类型创建唯一的 Symbol
createSingletonType(typeName) {
const typeSymbol = Symbol(`Singleton:${typeName}`);
return {
symbol: typeSymbol,
getInstance(Class, ...args) {
if (!this.instances.has(typeSymbol)) {
const instance = new Class(...args);
this.instances.set(typeSymbol, instance);
}
return this.instances.get(typeSymbol);
}.bind(this),
hasInstance() {
return this.instances.has(typeSymbol);
}.bind(this),
clearInstance() {
this.instances.delete(typeSymbol);
}.bind(this)
};
}
}
const singletonManager = new SingletonManager();
// 不同类型的单例
const dbSingleton = singletonManager.createSingletonType('Database');
const cacheSingleton = singletonManager.createSingletonType('Cache');
const loggerSingleton = singletonManager.createSingletonType('Logger');
class Database {
constructor(config) {
this.config = config;
console.log('Database 实例创建:', config);
}
}
class Cache {
constructor(size) {
this.size = size;
console.log('Cache 实例创建, 大小:', size);
}
}
// 使用多实例单例
const db1 = dbSingleton.getInstance(Database, { host: 'localhost' });
const db2 = dbSingleton.getInstance(Database, { host: 'remote' }); // 不会创建新实例
const cache1 = cacheSingleton.getInstance(Cache, 1000);
const cache2 = cacheSingleton.getInstance(Cache, 2000); // 不会创建新实例
console.log(db1 === db2); // true
console.log(cache1 === cache2); // true
console.log(db1 === cache1); // false - 不同类型的单例
3. 自定义迭代器的基础实现:
// 基础的自定义迭代器
class NumberRange {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
// 实现 Symbol.iterator 方法
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
const step = this.step;
return {
next() {
if (current < end) {
const value = current;
current += step;
return { value, done: false };
} else {
return { done: true };
}
}
};
}
}
// 使用自定义迭代器
const range = new NumberRange(1, 10, 2);
console.log('使用 for...of:');
for (const num of range) {
console.log(num); // 1, 3, 5, 7, 9
}
console.log('使用扩展运算符:');
console.log([...range]); // [1, 3, 5, 7, 9]
console.log('手动迭代:');
const iterator = range[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
console.log(result.value);
result = iterator.next();
}
4. 高级迭代器 - 可配置的迭代行为:
// 支持多种迭代模式的集合类
class AdvancedCollection {
constructor(data = []) {
this.data = [...data];
// 定义不同的迭代模式
this.iterationModes = {
[Symbol.for('forward')]: this.forwardIterator.bind(this),
[Symbol.for('backward')]: this.backwardIterator.bind(this),
[Symbol.for('random')]: this.randomIterator.bind(this),
[Symbol.for('filter')]: this.filterIterator.bind(this),
[Symbol.for('chunk')]: this.chunkIterator.bind(this)
};
}
// 默认迭代器(正向)
[Symbol.iterator]() {
return this.forwardIterator();
}
// 正向迭代器
forwardIterator() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
}
return { done: true };
}
};
}
// 反向迭代器
backwardIterator() {
let index = this.data.length - 1;
const data = this.data;
return {
next() {
if (index >= 0) {
return { value: data[index--], done: false };
}
return { done: true };
}
};
}
// 随机顺序迭代器
randomIterator() {
const shuffledData = [...this.data].sort(() => Math.random() - 0.5);
let index = 0;
return {
next() {
if (index < shuffledData.length) {
return { value: shuffledData[index++], done: false };
}
return { done: true };
}
};
}
// 过滤迭代器
filterIterator(predicate = () => true) {
const filteredData = this.data.filter(predicate);
let index = 0;
return {
next() {
if (index < filteredData.length) {
return { value: filteredData[index++], done: false };
}
return { done: true };
}
};
}
// 分块迭代器
chunkIterator(size = 2) {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
const chunk = data.slice(index, index + size);
index += size;
return { value: chunk, done: false };
}
return { done: true };
}
};
}
// 获取指定模式的迭代器
iterate(mode, ...args) {
const iteratorFactory = this.iterationModes[Symbol.for(mode)];
if (iteratorFactory) {
return iteratorFactory(...args);
}
throw new Error(`未知的迭代模式: ${mode}`);
}
// 添加元素
add(item) {
this.data.push(item);
return this;
}
}
// 使用高级迭代器
const collection = new AdvancedCollection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
console.log('正向迭代:');
for (const item of collection) {
console.log(item); // 1, 2, 3, ..., 10
}
console.log('反向迭代:');
const backwardIter = collection.iterate('backward');
let result = backwardIter.next();
while (!result.done) {
console.log(result.value); // 10, 9, 8, ..., 1
result = backwardIter.next();
}
console.log('过滤迭代(偶数):');
const evenIter = collection.iterate('filter', x => x % 2 === 0);
console.log([...{ [Symbol.iterator]: () => evenIter }]); // [2, 4, 6, 8, 10]
console.log('分块迭代(每块3个):');
const chunkIter = collection.iterate('chunk', 3);
result = chunkIter.next();
while (!result.done) {
console.log(result.value); // [1,2,3], [4,5,6], [7,8,9], [10]
result = chunkIter.next();
}
5. Symbol 在私有属性中的应用:
// 使用 Symbol 创建真正私有的属性
const createPrivateScope = () => {
// 私有 Symbol 键,外部无法访问
const _id = Symbol('id');
const _name = Symbol('name');
const _balance = Symbol('balance');
const _transactions = Symbol('transactions');
class BankAccount {
constructor(name, initialBalance = 0) {
this[_id] = Date.now() + Math.random();
this[_name] = name;
this[_balance] = initialBalance;
this[_transactions] = [];
}
// 公共方法
deposit(amount) {
if (amount <= 0) {
throw new Error('存款金额必须大于0');
}
this[_balance] += amount;
this[_transactions].push({
type: 'deposit',
amount,
timestamp: new Date(),
balance: this[_balance]
});
return this;
}
withdraw(amount) {
if (amount <= 0) {
throw new Error('取款金额必须大于0');
}
if (this[_balance] < amount) {
throw new Error('余额不足');
}
this[_balance] -= amount;
this[_transactions].push({
type: 'withdraw',
amount,
timestamp: new Date(),
balance: this[_balance]
});
return this;
}
getBalance() {
return this[_balance];
}
getAccountInfo() {
return {
id: this[_id],
name: this[_name],
balance: this[_balance],
transactionCount: this[_transactions].length
};
}
getTransactionHistory() {
// 返回交易记录的副本,防止外部修改
return this[_transactions].map(t => ({ ...t }));
}
// 私有方法(通过 Symbol 实现)
[Symbol.for('internal.validateAmount')](amount) {
return typeof amount === 'number' && amount > 0 && isFinite(amount);
}
}
return BankAccount;
};
const BankAccount = createPrivateScope();
const account = new BankAccount('Alice', 1000);
console.log('账户信息:', account.getAccountInfo());
account.deposit(500).withdraw(200);
console.log('余额:', account.getBalance());
console.log('交易历史:', account.getTransactionHistory());
// 尝试直接访问私有属性
console.log('直接访问私有属性:');
console.log(Object.keys(account)); // 空数组,看不到私有属性
console.log(Object.getOwnPropertyNames(account)); // 空数组
console.log(Object.getOwnPropertySymbols(account)); // 可以看到 Symbol 键,但无法使用
6. Symbol 在元编程中的应用:
// 使用 Symbol 创建元编程工具
class MetaProgrammingHelper {
constructor() {
// 创建元数据存储的 Symbol 键
this.META_KEY = Symbol('metadata');
this.HOOKS_KEY = Symbol('hooks');
this.OBSERVERS_KEY = Symbol('observers');
}
// 为对象添加元数据
addMetadata(target, key, value) {
if (!target[this.META_KEY]) {
target[this.META_KEY] = new Map();
}
target[this.META_KEY].set(key, value);
return this;
}
// 获取元数据
getMetadata(target, key) {
const meta = target[this.META_KEY];
return meta ? meta.get(key) : undefined;
}
// 添加生命周期钩子
addHook(target, event, callback) {
if (!target[this.HOOKS_KEY]) {
target[this.HOOKS_KEY] = new Map();
}
if (!target[this.HOOKS_KEY].has(event)) {
target[this.HOOKS_KEY].set(event, []);
}
target[this.HOOKS_KEY].get(event).push(callback);
return this;
}
// 触发钩子
triggerHook(target, event, ...args) {
const hooks = target[this.HOOKS_KEY];
if (hooks && hooks.has(event)) {
hooks.get(event).forEach(callback => {
try {
callback.call(target, ...args);
} catch (error) {
console.error(`钩子执行失败 [${event}]:`, error);
}
});
}
}
// 创建增强的类
enhance(Class) {
const self = this;
return class extends Class {
constructor(...args) {
super(...args);
// 初始化元编程能力
this[self.META_KEY] = new Map();
this[self.HOOKS_KEY] = new Map();
this[self.OBSERVERS_KEY] = new Set();
// 触发构造钩子
self.triggerHook(this, 'constructed', args);
}
// 添加元数据方法
setMeta(key, value) {
self.addMetadata(this, key, value);
self.triggerHook(this, 'metaChanged', key, value);
return this;
}
getMeta(key) {
return self.getMetadata(this, key);
}
// 添加钩子方法
on(event, callback) {
self.addHook(this, event, callback);
return this;
}
emit(event, ...args) {
self.triggerHook(this, event, ...args);
return this;
}
};
}
}
// 使用元编程助手
const metaHelper = new MetaProgrammingHelper();
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
updateEmail(newEmail) {
const oldEmail = this.email;
this.email = newEmail;
// 触发更新事件
if (this.emit) {
this.emit('emailUpdated', oldEmail, newEmail);
}
}
}
// 增强 User 类
const EnhancedUser = metaHelper.enhance(User);
const user = new EnhancedUser('Alice', '[email protected]');
// 添加元数据
user.setMeta('createdAt', new Date())
.setMeta('department', 'Engineering')
.setMeta('level', 'Senior');
// 添加事件监听器
user.on('constructed', (args) => {
console.log('用户创建:', args);
})
.on('emailUpdated', function(oldEmail, newEmail) {
console.log(`邮箱更新: ${oldEmail} -> ${newEmail}`);
this.setMeta('lastEmailUpdate', new Date());
})
.on('metaChanged', (key, value) => {
console.log(`元数据变更: ${key} = ${value}`);
});
// 测试增强功能
console.log('部门:', user.getMeta('department'));
user.updateEmail('[email protected]');
console.log('最后邮箱更新时间:', user.getMeta('lastEmailUpdate'));
7. Symbol 在迭代器协议中的高级应用:
// 实现支持多种迭代策略的树结构
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
this.parent = null;
}
addChild(child) {
if (child instanceof TreeNode) {
child.parent = this;
this.children.push(child);
}
return this;
}
// 深度优先迭代器
*[Symbol.iterator]() {
yield this.value;
for (const child of this.children) {
yield* child;
}
}
// 广度优先迭代器
*[Symbol.for('breadthFirst')]() {
const queue = [this];
while (queue.length > 0) {
const node = queue.shift();
yield node.value;
queue.push(...node.children);
}
}
// 叶子节点迭代器
*[Symbol.for('leaves')]() {
if (this.children.length === 0) {
yield this.value;
} else {
for (const child of this.children) {
yield* child[Symbol.for('leaves')]();
}
}
}
// 层级迭代器
*[Symbol.for('levels')]() {
let currentLevel = [this];
while (currentLevel.length > 0) {
const levelValues = currentLevel.map(node => node.value);
yield levelValues;
const nextLevel = [];
for (const node of currentLevel) {
nextLevel.push(...node.children);
}
currentLevel = nextLevel;
}
}
// 获取指定类型的迭代器
iterate(type = 'depth') {
const iteratorMap = {
'depth': this[Symbol.iterator].bind(this),
'breadth': this[Symbol.for('breadthFirst')].bind(this),
'leaves': this[Symbol.for('leaves')].bind(this),
'levels': this[Symbol.for('levels')].bind(this)
};
const iterator = iteratorMap[type];
if (!iterator) {
throw new Error(`不支持的迭代类型: ${type}`);
}
return iterator();
}
}
// 构建树结构
const root = new TreeNode('A');
const b = new TreeNode('B');
const c = new TreeNode('C');
const d = new TreeNode('D');
const e = new TreeNode('E');
const f = new TreeNode('F');
root.addChild(b).addChild(c);
b.addChild(d).addChild(e);
c.addChild(f);
// 测试不同的迭代方式
console.log('深度优先:', [...root]); // ['A', 'B', 'D', 'E', 'C', 'F']
console.log('广度优先:', [...root.iterate('breadth')]); // ['A', 'B', 'C', 'D', 'E', 'F']
console.log('叶子节点:', [...root.iterate('leaves')]); // ['D', 'E', 'F']
console.log('按层遍历:', [...root.iterate('levels')]); // [['A'], ['B', 'C'], ['D', 'E', 'F']]
Symbol 的最佳实践:
Symbol 的这些高级应用展示了它在现代 JavaScript 开发中的重要价值,特别是在框架开发、库设计和元编程领域。
How to implement a simple reactive system (like Vue3 reactivity principle)?
How to implement a simple reactive system (like Vue3 reactivity principle)?
考察点:Proxy、依赖收集、数据劫持。
答案:
响应式系统是现代前端框架的核心机制,它能够自动追踪数据变化并触发相应的更新。Vue3 使用 Proxy 重写了响应式系统,相比 Vue2 的 Object.defineProperty 方案具有更好的性能和功能完整性。
1. 响应式系统核心概念:
// 响应式系统的核心组件
class ReactivitySystem {
constructor() {
// 当前正在执行的副作用函数
this.activeEffect = null;
// 存储目标对象 -> 属性 -> 依赖集合的映射
// WeakMap { target -> Map { key -> Set<effect> } }
this.targetMap = new WeakMap();
// 副作用函数栈,支持嵌套 effect
this.effectStack = [];
}
// 依赖收集 - 在属性访问时调用
track(target, key) {
// 如果没有活跃的副作用函数,则不需要收集依赖
if (!this.activeEffect) return;
// 获取目标对象的依赖映射
let depsMap = this.targetMap.get(target);
if (!depsMap) {
this.targetMap.set(target, (depsMap = new Map()));
}
// 获取属性的依赖集合
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
// 建立双向引用:effect 知道自己依赖哪些属性,属性知道哪些 effect 依赖自己
dep.add(this.activeEffect);
this.activeEffect.deps.push(dep);
console.log(`👀 Track: ${target.constructor.name}.${String(key)}`);
}
// 触发更新 - 在属性设置时调用
trigger(target, key, newValue, oldValue) {
const depsMap = this.targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
console.log(`🔥 Trigger: ${target.constructor.name}.${String(key)} = ${newValue}`);
// 创建新的 Set 避免在遍历过程中修改原 Set
const effectsToRun = new Set(dep);
effectsToRun.forEach(effect => {
// 避免自递归(effect 内部修改了触发自己的属性)
if (effect !== this.activeEffect) {
if (effect.scheduler) {
// 如果有调度器,使用调度器执行
effect.scheduler(effect);
} else {
// 否则直接执行
effect();
}
}
});
}
// 创建响应式对象
reactive(target) {
// 如果不是对象,直接返回
if (!isObject(target)) return target;
// 如果已经是响应式对象,直接返回
if (target.__v_reactive) return target;
const proxy = new Proxy(target, {
get: (obj, key, receiver) => {
// 标记为响应式对象
if (key === '__v_reactive') return true;
const result = Reflect.get(obj, key, receiver);
// 收集依赖
this.track(obj, key);
// 如果属性值是对象,递归创建响应式
if (isObject(result)) {
return this.reactive(result);
}
return result;
},
set: (obj, key, value, receiver) => {
const oldValue = obj[key];
const result = Reflect.set(obj, key, value, receiver);
// 只有当值真正改变时才触发更新
if (oldValue !== value) {
this.trigger(obj, key, value, oldValue);
}
return result;
},
deleteProperty: (obj, key) => {
const hadKey = hasOwnProperty.call(obj, key);
const oldValue = obj[key];
const result = Reflect.deleteProperty(obj, key);
if (result && hadKey) {
this.trigger(obj, key, undefined, oldValue);
}
return result;
}
});
return proxy;
}
// 副作用函数
effect(fn, options = {}) {
const effect = () => {
// 清理之前的依赖
cleanup(effect);
// 设置当前活跃的 effect
this.activeEffect = effect;
this.effectStack.push(effect);
try {
// 执行副作用函数
return fn();
} finally {
// 恢复之前的 effect
this.effectStack.pop();
this.activeEffect = this.effectStack[this.effectStack.length - 1] || null;
}
};
// 为 effect 添加属性
effect.deps = []; // 存储依赖的属性集合
effect.options = options;
effect.scheduler = options.scheduler;
// 如果不是懒执行,立即执行一次
if (!options.lazy) {
effect();
}
return effect;
}
}
// 工具函数
function isObject(value) {
return typeof value === 'object' && value !== null;
}
function cleanup(effect) {
const { deps } = effect;
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect);
}
deps.length = 0;
}
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
2. 基础响应式系统使用示例:
// 创建响应式系统实例
const reactivity = new ReactivitySystem();
// 创建响应式数据
const state = reactivity.reactive({
count: 0,
user: {
name: 'Alice',
age: 25,
profile: {
email: '[email protected]'
}
},
items: [1, 2, 3]
});
// 创建副作用函数
console.log('=== 创建副作用函数 ===');
reactivity.effect(() => {
console.log(`计数器: ${state.count}`);
});
reactivity.effect(() => {
console.log(`用户信息: ${state.user.name} (${state.user.age}岁)`);
});
reactivity.effect(() => {
console.log(`邮箱: ${state.user.profile.email}`);
});
// 触发更新
console.log('\n=== 触发更新 ===');
state.count++; // 输出: 计数器: 1
state.user.name = 'Bob'; // 输出: 用户信息: Bob (25岁)
state.user.profile.email = '[email protected]'; // 输出: 邮箱: [email protected]
// 嵌套对象也是响应式的
console.log('\n=== 嵌套对象响应式 ===');
state.user.profile.settings = { theme: 'dark' };
reactivity.effect(() => {
if (state.user.profile.settings) {
console.log(`主题: ${state.user.profile.settings.theme}`);
}
});
state.user.profile.settings.theme = 'light'; // 输出: 主题: light
3. 高级特性:computed 计算属性:
// 扩展响应式系统,添加计算属性支持
class EnhancedReactivitySystem extends ReactivitySystem {
// 计算属性
computed(getter, options = {}) {
let value;
let dirty = true; // 标记是否需要重新计算
// 创建 effect,但不立即执行
const effect = this.effect(getter, {
lazy: true,
scheduler: () => {
if (!dirty) {
dirty = true;
// 触发计算属性的依赖更新
this.trigger(computedRef, 'value');
}
}
});
const computedRef = {
get value() {
// 只有在 dirty 时才重新计算
if (dirty) {
value = effect();
dirty = false;
}
// 收集对计算属性的依赖
this.track(computedRef, 'value');
return value;
}.bind(this)
};
return computedRef;
}
// watch 监听器
watch(source, callback, options = {}) {
let getter;
if (typeof source === 'function') {
getter = source;
} else {
// 如果是响应式对象,创建一个访问它的函数
getter = () => traverse(source);
}
let oldValue;
let cleanup;
const onInvalidate = (fn) => {
cleanup = fn;
};
const job = () => {
if (cleanup) cleanup();
const newValue = effect();
if (newValue !== oldValue || options.deep) {
callback(newValue, oldValue, onInvalidate);
oldValue = newValue;
}
};
const effect = this.effect(getter, {
lazy: true,
scheduler: job
});
if (options.immediate) {
job();
} else {
oldValue = effect();
}
// 返回停止监听的函数
return () => {
cleanup && cleanup();
// 停止 effect
effect.deps.forEach(dep => dep.delete(effect));
};
}
}
// 深度遍历对象的所有属性(用于 watch)
function traverse(value, seen = new Set()) {
if (!isObject(value) || seen.has(value)) {
return value;
}
seen.add(value);
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
traverse(value[i], seen);
}
} else {
for (const key in value) {
traverse(value[key], seen);
}
}
return value;
}
// 使用增强的响应式系统
const enhanced = new EnhancedReactivitySystem();
const data = enhanced.reactive({
firstName: 'John',
lastName: 'Doe',
age: 30
});
// 计算属性
const fullName = enhanced.computed(() => {
console.log('💭 计算 fullName');
return `${data.firstName} ${data.lastName}`;
});
const isAdult = enhanced.computed(() => {
console.log('💭 计算 isAdult');
return data.age >= 18;
});
// 使用计算属性
console.log('\n=== 计算属性示例 ===');
console.log('全名:', fullName.value); // John Doe
console.log('是否成年:', isAdult.value); // true
// 修改依赖数据
data.firstName = 'Jane';
console.log('更新后全名:', fullName.value); // Jane Doe(重新计算)
console.log('是否成年:', isAdult.value); // true(从缓存读取)
// watch 监听器
console.log('\n=== Watch 示例 ===');
const stopWatching = enhanced.watch(
() => data.age,
(newAge, oldAge) => {
console.log(`年龄变化: ${oldAge} -> ${newAge}`);
}
);
enhanced.watch(
data,
() => {
console.log('数据对象发生了变化');
},
{ deep: true }
);
data.age = 25; // 年龄变化: 30 -> 25 & 数据对象发生了变化
data.firstName = 'Alice'; // 数据对象发生了变化
// 停止监听
stopWatching();
data.age = 20; // 不会触发年龄变化的回调
4. 响应式数组和集合:
// 扩展以支持数组和集合的响应式
class FullReactivitySystem extends EnhancedReactivitySystem {
reactive(target) {
if (!isObject(target)) return target;
if (target.__v_reactive) return target;
// 数组的特殊处理
if (Array.isArray(target)) {
return this.createReactiveArray(target);
}
// Map 的特殊处理
if (target instanceof Map) {
return this.createReactiveMap(target);
}
// Set 的特殊处理
if (target instanceof Set) {
return this.createReactiveSet(target);
}
return super.reactive(target);
}
createReactiveArray(target) {
const reactiveArray = new Proxy(target, {
get: (arr, key, receiver) => {
if (key === '__v_reactive') return true;
// 处理数组方法
if (arrayInstrumentations[key]) {
return arrayInstrumentations[key].bind(this, arr);
}
const result = Reflect.get(arr, key, receiver);
// 收集依赖
if (typeof key !== 'symbol' && !isNaN(key)) {
this.track(arr, key);
}
// 收集 length 依赖
if (key === 'length') {
this.track(arr, 'length');
}
return isObject(result) ? this.reactive(result) : result;
},
set: (arr, key, value, receiver) => {
const oldValue = arr[key];
const oldLength = arr.length;
const result = Reflect.set(arr, key, value, receiver);
if (oldValue !== value) {
this.trigger(arr, key, value, oldValue);
// 如果修改了数组长度
if (key === 'length' || (typeof key !== 'symbol' && !isNaN(key))) {
if (arr.length !== oldLength) {
this.trigger(arr, 'length', arr.length, oldLength);
}
}
}
return result;
}
});
return reactiveArray;
}
}
// 数组方法的响应式包装
const arrayInstrumentations = {
push(arr, ...items) {
const length = arr.length;
const result = Array.prototype.push.apply(arr, items);
this.trigger(arr, 'length', arr.length, length);
return result;
},
pop(arr) {
const length = arr.length;
const result = Array.prototype.pop.call(arr);
if (length > 0) {
this.trigger(arr, 'length', arr.length, length);
this.trigger(arr, length - 1, undefined, result);
}
return result;
},
shift(arr) {
const length = arr.length;
const result = Array.prototype.shift.call(arr);
if (length > 0) {
this.trigger(arr, 'length', arr.length, length);
// 所有索引都发生了变化
for (let i = 0; i < arr.length; i++) {
this.trigger(arr, i.toString(), arr[i], undefined);
}
}
return result;
},
unshift(arr, ...items) {
const length = arr.length;
const result = Array.prototype.unshift.apply(arr, items);
this.trigger(arr, 'length', arr.length, length);
// 所有索引都发生了变化
for (let i = 0; i < arr.length; i++) {
this.trigger(arr, i.toString(), arr[i], undefined);
}
return result;
},
splice(arr, start, deleteCount, ...items) {
const length = arr.length;
const result = Array.prototype.splice.apply(arr, [start, deleteCount, ...items]);
if (result.length > 0 || items.length > 0) {
this.trigger(arr, 'length', arr.length, length);
// 从 start 开始的所有索引都可能发生了变化
for (let i = start; i < Math.max(length, arr.length); i++) {
this.trigger(arr, i.toString(), arr[i], undefined);
}
}
return result;
}
};
// 测试响应式数组
const fullReactivity = new FullReactivitySystem();
const reactiveData = fullReactivity.reactive({
numbers: [1, 2, 3],
users: [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
]
});
console.log('\n=== 响应式数组测试 ===');
fullReactivity.effect(() => {
console.log('数组长度:', reactiveData.numbers.length);
console.log('数组内容:', reactiveData.numbers.join(', '));
});
fullReactivity.effect(() => {
console.log('用户列表:', reactiveData.users.map(u => u.name).join(', '));
});
// 测试数组操作
console.log('\n--- 数组操作 ---');
reactiveData.numbers.push(4); // 触发长度和内容更新
reactiveData.numbers[0] = 10; // 触发索引更新
reactiveData.users.push({ name: 'Charlie', age: 28 }); // 触发用户列表更新
5. 性能优化和调度系统:
// 添加调度和批量更新优化
class OptimizedReactivitySystem extends FullReactivitySystem {
constructor() {
super();
this.updateQueue = new Set();
this.isFlushPending = false;
}
// 批量更新调度器
queueUpdate(effect) {
this.updateQueue.add(effect);
if (!this.isFlushPending) {
this.isFlushPending = true;
// 使用微任务异步批量执行更新
queueMicrotask(() => {
this.flushUpdates();
});
}
}
flushUpdates() {
console.log(`🔄 批量执行 ${this.updateQueue.size} 个更新`);
this.updateQueue.forEach(effect => {
if (effect.active !== false) {
effect();
}
});
this.updateQueue.clear();
this.isFlushPending = false;
}
// 重写 effect 方法,添加调度支持
effect(fn, options = {}) {
const effect = super.effect(fn, {
...options,
scheduler: options.scheduler || ((effect) => {
this.queueUpdate(effect);
})
});
effect.active = true;
return effect;
}
// 停止 effect
stop(effect) {
effect.active = false;
effect.deps.forEach(dep => dep.delete(effect));
effect.deps.length = 0;
}
}
// 使用优化的响应式系统
const optimized = new OptimizedReactivitySystem();
const testData = optimized.reactive({
count: 0,
name: 'Test'
});
console.log('\n=== 批量更新测试 ===');
// 创建多个 effect
const effect1 = optimized.effect(() => {
console.log('Effect 1: count =', testData.count);
});
const effect2 = optimized.effect(() => {
console.log('Effect 2: count * 2 =', testData.count * 2);
});
const effect3 = optimized.effect(() => {
console.log('Effect 3: name =', testData.name);
});
// 同步进行多个更新
console.log('\n--- 同步更新(会被批量处理)---');
testData.count = 1;
testData.count = 2;
testData.count = 3;
testData.name = 'Updated';
// 等待微任务队列执行
setTimeout(() => {
console.log('\n--- 停止某个 effect ---');
optimized.stop(effect2);
testData.count = 4; // effect2 不会再执行
}, 0);
响应式系统的核心特性总结:
这个实现展示了现代响应式系统的核心原理,类似于 Vue3 的实现思路,但进行了简化以便理解。在实际框架中,还会有更多的优化和边界情况处理。
What are the differences between ES6 modules and CommonJS? How to make them compatible?
What are the differences between ES6 modules and CommonJS? How to make them compatible?
考察点:静态/动态加载、导入导出机制。
答案:
ES6 模块(ESM)和 CommonJS(CJS)是 JavaScript 中两种主要的模块化规范。理解它们的区别以及如何兼容使用对于现代 JavaScript 开发至关重要。
1. 基础语法对比:
// === ES6 Modules (ESM) ===
// 导出方式
// 1. 命名导出
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
multiply(a, b) {
return a * b;
}
}
// 2. 默认导出
export default class MathUtils {
static square(x) {
return x * x;
}
}
// 3. 重新导出
export { sin, cos } from 'math-library';
export * from 'utilities';
// 导入方式
import MathUtils, { PI, add, Calculator } from './math.js';
import * as math from './math.js';
import { add as addition } from './math.js';
// === CommonJS (CJS) ===
// 导出方式
// 1. exports 对象
exports.PI = 3.14159;
exports.add = function(a, b) {
return a + b;
};
// 2. module.exports
class Calculator {
multiply(a, b) {
return a * b;
}
}
module.exports = Calculator;
// 3. 混合导出
module.exports = {
PI: 3.14159,
add: function(a, b) {
return a + b;
},
Calculator: Calculator
};
// 导入方式
const MathUtils = require('./math.js');
const { PI, add } = require('./math.js');
2. 核心区别分析:
// === 1. 加载时机差异 ===
// ES6 Modules: 静态加载(编译时)
import { debugFunction } from './debug.js'; // 编译时就确定了依赖
if (process.env.NODE_ENV === 'development') {
// ❌ 错误:ES6 import 不能在条件语句中
// import { devTool } from './dev-tools.js';
// ✅ 正确:使用动态 import
const { devTool } = await import('./dev-tools.js');
}
// CommonJS: 动态加载(运行时)
if (process.env.NODE_ENV === 'development') {
// ✅ 正确:CommonJS require 可以在条件语句中
const { devTool } = require('./dev-tools.js');
}
// === 2. 值引用差异 ===
// ES6 Modules: 活绑定(live binding)
// counter.js (ESM)
export let count = 0;
export function increment() {
count++;
}
// main.js (ESM)
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 - 值实时更新
// CommonJS: 值拷贝
// counter.js (CJS)
let count = 0;
function increment() {
count++;
}
module.exports = { count, increment };
// main.js (CJS)
const { count, increment } = require('./counter.js');
console.log(count); // 0
increment();
console.log(count); // 0 - 值不会更新
3. 详细特性对比表:
// 创建对比演示
class ModuleComparisonDemo {
static demonstrateDifferences() {
console.log('=== ES6 Modules vs CommonJS 对比 ===\n');
const comparison = {
'特性': {
'ES6 Modules': 'CommonJS'
},
'语法': {
'ES6 Modules': 'import/export',
'CommonJS': 'require()/module.exports'
},
'加载方式': {
'ES6 Modules': '静态加载(编译时)',
'CommonJS': '动态加载(运行时)'
},
'值引用': {
'ES6 Modules': '活绑定(live binding)',
'CommonJS': '值拷贝(copy)'
},
'顶层 this': {
'ES6 Modules': 'undefined',
'CommonJS': 'module.exports'
},
'循环依赖': {
'ES6 Modules': '更好的支持',
'CommonJS': '可能出现问题'
},
'条件导入': {
'ES6 Modules': '不支持(需动态import)',
'CommonJS': '支持'
},
'Tree Shaking': {
'ES6 Modules': '支持',
'CommonJS': '不支持'
},
'浏览器支持': {
'ES6 Modules': '原生支持(现代浏览器)',
'CommonJS': '需要构建工具'
},
'Node.js 支持': {
'ES6 Modules': 'v12+ 实验性,v14+ 稳定',
'CommonJS': '原生支持'
}
};
Object.entries(comparison).forEach(([feature, values]) => {
if (feature === '特性') return;
console.log(`${feature}:`);
console.log(` ES6: ${values['ES6 Modules']}`);
console.log(` CJS: ${values['CommonJS']}\n`);
});
}
}
ModuleComparisonDemo.demonstrateDifferences();
4. 循环依赖处理差异:
// === ES6 Modules 循环依赖 ===
// a.js (ESM)
import { b } from './b.js';
export const a = 'a';
console.log('a.js:', b);
// b.js (ESM)
import { a } from './a.js';
export const b = 'b';
console.log('b.js:', a); // undefined,但不会出错
// === CommonJS 循环依赖 ===
// a.js (CJS)
const { b } = require('./b.js');
module.exports = { a: 'a' };
console.log('a.js:', b);
// b.js (CJS)
const { a } = require('./a.js'); // 可能获取到不完整的 exports
module.exports = { b: 'b' };
console.log('b.js:', a); // 可能是 undefined 或不完整对象
// 解决循环依赖的最佳实践
class CircularDependencyResolver {
// 方法1:延迟导入
static lazyImport() {
// 在需要时才导入
const getModuleB = () => import('./b.js');
return {
async useB() {
const { b } = await getModuleB();
return b;
}
};
}
// 方法2:依赖注入
static dependencyInjection() {
class ModuleA {
constructor(moduleB = null) {
this.moduleB = moduleB;
}
setModuleB(moduleB) {
this.moduleB = moduleB;
}
useB() {
return this.moduleB?.someMethod();
}
}
return ModuleA;
}
// 方法3:事件驱动
static eventDriven() {
const EventEmitter = require('events');
const eventBus = new EventEmitter();
return {
moduleA: {
init() {
eventBus.on('moduleB:ready', (moduleB) => {
console.log('Module A received Module B:', moduleB);
});
},
notifyReady() {
eventBus.emit('moduleA:ready', this);
}
}
};
}
}
5. 兼容性解决方案:
// === 1. 构建工具配置兼容 ===
// webpack.config.js
module.exports = {
resolve: {
// 支持不同的模块扩展名
extensions: ['.mjs', '.js', '.json'],
// 模块解析优先级
mainFields: ['module', 'main']
},
module: {
rules: [
{
test: /\.m?js$/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
modules: false // 保持 ES6 模块用于 tree shaking
}]
]
}
}
}
]
}
};
// === 2. package.json 配置 ===
// package.json
{
"name": "my-package",
"version": "1.0.0",
"type": "module", // 设置为 ES6 模块
"main": "./dist/index.cjs", // CommonJS 入口
"module": "./dist/index.mjs", // ES6 模块入口
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
// === 3. 通用模块适配器 ===
class UniversalModuleAdapter {
// 创建同时支持 ES6 和 CommonJS 的模块
static createUniversalModule(moduleFactory) {
// UMD (Universal Module Definition) 模式
return `
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory();
} else {
// Browser globals
root.MyModule = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
${moduleFactory.toString()}
return moduleFactory();
}));
`;
}
// ES6 和 CommonJS 互相调用的桥接
static createBridge() {
return {
// ES6 调用 CommonJS
async importCommonJS(modulePath) {
// 在 Node.js 中
if (typeof require !== 'undefined') {
return require(modulePath);
}
// 在浏览器中需要通过构建工具处理
throw new Error('CommonJS modules not supported in browser');
},
// CommonJS 调用 ES6
requireESModule: (modulePath) => {
// Node.js 14+ 支持
return import(modulePath);
}
};
}
}
// === 4. 条件导出适配 ===
class ConditionalExports {
// 基于环境的模块导出
static createConditionalExport() {
// 检测当前环境
const isNode = typeof process !== 'undefined' && process.versions?.node;
const isBrowser = typeof window !== 'undefined';
if (isNode) {
// Node.js 环境:支持 CommonJS 和 ES6
return {
async loadModule(specifier) {
try {
// 优先尝试 ES6 import
return await import(specifier);
} catch (error) {
// 回退到 CommonJS require
if (typeof require !== 'undefined') {
return require(specifier);
}
throw error;
}
}
};
} else if (isBrowser) {
// 浏览器环境:只支持 ES6(或需要构建工具)
return {
async loadModule(specifier) {
return await import(specifier);
}
};
}
}
}
6. 实际项目中的最佳实践:
// === 1. 渐进式迁移策略 ===
class MigrationStrategy {
// 从 CommonJS 迁移到 ES6 的步骤
static getSteps() {
return [
{
step: 1,
title: '添加 ES6 模块支持',
actions: [
'在 package.json 中添加 "type": "module"',
'或使用 .mjs 扩展名',
'更新 Node.js 到支持 ES6 模块的版本'
]
},
{
step: 2,
title: '逐步转换模块',
actions: [
'从叶子节点(无依赖的模块)开始',
'将 require() 替换为 import',
'将 module.exports 替换为 export',
'处理循环依赖问题'
]
},
{
step: 3,
title: '更新构建配置',
actions: [
'配置 webpack/rollup 支持 ES6 模块',
'启用 tree shaking',
'配置多入口支持'
]
},
{
step: 4,
title: '测试和验证',
actions: [
'确保所有测试通过',
'验证 tree shaking 效果',
'检查打包体积优化'
]
}
];
}
// 兼容性检查工具
static checkCompatibility() {
const results = {
nodeVersion: process.version,
esModuleSupport: false,
dynamicImportSupport: false
};
// 检查 ES6 模块支持
try {
new Function('import("./test")')();
results.esModuleSupport = true;
} catch (e) {
results.esModuleSupport = false;
}
// 检查动态 import 支持
try {
eval('import("./test")');
results.dynamicImportSupport = true;
} catch (e) {
results.dynamicImportSupport = false;
}
return results;
}
}
// === 2. 模块加载器 ===
class ModuleLoader {
constructor() {
this.cache = new Map();
this.loading = new Map();
}
// 通用模块加载器
async load(specifier, options = {}) {
// 检查缓存
if (this.cache.has(specifier)) {
return this.cache.get(specifier);
}
// 避免重复加载
if (this.loading.has(specifier)) {
return this.loading.get(specifier);
}
const loadPromise = this.loadModule(specifier, options);
this.loading.set(specifier, loadPromise);
try {
const module = await loadPromise;
this.cache.set(specifier, module);
return module;
} finally {
this.loading.delete(specifier);
}
}
async loadModule(specifier, options) {
const { format = 'auto' } = options;
switch (format) {
case 'esm':
return await this.loadESModule(specifier);
case 'cjs':
return this.loadCommonJS(specifier);
case 'auto':
default:
return await this.loadAuto(specifier);
}
}
async loadESModule(specifier) {
try {
return await import(specifier);
} catch (error) {
throw new Error(`Failed to load ES module: ${specifier}. ${error.message}`);
}
}
loadCommonJS(specifier) {
if (typeof require === 'undefined') {
throw new Error('CommonJS require is not available in this environment');
}
try {
return require(specifier);
} catch (error) {
throw new Error(`Failed to load CommonJS module: ${specifier}. ${error.message}`);
}
}
async loadAuto(specifier) {
// 尝试不同的加载方式
const attempts = [
() => this.loadESModule(specifier),
() => Promise.resolve(this.loadCommonJS(specifier))
];
for (const attempt of attempts) {
try {
return await attempt();
} catch (error) {
// 继续下一种方式
console.warn(`Load attempt failed for ${specifier}:`, error.message);
}
}
throw new Error(`Failed to load module ${specifier} with any method`);
}
}
// 使用示例
const loader = new ModuleLoader();
async function demonstrateModuleLoading() {
try {
console.log('=== 模块加载演示 ===');
// 自动检测加载
const mathModule = await loader.load('./math-utils');
console.log('Math module loaded:', mathModule);
// 强制指定格式
const esmModule = await loader.load('./esm-module', { format: 'esm' });
console.log('ESM module loaded:', esmModule);
} catch (error) {
console.error('Module loading failed:', error);
}
}
7. 性能和优化考虑:
// 模块加载性能优化
class ModuleOptimizer {
// Tree Shaking 示例
static demonstrateTreeShaking() {
// 库文件 - utils.js
const createUtils = () => ({
// 会被使用的函数
add: (a, b) => a + b,
multiply: (a, b) => a * b,
// 不会被使用的函数(会被 tree shake)
subtract: (a, b) => a - b,
divide: (a, b) => a / b,
complexCalculation: () => {
// 复杂计算逻辑
return Array.from({length: 1000}, (_, i) => i * i).reduce((a, b) => a + b);
}
});
return createUtils();
}
// 代码分割和懒加载
static async implementCodeSplitting() {
const routes = {
'/home': () => import('./pages/home.js'),
'/about': () => import('./pages/about.js'),
'/contact': () => import('./pages/contact.js')
};
return {
async loadRoute(path) {
const loader = routes[path];
if (!loader) {
throw new Error(`Route ${path} not found`);
}
console.log(`Loading route: ${path}`);
const module = await loader();
console.log(`Route ${path} loaded successfully`);
return module;
}
};
}
// 模块预加载策略
static implementPreloading() {
const preloadQueue = [];
let isPreloading = false;
return {
addToPreload(specifier) {
preloadQueue.push(specifier);
this.startPreloading();
},
async startPreloading() {
if (isPreloading || preloadQueue.length === 0) return;
isPreloading = true;
while (preloadQueue.length > 0) {
const specifier = preloadQueue.shift();
try {
await import(specifier);
console.log(`Preloaded: ${specifier}`);
} catch (error) {
console.warn(`Failed to preload: ${specifier}`, error);
}
// 避免阻塞主线程
await new Promise(resolve => setTimeout(resolve, 0));
}
isPreloading = false;
}
};
}
}
总结和建议:
ES6 模块代表了 JavaScript 模块化的未来方向,虽然迁移需要时间,但长期来看能带来更好的开发体验和性能优化。
How to implement an async iterator? What are the application scenarios?
How to implement an async iterator? What are the application scenarios?
考察点:Symbol.asyncIterator、for await…of。
答案:
异步迭代器是 ES2018 (ES9) 引入的特性,它允许我们以迭代的方式处理异步数据流。异步迭代器结合了迭代器模式和 Promise,为处理流式数据、API 分页、实时数据等场景提供了优雅的解决方案。
1. 异步迭代器的基础实现:
// 基础异步迭代器实现
class AsyncRange {
constructor(start, end, delay = 100) {
this.start = start;
this.end = end;
this.delay = delay;
}
// 实现 Symbol.asyncIterator 方法
[Symbol.asyncIterator]() {
let current = this.start;
const end = this.end;
const delay = this.delay;
return {
async next() {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, delay));
if (current <= end) {
return { value: current++, done: false };
} else {
return { done: true };
}
}
};
}
}
// 使用异步迭代器
async function demonstrateAsyncIterator() {
console.log('=== 异步迭代器基础使用 ===');
const asyncRange = new AsyncRange(1, 5, 200);
// 使用 for await...of 语法
console.log('使用 for await...of:');
for await (const num of asyncRange) {
console.log(`异步获取到数字: ${num}`);
}
console.log('迭代完成\n');
// 手动使用异步迭代器
console.log('手动使用异步迭代器:');
const iterator = asyncRange[Symbol.asyncIterator]();
let result = await iterator.next();
while (!result.done) {
console.log(`手动获取到数字: ${result.value}`);
result = await iterator.next();
}
console.log('手动迭代完成\n');
}
demonstrateAsyncIterator();
2. 高级异步迭代器 - 数据流处理:
// 异步数据流迭代器
class AsyncDataStream {
constructor(dataSource, options = {}) {
this.dataSource = dataSource;
this.batchSize = options.batchSize || 10;
this.delay = options.delay || 100;
this.transform = options.transform || (x => x);
this.filter = options.filter || (() => true);
}
async *[Symbol.asyncIterator]() {
let offset = 0;
let hasMore = true;
while (hasMore) {
try {
// 获取一批数据
const batch = await this.fetchBatch(offset, this.batchSize);
if (!batch || batch.length === 0) {
hasMore = false;
break;
}
// 处理批次数据
for (const item of batch) {
// 应用过滤器
if (this.filter(item)) {
// 应用转换器
const transformedItem = await this.transform(item);
yield transformedItem;
}
}
offset += batch.length;
// 如果批次大小小于预期,说明已经到达末尾
if (batch.length < this.batchSize) {
hasMore = false;
}
// 添加延迟,避免过于频繁的请求
if (this.delay > 0) {
await new Promise(resolve => setTimeout(resolve, this.delay));
}
} catch (error) {
console.error('获取数据批次失败:', error);
throw error;
}
}
}
async fetchBatch(offset, limit) {
// 模拟异步数据获取(如 API 调用)
if (typeof this.dataSource === 'function') {
return await this.dataSource(offset, limit);
} else if (Array.isArray(this.dataSource)) {
return this.dataSource.slice(offset, offset + limit);
} else {
throw new Error('不支持的数据源类型');
}
}
// 添加操作符方法,支持链式调用
map(mapFn) {
return new AsyncDataStream(
this.dataSource,
{
...this.options,
transform: async (item) => {
const transformed = await this.transform(item);
return await mapFn(transformed);
}
}
);
}
filter(filterFn) {
return new AsyncDataStream(
this.dataSource,
{
...this.options,
filter: async (item) => {
const passed = await this.filter(item);
return passed && await filterFn(item);
}
}
);
}
take(count) {
let taken = 0;
return {
async *[Symbol.asyncIterator]() {
for await (const item of this) {
if (taken >= count) break;
yield item;
taken++;
}
}
};
}
}
// 使用数据流迭代器
async function demonstrateDataStream() {
console.log('=== 异步数据流处理 ===');
// 模拟 API 数据源
const mockAPI = async (offset, limit) => {
console.log(`📡 API 调用: offset=${offset}, limit=${limit}`);
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 300));
// 生成模拟数据
const data = Array.from({ length: limit }, (_, i) => ({
id: offset + i + 1,
name: `User ${offset + i + 1}`,
score: Math.floor(Math.random() * 100)
}));
// 模拟数据结束条件
return offset >= 25 ? [] : data;
};
// 创建数据流
const userStream = new AsyncDataStream(mockAPI, {
batchSize: 5,
delay: 200,
filter: (user) => user.score >= 60, // 只要分数 >= 60 的用户
transform: async (user) => ({
...user,
grade: user.score >= 90 ? 'A' : user.score >= 80 ? 'B' : 'C',
timestamp: new Date().toISOString()
})
});
// 处理数据流
let count = 0;
for await (const user of userStream.take(8)) {
console.log(`👤 用户 ${++count}:`, {
name: user.name,
score: user.score,
grade: user.grade
});
}
console.log('数据流处理完成\n');
}
demonstrateDataStream();
3. 实际应用场景 - 文件流处理:
// 异步文件读取迭代器
class AsyncFileReader {
constructor(filePath, options = {}) {
this.filePath = filePath;
this.chunkSize = options.chunkSize || 1024;
this.encoding = options.encoding || 'utf8';
}
async *[Symbol.asyncIterator]() {
const fs = require('fs').promises;
let fileHandle;
try {
fileHandle = await fs.open(this.filePath, 'r');
const stats = await fileHandle.stat();
const fileSize = stats.size;
let position = 0;
const buffer = Buffer.alloc(this.chunkSize);
while (position < fileSize) {
const { bytesRead } = await fileHandle.read(
buffer,
0,
this.chunkSize,
position
);
if (bytesRead === 0) break;
const chunk = buffer.slice(0, bytesRead).toString(this.encoding);
yield chunk;
position += bytesRead;
}
} finally {
if (fileHandle) {
await fileHandle.close();
}
}
}
// 按行读取
async *lines() {
let leftover = '';
for await (const chunk of this) {
const lines = (leftover + chunk).split('\n');
leftover = lines.pop() || ''; // 保存最后一行的残留部分
for (const line of lines) {
yield line;
}
}
// 处理文件末尾的残留内容
if (leftover) {
yield leftover;
}
}
}
// 使用文件读取迭代器
async function demonstrateFileReading() {
console.log('=== 异步文件读取 ===');
// 创建示例文件内容(在实际项目中,这个文件应该已存在)
const fs = require('fs').promises;
const testFile = './test-data.txt';
try {
await fs.writeFile(testFile,
'Line 1: Hello World\nLine 2: Async Iterator\nLine 3: File Processing\nLine 4: Demo Complete'
);
const fileReader = new AsyncFileReader(testFile, { chunkSize: 10 });
console.log('按块读取:');
let chunkCount = 0;
for await (const chunk of fileReader) {
console.log(`📄 块 ${++chunkCount}: "${chunk}"`);
}
console.log('\n按行读取:');
let lineCount = 0;
for await (const line of fileReader.lines()) {
console.log(`📝 行 ${++lineCount}: "${line}"`);
}
// 清理测试文件
await fs.unlink(testFile);
} catch (error) {
console.error('文件操作失败:', error.message);
}
console.log('文件处理完成\n');
}
// 仅在 Node.js 环境中运行
if (typeof require !== 'undefined') {
demonstrateFileReading();
}
4. Web API 集成 - 分页数据处理:
// 分页 API 异步迭代器
class PaginatedAPIIterator {
constructor(baseUrl, options = {}) {
this.baseUrl = baseUrl;
this.pageSize = options.pageSize || 20;
this.headers = options.headers || {};
this.params = options.params || {};
this.totalPages = null;
}
async *[Symbol.asyncIterator]() {
let currentPage = 1;
while (true) {
try {
const response = await this.fetchPage(currentPage);
if (!response.data || response.data.length === 0) {
break;
}
// 更新总页数信息
if (response.pagination) {
this.totalPages = response.pagination.totalPages;
}
// 逐个产出数据项
for (const item of response.data) {
yield {
...item,
_metadata: {
page: currentPage,
totalPages: this.totalPages
}
};
}
// 检查是否还有更多页面
if (this.totalPages && currentPage >= this.totalPages) {
break;
}
// 如果当前页数据少于页大小,说明已经到最后一页
if (response.data.length < this.pageSize) {
break;
}
currentPage++;
} catch (error) {
console.error(`获取第 ${currentPage} 页数据失败:`, error.message);
throw error;
}
}
}
async fetchPage(page) {
const url = new URL(this.baseUrl);
// 添加分页参数
url.searchParams.set('page', page);
url.searchParams.set('limit', this.pageSize);
// 添加其他参数
Object.entries(this.params).forEach(([key, value]) => {
url.searchParams.set(key, value);
});
console.log(`🌐 请求第 ${page} 页数据: ${url}`);
const response = await fetch(url, {
headers: this.headers
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
// 添加过滤器
filter(predicate) {
const originalIterator = this[Symbol.asyncIterator].bind(this);
return {
async *[Symbol.asyncIterator]() {
for await (const item of { [Symbol.asyncIterator]: originalIterator }) {
if (await predicate(item)) {
yield item;
}
}
}
};
}
// 添加映射器
map(mapper) {
const originalIterator = this[Symbol.asyncIterator].bind(this);
return {
async *[Symbol.asyncIterator]() {
for await (const item of { [Symbol.asyncIterator]: originalIterator }) {
yield await mapper(item);
}
}
};
}
}
// 模拟分页 API 使用
async function demonstratePaginatedAPI() {
console.log('=== 分页 API 数据处理 ===');
// 模拟 fetch 函数(在实际环境中会使用真实的 fetch)
global.fetch = async (url) => {
const urlObj = new URL(url);
const page = parseInt(urlObj.searchParams.get('page') || '1');
const limit = parseInt(urlObj.searchParams.get('limit') || '20');
// 模拟 API 响应
await new Promise(resolve => setTimeout(resolve, 200));
const totalItems = 45;
const totalPages = Math.ceil(totalItems / limit);
const startIndex = (page - 1) * limit;
const endIndex = Math.min(startIndex + limit, totalItems);
const data = [];
for (let i = startIndex; i < endIndex; i++) {
data.push({
id: i + 1,
name: `Item ${i + 1}`,
category: i % 3 === 0 ? 'A' : i % 3 === 1 ? 'B' : 'C',
price: Math.floor(Math.random() * 100) + 10
});
}
return {
ok: true,
async json() {
return {
data,
pagination: {
currentPage: page,
totalPages,
totalItems,
hasNext: page < totalPages
}
};
}
};
};
// 使用分页迭代器
const apiIterator = new PaginatedAPIIterator('https://api.example.com/items', {
pageSize: 10,
params: { category: 'products' }
});
// 过滤和处理数据
const filteredItems = apiIterator
.filter(item => item.price > 50)
.map(item => ({
...item,
formattedPrice: `$${item.price}`,
isExpensive: item.price > 80
}));
let count = 0;
for await (const item of filteredItems) {
console.log(`🛍️ 商品 ${++count}:`, {
name: item.name,
category: item.category,
price: item.formattedPrice,
expensive: item.isExpensive,
page: item._metadata.page
});
// 限制输出数量
if (count >= 8) break;
}
console.log('API 数据处理完成\n');
}
demonstratePaginatedAPI();
5. 实时数据流 - WebSocket 异步迭代器:
// WebSocket 异步迭代器
class WebSocketAsyncIterator {
constructor(url, options = {}) {
this.url = url;
this.options = options;
this.messageQueue = [];
this.resolvers = [];
this.isConnected = false;
this.isClosed = false;
}
async connect() {
return new Promise((resolve, reject) => {
// 在实际环境中使用 WebSocket
// 这里模拟 WebSocket 连接
this.ws = {
readyState: 1, // WebSocket.OPEN
close: () => {
this.isClosed = true;
this.resolveAll(null, true);
},
send: (data) => {
console.log('📤 发送消息:', data);
}
};
this.isConnected = true;
// 模拟接收消息
this.simulateMessages();
resolve();
});
}
simulateMessages() {
let messageCount = 0;
const interval = setInterval(() => {
if (this.isClosed) {
clearInterval(interval);
return;
}
const message = {
id: ++messageCount,
type: 'data',
payload: {
timestamp: new Date().toISOString(),
value: Math.random() * 100,
status: 'active'
}
};
this.handleMessage(message);
if (messageCount >= 10) {
clearInterval(interval);
this.close();
}
}, 500);
}
handleMessage(message) {
if (this.resolvers.length > 0) {
const resolver = this.resolvers.shift();
resolver(message);
} else {
this.messageQueue.push(message);
}
}
async *[Symbol.asyncIterator]() {
if (!this.isConnected) {
await this.connect();
}
try {
while (!this.isClosed) {
const message = await this.getNextMessage();
if (message === null) break;
yield message;
}
} finally {
this.close();
}
}
async getNextMessage() {
if (this.messageQueue.length > 0) {
return this.messageQueue.shift();
}
if (this.isClosed) {
return null;
}
return new Promise((resolve) => {
this.resolvers.push(resolve);
});
}
resolveAll(value, isDone = false) {
while (this.resolvers.length > 0) {
const resolver = this.resolvers.shift();
resolver(isDone ? null : value);
}
}
close() {
if (!this.isClosed) {
this.isClosed = true;
if (this.ws) {
this.ws.close();
}
this.resolveAll(null, true);
}
}
send(message) {
if (this.isConnected && !this.isClosed) {
this.ws.send(JSON.stringify(message));
}
}
}
// 使用 WebSocket 异步迭代器
async function demonstrateWebSocketStream() {
console.log('=== WebSocket 实时数据流 ===');
const wsIterator = new WebSocketAsyncIterator('wss://api.example.com/stream');
try {
let messageCount = 0;
for await (const message of wsIterator) {
console.log(`📨 收到消息 ${++messageCount}:`, {
id: message.id,
timestamp: message.payload.timestamp,
value: message.payload.value.toFixed(2)
});
}
console.log('WebSocket 连接已关闭');
} catch (error) {
console.error('WebSocket 错误:', error);
}
console.log('实时数据流处理完成\n');
}
demonstrateWebSocketStream();
6. 异步迭代器工具库:
// 异步迭代器工具函数
class AsyncIteratorUtils {
// 将普通迭代器转换为异步迭代器
static async *fromSync(syncIterable) {
for (const item of syncIterable) {
yield item;
}
}
// 将 Promise 数组转换为异步迭代器
static async *fromPromises(promises) {
for (const promise of promises) {
yield await promise;
}
}
// 合并多个异步迭代器
static async *merge(...asyncIterables) {
const iterators = asyncIterables.map(iterable =>
iterable[Symbol.asyncIterator]()
);
const promises = iterators.map((iterator, index) =>
iterator.next().then(result => ({ ...result, index }))
);
while (promises.length > 0) {
const { value, done, index } = await Promise.race(promises);
if (!done) {
yield value;
// 替换已完成的 Promise
promises[index] = iterators[index].next()
.then(result => ({ ...result, index }));
} else {
// 移除已完成的迭代器
promises.splice(index, 1);
iterators.splice(index, 1);
// 更新剩余 Promise 的索引
for (let i = index; i < promises.length; i++) {
promises[i] = promises[i].then(result => ({
...result,
index: result.index > index ? result.index - 1 : result.index
}));
}
}
}
}
// 限制并发数量
static async *concurrent(asyncIterable, concurrency = 3) {
const iterator = asyncIterable[Symbol.asyncIterator]();
const running = new Set();
let done = false;
while (!done || running.size > 0) {
// 填充运行队列
while (running.size < concurrency && !done) {
const { value, done: isDone } = await iterator.next();
if (isDone) {
done = true;
break;
}
const promise = Promise.resolve(value)
.then(result => ({ result, success: true }))
.catch(error => ({ error, success: false }))
.finally(() => running.delete(promise));
running.add(promise);
}
if (running.size > 0) {
const { result, error, success } = await Promise.race(running);
if (success) {
yield result;
} else {
throw error;
}
}
}
}
// 收集异步迭代器的所有值
static async collect(asyncIterable) {
const results = [];
for await (const item of asyncIterable) {
results.push(item);
}
return results;
}
// 异步 reduce
static async reduce(asyncIterable, reducer, initialValue) {
let accumulator = initialValue;
let index = 0;
for await (const item of asyncIterable) {
accumulator = await reducer(accumulator, item, index++);
}
return accumulator;
}
}
// 使用工具库
async function demonstrateUtils() {
console.log('=== 异步迭代器工具库 ===');
// 创建测试数据
const asyncRange1 = new AsyncRange(1, 3, 100);
const asyncRange2 = new AsyncRange(10, 12, 150);
// 合并多个异步迭代器
console.log('合并异步迭代器:');
const merged = AsyncIteratorUtils.merge(asyncRange1, asyncRange2);
for await (const value of merged) {
console.log(`📊 合并值: ${value}`);
}
// 收集所有值
const range3 = new AsyncRange(1, 5, 50);
const collected = await AsyncIteratorUtils.collect(range3);
console.log('📦 收集的值:', collected);
// 异步 reduce
const range4 = new AsyncRange(1, 4, 30);
const sum = await AsyncIteratorUtils.reduce(
range4,
(acc, value) => acc + value,
0
);
console.log('📈 求和结果:', sum);
console.log('工具库演示完成\n');
}
demonstrateUtils();
异步迭代器的应用场景总结:
异步迭代器提供了一种优雅的方式来处理异步数据流,结合现代 JavaScript 的 async/await 语法,能够编写出既高效又易读的异步代码。
What are the common methods of Reflect API? What is its relationship with Proxy?
What are the common methods of Reflect API? What is its relationship with Proxy?
考察点:元编程与对象操作。
答案:
Reflect API 是 ES6 引入的新全局对象,它提供了一组与 Proxy 处理器方法对应的静态方法。Reflect 的设计目标是让对象操作变得更加函数化和统一化,同时为 Proxy 提供了默认行为的实现。
1. Reflect API 的常用方法概览:
// Reflect API 方法完整示例
class ReflectDemo {
constructor() {
this.name = 'Demo';
this.value = 42;
}
method() {
return 'Hello from method';
}
static staticMethod() {
return 'Hello from static method';
}
}
const demo = new ReflectDemo();
const obj = { x: 1, y: 2, z: 3 };
console.log('=== Reflect API 方法演示 ===\n');
// 1. Reflect.get - 获取属性值
console.log('1. Reflect.get:');
console.log('demo.name =', Reflect.get(demo, 'name')); // 'Demo'
console.log('obj.x =', Reflect.get(obj, 'x')); // 1
console.log('obj["y"] =', Reflect.get(obj, 'y')); // 2
console.log();
// 2. Reflect.set - 设置属性值
console.log('2. Reflect.set:');
console.log('设置 obj.x = 10:', Reflect.set(obj, 'x', 10)); // true
console.log('验证 obj.x =', obj.x); // 10
console.log('设置 demo.value = 100:', Reflect.set(demo, 'value', 100)); // true
console.log('验证 demo.value =', demo.value); // 100
console.log();
// 3. Reflect.has - 检查属性是否存在
console.log('3. Reflect.has:');
console.log('obj 中是否有 x:', Reflect.has(obj, 'x')); // true
console.log('obj 中是否有 w:', Reflect.has(obj, 'w')); // false
console.log('demo 中是否有 name:', Reflect.has(demo, 'name')); // true
console.log();
// 4. Reflect.deleteProperty - 删除属性
console.log('4. Reflect.deleteProperty:');
console.log('删除前 obj:', obj);
console.log('删除 obj.z:', Reflect.deleteProperty(obj, 'z')); // true
console.log('删除后 obj:', obj);
console.log();
// 5. Reflect.ownKeys - 获取所有自有属性键
console.log('5. Reflect.ownKeys:');
console.log('obj 的所有键:', Reflect.ownKeys(obj)); // ['x', 'y']
console.log('demo 的所有键:', Reflect.ownKeys(demo)); // ['name', 'value']
console.log();
// 6. Reflect.getPrototypeOf - 获取原型
console.log('6. Reflect.getPrototypeOf:');
console.log('obj 的原型是 Object.prototype:',
Reflect.getPrototypeOf(obj) === Object.prototype); // true
console.log('demo 的原型是 ReflectDemo.prototype:',
Reflect.getPrototypeOf(demo) === ReflectDemo.prototype); // true
console.log();
// 7. Reflect.setPrototypeOf - 设置原型
console.log('7. Reflect.setPrototypeOf:');
const newProto = { customMethod() { return 'custom'; } };
console.log('设置新原型:', Reflect.setPrototypeOf(obj, newProto)); // true
console.log('调用原型方法:', obj.customMethod()); // 'custom'
console.log();
2. 深入理解各个方法的使用场景:
// 详细的 Reflect 方法使用案例
class AdvancedReflectUsage {
// Reflect.defineProperty - 定义属性
static demonstrateDefineProperty() {
console.log('=== Reflect.defineProperty 演示 ===');
const obj = {};
// 定义数据属性
const success1 = Reflect.defineProperty(obj, 'name', {
value: 'Alice',
writable: true,
enumerable: true,
configurable: true
});
console.log('定义数据属性成功:', success1); // true
// 定义访问器属性
let _age = 25;
const success2 = Reflect.defineProperty(obj, 'age', {
get() { return _age; },
set(value) {
if (value >= 0) _age = value;
},
enumerable: true,
configurable: true
});
console.log('定义访问器属性成功:', success2); // true
console.log('obj:', obj); // { name: 'Alice' }
console.log('obj.age:', obj.age); // 25
obj.age = 30;
console.log('设置后 obj.age:', obj.age); // 30
console.log();
return obj;
}
// Reflect.getOwnPropertyDescriptor - 获取属性描述符
static demonstrateGetOwnPropertyDescriptor() {
console.log('=== Reflect.getOwnPropertyDescriptor 演示 ===');
const obj = this.demonstrateDefineProperty();
const nameDesc = Reflect.getOwnPropertyDescriptor(obj, 'name');
console.log('name 属性描述符:', nameDesc);
const ageDesc = Reflect.getOwnPropertyDescriptor(obj, 'age');
console.log('age 属性描述符:', {
get: ageDesc.get ? '[Function: get]' : undefined,
set: ageDesc.set ? '[Function: set]' : undefined,
enumerable: ageDesc.enumerable,
configurable: ageDesc.configurable
});
console.log();
}
// Reflect.apply - 调用函数
static demonstrateApply() {
console.log('=== Reflect.apply 演示 ===');
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: 'Bob' };
// 使用 Reflect.apply 调用函数
const result1 = Reflect.apply(greet, person, ['Hello', '!']);
console.log('Reflect.apply 结果:', result1); // 'Hello, Bob!'
// 对比传统方式
const result2 = greet.call(person, 'Hi', '.');
console.log('传统 call 结果:', result2); // 'Hi, Bob.'
// 数组方法的应用
const numbers = [1, 2, 3, 4, 5];
const max = Reflect.apply(Math.max, null, numbers);
console.log('数组最大值:', max); // 5
console.log();
}
// Reflect.construct - 构造实例
static demonstrateConstruct() {
console.log('=== Reflect.construct 演示 ===');
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
return `I'm ${this.name}, ${this.age} years old.`;
}
}
// 使用 Reflect.construct 创建实例
const person1 = Reflect.construct(Person, ['Charlie', 28]);
console.log('Reflect.construct 创建:', person1.introduce());
// 对比 new 操作符
const person2 = new Person('David', 32);
console.log('new 操作符创建:', person2.introduce());
// 指定不同的原型
class Employee extends Person {
constructor(name, age, department) {
super(name, age);
this.department = department;
}
}
const employee = Reflect.construct(Person, ['Eve', 27], Employee);
console.log('指定原型创建:', employee instanceof Employee); // true
console.log('employee:', employee);
console.log();
}
}
// 运行演示
AdvancedReflectUsage.demonstrateDefineProperty();
AdvancedReflectUsage.demonstrateGetOwnPropertyDescriptor();
AdvancedReflectUsage.demonstrateApply();
AdvancedReflectUsage.demonstrateConstruct();
3. Reflect 与 Proxy 的关系:
// Reflect 与 Proxy 的完美结合
class ProxyReflectIntegration {
// 基础的 Proxy + Reflect 组合
static createBasicProxy(target) {
return new Proxy(target, {
get(obj, prop, receiver) {
console.log(`📖 读取属性: ${String(prop)}`);
return Reflect.get(obj, prop, receiver);
},
set(obj, prop, value, receiver) {
console.log(`✏️ 设置属性: ${String(prop)} = ${value}`);
return Reflect.set(obj, prop, value, receiver);
},
has(obj, prop) {
console.log(`🔍 检查属性: ${String(prop)}`);
return Reflect.has(obj, prop);
},
deleteProperty(obj, prop) {
console.log(`🗑️ 删除属性: ${String(prop)}`);
return Reflect.deleteProperty(obj, prop);
}
});
}
// 高级的验证代理
static createValidatedProxy(target, validators = {}) {
return new Proxy(target, {
set(obj, prop, value, receiver) {
// 使用验证器
if (validators[prop]) {
const isValid = validators[prop](value);
if (!isValid) {
throw new Error(`Invalid value for ${String(prop)}: ${value}`);
}
}
console.log(`✅ 验证通过,设置: ${String(prop)} = ${value}`);
return Reflect.set(obj, prop, value, receiver);
},
get(obj, prop, receiver) {
const value = Reflect.get(obj, prop, receiver);
// 自动转换某些属性
if (prop === 'fullName' && !Reflect.has(obj, prop)) {
return `${obj.firstName || ''} ${obj.lastName || ''}`.trim();
}
return value;
},
ownKeys(obj) {
const keys = Reflect.ownKeys(obj);
// 添加计算属性到键列表
if (obj.firstName && obj.lastName && !keys.includes('fullName')) {
keys.push('fullName');
}
return keys;
},
getOwnPropertyDescriptor(obj, prop) {
if (prop === 'fullName' && obj.firstName && obj.lastName) {
return {
enumerable: true,
configurable: true,
get() {
return `${obj.firstName} ${obj.lastName}`;
}
};
}
return Reflect.getOwnPropertyDescriptor(obj, prop);
}
});
}
// 方法调用拦截器
static createMethodInterceptor(target) {
return new Proxy(target, {
get(obj, prop, receiver) {
const value = Reflect.get(obj, prop, receiver);
// 如果是方法,包装它
if (typeof value === 'function') {
return new Proxy(value, {
apply(fn, thisArg, argumentsList) {
console.log(`🎯 调用方法: ${String(prop)}`);
console.log(`📥 参数:`, argumentsList);
const startTime = performance.now();
try {
const result = Reflect.apply(fn, thisArg, argumentsList);
const endTime = performance.now();
console.log(`⏱️ 执行时间: ${(endTime - startTime).toFixed(2)}ms`);
console.log(`📤 返回值:`, result);
return result;
} catch (error) {
console.log(`❌ 方法执行失败:`, error.message);
throw error;
}
}
});
}
return value;
}
});
}
}
// 使用示例
console.log('=== Proxy + Reflect 集成演示 ===\n');
// 1. 基础代理
console.log('1. 基础代理:');
const basicObj = { name: 'Test', value: 123 };
const basicProxy = ProxyReflectIntegration.createBasicProxy(basicObj);
console.log('访问 name:', basicProxy.name);
basicProxy.newProp = 'new value';
console.log('检查 newProp:', 'newProp' in basicProxy);
delete basicProxy.value;
console.log();
// 2. 验证代理
console.log('2. 验证代理:');
const user = {};
const validatedUser = ProxyReflectIntegration.createValidatedProxy(user, {
age: (value) => typeof value === 'number' && value >= 0 && value <= 150,
email: (value) => typeof value === 'string' && value.includes('@')
});
validatedUser.firstName = 'John';
validatedUser.lastName = 'Doe';
validatedUser.age = 30;
console.log('计算属性 fullName:', validatedUser.fullName);
console.log('对象键:', Object.keys(validatedUser));
try {
validatedUser.age = -5; // 会抛出错误
} catch (error) {
console.log('验证失败:', error.message);
}
console.log();
// 3. 方法拦截器
console.log('3. 方法拦截器:');
class Calculator {
add(a, b) {
return a + b;
}
multiply(a, b) {
const result = a * b;
// 模拟一些计算时间
for (let i = 0; i < 1000000; i++) {
Math.random();
}
return result;
}
divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
}
const calc = new Calculator();
const interceptedCalc = ProxyReflectIntegration.createMethodInterceptor(calc);
console.log('执行加法:');
interceptedCalc.add(5, 3);
console.log('\n执行乘法:');
interceptedCalc.multiply(4, 7);
console.log('\n执行除法(错误情况):');
try {
interceptedCalc.divide(10, 0);
} catch (error) {
console.log('捕获到错误');
}
4. Reflect 的优势和最佳实践:
// Reflect API 的优势展示
class ReflectAdvantages {
// 1. 函数式的对象操作
static functionalObjectOperations() {
console.log('=== 函数式对象操作 ===');
const obj = { a: 1, b: 2, c: 3 };
// 传统方式 vs Reflect 方式
console.log('传统方式的问题:');
// 传统的属性操作可能会有异常
try {
obj.newProp = 'value'; // 可能会静默失败(在严格模式下)
delete obj.c;
console.log('has property a:', 'a' in obj);
} catch (error) {
console.log('操作失败:', error.message);
}
console.log('\nReflect 方式的优势:');
// Reflect 方法总是返回布尔值表示成功/失败
const setResult = Reflect.set(obj, 'reflectProp', 'reflect value');
console.log('设置属性成功:', setResult);
const deleteResult = Reflect.deleteProperty(obj, 'b');
console.log('删除属性成功:', deleteResult);
const hasResult = Reflect.has(obj, 'a');
console.log('属性存在:', hasResult);
console.log('最终对象:', obj);
console.log();
}
// 2. 更好的错误处理
static betterErrorHandling() {
console.log('=== 更好的错误处理 ===');
const obj = {};
// 使用 Object.defineProperty 的问题
try {
Object.defineProperty(obj, 'prop1', {
value: 'value1',
writable: false,
configurable: false
});
// 这会抛出错误
Object.defineProperty(obj, 'prop1', {
value: 'new value'
});
} catch (error) {
console.log('Object.defineProperty 错误:', error.message);
}
// 使用 Reflect.defineProperty 的优势
const success1 = Reflect.defineProperty(obj, 'prop2', {
value: 'value2',
writable: false,
configurable: false
});
console.log('第一次定义成功:', success1); // true
const success2 = Reflect.defineProperty(obj, 'prop2', {
value: 'new value'
});
console.log('第二次定义成功:', success2); // false,但不会抛错
console.log();
}
// 3. 与 Proxy 的完美配合
static proxyIntegration() {
console.log('=== 与 Proxy 的完美配合 ===');
// 没有 Reflect 的 Proxy 处理器
const withoutReflect = new Proxy({}, {
get(target, property) {
console.log(`Getting ${String(property)}`);
return target[property]; // 可能不会正确处理 receiver
},
set(target, property, value) {
console.log(`Setting ${String(property)} = ${value}`);
target[property] = value;
return true; // 总是返回 true,可能不准确
}
});
// 使用 Reflect 的 Proxy 处理器
const withReflect = new Proxy({}, {
get(target, property, receiver) {
console.log(`[Reflect] Getting ${String(property)}`);
return Reflect.get(target, property, receiver); // 正确处理 receiver
},
set(target, property, value, receiver) {
console.log(`[Reflect] Setting ${String(property)} = ${value}`);
return Reflect.set(target, property, value, receiver); // 返回实际结果
}
});
console.log('测试不使用 Reflect 的代理:');
withoutReflect.test = 'value';
console.log('获取值:', withoutReflect.test);
console.log('\n测试使用 Reflect 的代理:');
withReflect.test = 'value';
console.log('获取值:', withReflect.test);
console.log();
}
// 4. 动态对象操作工具
static createObjectUtilities() {
console.log('=== 动态对象操作工具 ===');
class ObjectUtils {
// 安全的属性设置
static safeSet(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!Reflect.has(current, key) || typeof current[key] !== 'object') {
const success = Reflect.set(current, key, {});
if (!success) return false;
}
current = current[key];
}
return Reflect.set(current, keys[keys.length - 1], value);
}
// 安全的属性获取
static safeGet(obj, path, defaultValue = undefined) {
const keys = path.split('.');
let current = obj;
for (const key of keys) {
if (!Reflect.has(current, key)) {
return defaultValue;
}
current = Reflect.get(current, key);
}
return current;
}
// 深度合并对象
static deepMerge(target, source) {
const result = { ...target };
for (const key of Reflect.ownKeys(source)) {
const sourceValue = Reflect.get(source, key);
const targetValue = Reflect.get(result, key);
if (typeof sourceValue === 'object' && sourceValue !== null &&
typeof targetValue === 'object' && targetValue !== null) {
Reflect.set(result, key, this.deepMerge(targetValue, sourceValue));
} else {
Reflect.set(result, key, sourceValue);
}
}
return result;
}
// 对象属性映射
static mapObject(obj, mapper) {
const result = {};
for (const key of Reflect.ownKeys(obj)) {
const value = Reflect.get(obj, key);
const [newKey, newValue] = mapper(key, value);
Reflect.set(result, newKey, newValue);
}
return result;
}
}
// 测试工具函数
const testObj = { a: { b: { c: 1 } } };
console.log('安全设置属性:');
ObjectUtils.safeSet(testObj, 'x.y.z', 'deep value');
console.log('设置后:', testObj);
console.log('安全获取属性:');
console.log('存在的路径:', ObjectUtils.safeGet(testObj, 'a.b.c'));
console.log('不存在的路径:', ObjectUtils.safeGet(testObj, 'a.b.d', 'default'));
console.log('深度合并:');
const obj1 = { a: { x: 1, y: 2 }, b: 3 };
const obj2 = { a: { y: 20, z: 30 }, c: 4 };
const merged = ObjectUtils.deepMerge(obj1, obj2);
console.log('合并结果:', merged);
console.log('对象映射:');
const numbers = { one: 1, two: 2, three: 3 };
const doubled = ObjectUtils.mapObject(numbers, (key, value) => [
key.toUpperCase(),
value * 2
]);
console.log('映射结果:', doubled);
return ObjectUtils;
}
}
// 运行所有演示
ReflectAdvantages.functionalObjectOperations();
ReflectAdvantages.betterErrorHandling();
ReflectAdvantages.proxyIntegration();
ReflectAdvantages.createObjectUtilities();
Reflect API 的核心价值总结:
Reflect API 虽然看似简单,但它是现代 JavaScript 元编程的重要基础,特别是在结合 Proxy 使用时,能够创建出功能强大且安全的对象操作模式。
How does ES6 optimize performance? What new features help improve performance?
How does ES6 optimize performance? What new features help improve performance?
考察点:尾调用优化、懒加载、数据结构选择。
答案:
ES6 引入了多个有助于性能提升的新特性,这些特性不仅提供了更简洁的语法,更重要的是在底层实现上带来了性能优化。让我们深入分析这些性能优化点。
1. let/const vs var 的性能优化:
// 性能对比演示:let/const vs var
class ScopePerformanceComparison {
// 测试块级作用域的性能优势
static testBlockScopePerformance() {
console.log('=== 块级作用域性能测试 ===');
// 使用 var 的传统循环(可能的性能问题)
console.time('var 循环');
for (var i = 0; i < 1000000; i++) {
// var 变量会被提升,可能影响优化
var temp = i * 2;
}
console.timeEnd('var 循环');
// 使用 let 的现代循环(更好的优化)
console.time('let 循环');
for (let j = 0; j < 1000000; j++) {
// let 变量有明确的作用域,利于引擎优化
let temp = j * 2;
}
console.timeEnd('let 循环');
// const 用于不变值(最佳优化)
console.time('const 常量');
const multiplier = 2;
for (let k = 0; k < 1000000; k++) {
// 引擎知道 multiplier 不会改变,可以进行激进优化
const result = k * multiplier;
}
console.timeEnd('const 常量');
console.log();
}
}
// 运行测试
ScopePerformanceComparison.testBlockScopePerformance();
2. 箭头函数的性能优势:
// 箭头函数性能分析
class ArrowFunctionPerformance {
constructor() {
this.data = Array.from({ length: 100000 }, (_, i) => i);
}
// 测试函数绑定性能
testFunctionBinding() {
console.log('=== 函数绑定性能测试 ===');
// 传统函数需要绑定 this
console.time('传统函数 + bind');
const traditionalResult = this.data.map(function(x) {
return this.processValue(x);
}.bind(this));
console.timeEnd('传统函数 + bind');
// 箭头函数自动绑定词法 this
console.time('箭头函数');
const arrowResult = this.data.map(x => this.processValue(x));
console.timeEnd('箭头函数');
console.log('结果长度一致:', traditionalResult.length === arrowResult.length);
console.log();
}
processValue(x) {
return x * 2 + 1;
}
}
const arrowPerf = new ArrowFunctionPerformance();
arrowPerf.testFunctionBinding();
3. 新数据结构的性能优势:
// ES6 数据结构性能分析
class ES6DataStructurePerformance {
static testSetVsArray() {
console.log('=== Set vs Array 性能测试 ===');
const size = 10000;
const testData = Array.from({ length: size }, (_, i) => i);
// 创建测试数据
const testArray = [...testData];
const testSet = new Set(testData);
// 查找性能测试
const searchItems = Array.from({ length: 1000 }, () =>
Math.floor(Math.random() * size));
console.time('Array includes 查找');
let foundInArray = 0;
for (const item of searchItems) {
if (testArray.includes(item)) {
foundInArray++;
}
}
console.timeEnd('Array includes 查找');
console.time('Set has 查找');
let foundInSet = 0;
for (const item of searchItems) {
if (testSet.has(item)) {
foundInSet++;
}
}
console.timeEnd('Set has 查找');
console.log('Set查找速度明显快于Array.includes');
console.log();
}
static testMapVsObject() {
console.log('=== Map vs Object 性能测试 ===');
const size = 10000;
const testObj = {};
const testMap = new Map();
// 插入性能测试
console.time('Object 属性设置');
for (let i = 0; i < size; i++) {
testObj[`key${i}`] = `value${i}`;
}
console.timeEnd('Object 属性设置');
console.time('Map set 操作');
for (let i = 0; i < size; i++) {
testMap.set(`key${i}`, `value${i}`);
}
console.timeEnd('Map set 操作');
console.log('Map在频繁的键值操作中性能更稳定');
console.log();
}
}
ES6DataStructurePerformance.testSetVsArray();
ES6DataStructurePerformance.testMapVsObject();
4. 迭代器和 for…of 的性能:
// 迭代器性能分析
class IteratorPerformance {
constructor() {
this.testArray = Array.from({ length: 100000 }, (_, i) => i);
}
// 数组遍历性能对比
testArrayIteration() {
console.log('=== 数组遍历性能测试 ===');
let sum = 0;
// 传统 for 循环
console.time('传统 for 循环');
sum = 0;
for (let i = 0; i < this.testArray.length; i++) {
sum += this.testArray[i];
}
console.timeEnd('传统 for 循环');
// for...of 循环
console.time('for...of 循环');
sum = 0;
for (const value of this.testArray) {
sum += value;
}
console.timeEnd('for...of 循环');
// forEach 方法
console.time('forEach 方法');
sum = 0;
this.testArray.forEach(value => sum += value);
console.timeEnd('forEach 方法');
console.log('for...of 在大多数情况下性能接近传统for循环');
console.log();
}
}
const iterPerf = new IteratorPerformance();
iterPerf.testArrayIteration();
5. 模板字符串性能分析:
// 模板字符串性能分析
class TemplateStringPerformance {
static testStringConcatenation() {
console.log('=== 字符串拼接性能测试 ===');
const name = 'JavaScript';
const version = 'ES6';
const year = 2015;
const iterations = 100000;
// 传统字符串拼接
console.time('传统拼接');
for (let i = 0; i < iterations; i++) {
const result = 'Hello ' + name + ', welcome to ' + version + ' (' + year + ')!';
}
console.timeEnd('传统拼接');
// 模板字符串
console.time('模板字符串');
for (let i = 0; i < iterations; i++) {
const result = `Hello ${name}, welcome to ${version} (${year})!`;
}
console.timeEnd('模板字符串');
console.log('模板字符串性能与传统拼接相当,但代码更清晰');
console.log();
}
}
TemplateStringPerformance.testStringConcatenation();
6. 解构赋值的性能考量:
// 解构赋值性能分析
class DestructuringPerformance {
static testObjectDestructuring() {
console.log('=== 对象解构性能测试 ===');
const testObj = {
a: 1, b: 2, c: 3, d: 4, e: 5,
nested: { x: 10, y: 20, z: 30 }
};
const iterations = 1000000;
// 传统属性访问
console.time('传统属性访问');
for (let i = 0; i < iterations; i++) {
const a = testObj.a;
const b = testObj.b;
const c = testObj.c;
}
console.timeEnd('传统属性访问');
// 对象解构
console.time('对象解构');
for (let i = 0; i < iterations; i++) {
const { a, b, c } = testObj;
}
console.timeEnd('对象解构');
console.log('适度的解构性能良好,但避免过度深层解构');
console.log();
}
}
DestructuringPerformance.testObjectDestructuring();
7. ES6 模块的性能优势:
// ES6 模块性能优势演示
console.log('=== ES6 模块性能优势 ===');
console.log('1. 静态分析:编译时确定依赖关系');
console.log('2. Tree Shaking:死代码消除');
console.log('3. 循环依赖处理:更安全的依赖管理');
console.log('4. 懒加载:动态导入支持按需加载');
// 动态导入示例(性能优化)
async function loadModuleWhenNeeded() {
if (someCondition) {
// 只在需要时加载模块
const module = await import('./heavyModule.js');
return module.default;
}
}
// Tree Shaking 友好的导出
export const utilFunction1 = () => { /* ... */ };
export const utilFunction2 = () => { /* ... */ };
// 未使用的 utilFunction2 会被打包工具移除
8. 生成器的内存优势:
// 生成器内存性能演示
class GeneratorPerformance {
// 传统方式:一次性生成所有数据
static generateArrayTraditional(size) {
console.time('传统数组生成');
const result = [];
for (let i = 0; i < size; i++) {
result.push(i * i);
}
console.timeEnd('传统数组生成');
return result;
}
// 生成器方式:按需生成
static *generateNumbersLazy(size) {
for (let i = 0; i < size; i++) {
yield i * i;
}
}
static testMemoryUsage() {
console.log('=== 生成器内存性能测试 ===');
const size = 100000;
// 传统方式:占用大量内存
const traditionalArray = this.generateArrayTraditional(size);
console.log('传统数组内存占用:约', (size * 8 / 1024 / 1024).toFixed(2), 'MB');
// 生成器方式:按需生成,节省内存
console.time('生成器创建');
const lazyGenerator = this.generateNumbersLazy(size);
console.timeEnd('生成器创建');
console.log('生成器内存占用:极小(延迟计算)');
// 使用生成器进行部分计算
console.time('生成器部分使用');
let sum = 0;
let count = 0;
for (const value of lazyGenerator) {
sum += value;
count++;
if (count >= 1000) break; // 只使用前1000个
}
console.timeEnd('生成器部分使用');
console.log('只计算了需要的部分,节省了99%的计算');
console.log();
}
}
GeneratorPerformance.testMemoryUsage();
9. 实际应用中的性能优化策略:
// ES6 性能优化最佳实践
class ES6PerformanceBestPractices {
// 1. 合理使用解构赋值
static optimizedDestructuring() {
// ❌ 避免过度深层解构
function inefficient({ user: { profile: { settings: { theme } } } }) {
return theme;
}
// ✅ 推荐的优化方式
function efficient({ user }) {
return user.profile.settings.theme;
}
}
// 2. 智能使用 const 和 let
static optimizedVariableDeclarations() {
// ✅ 使用 const 帮助引擎优化
const MULTIPLIER = 2;
const processNumbers = (numbers) => {
return numbers.map(num => num * MULTIPLIER); // 引擎可以内联常量
};
}
// 3. 高效的数组操作
static optimizedArrayOperations(data) {
// ❌ 多次遍历创建中间数组
const inefficient = data
.filter(item => item.active)
.map(item => item.value * 2)
.filter(value => value > 100);
// ✅ 单次遍历优化
const efficient = data.reduce((acc, item) => {
if (item.active) {
const processed = item.value * 2;
if (processed > 100) {
acc.push(processed);
}
}
return acc;
}, []);
return { inefficient, efficient };
}
// 4. 字符串操作优化
static optimizedStringOperations(items) {
// ❌ 频繁的字符串拼接
let inefficient = '';
for (const item of items) {
inefficient += `<li>${item}</li>`;
}
// ✅ 数组缓冲优化
const buffer = [];
for (const item of items) {
buffer.push(`<li>${item}</li>`);
}
const efficient = buffer.join('');
return { inefficient, efficient };
}
}
10. 性能监控和测试工具:
// ES6 性能测试工具类
class PerformanceProfiler {
static profile(name, fn, iterations = 1000000) {
console.log(`\n=== 性能测试: ${name} ===`);
// 预热
for (let i = 0; i < 1000; i++) {
fn();
}
// 正式测试
const startTime = performance.now();
const startMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
for (let i = 0; i < iterations; i++) {
fn();
}
const endTime = performance.now();
const endMemory = performance.memory ? performance.memory.usedJSHeapSize : 0;
console.log(`执行时间: ${(endTime - startTime).toFixed(2)}ms`);
console.log(`每次调用: ${((endTime - startTime) / iterations * 1000).toFixed(4)}μs`);
if (performance.memory) {
console.log(`内存变化: ${((endMemory - startMemory) / 1024 / 1024).toFixed(2)}MB`);
}
return {
totalTime: endTime - startTime,
avgTime: (endTime - startTime) / iterations,
memoryDelta: endMemory - startMemory
};
}
static compare(tests) {
console.log('\n=== 性能对比结果 ===');
const results = {};
for (const [name, fn] of Object.entries(tests)) {
results[name] = this.profile(name, fn, 100000);
}
// 找出最快的实现
const fastest = Object.keys(results).reduce((a, b) =>
results[a].totalTime < results[b].totalTime ? a : b);
console.log(`\n最快实现: ${fastest}`);
// 显示相对性能
Object.keys(results).forEach(name => {
const ratio = (results[name].totalTime / results[fastest].totalTime).toFixed(2);
console.log(`${name}: ${ratio}x ${name === fastest ? '(基准)' : '慢于基准'}`);
});
return results;
}
}
// 使用示例
const tests = {
'ES5 函数': function() {
return [1, 2, 3].map(function(x) { return x * 2; });
},
'ES6 箭头函数': function() {
return [1, 2, 3].map(x => x * 2);
},
'ES6 解构': function() {
const [a, b, c] = [1, 2, 3];
return [a * 2, b * 2, c * 2];
}
};
PerformanceProfiler.compare(tests);
ES6 性能优化核心要点总结:
语法级优化:
const/let 提供更好的作用域管理,帮助引擎优化数据结构优化:
Set/Map 在特定场景下性能显著优于传统结构WeakMap/WeakSet 防止内存泄漏迭代优化:
for...of 和迭代器支持更高效的遍历模块化优势:
最佳实践:
这些优化不仅提升了代码的可读性和维护性,更重要的是为 JavaScript 引擎提供了更多优化机会,从而获得更好的运行时性能。
How to implement a simple template engine (supporting interpolation expressions)?
How to implement a simple template engine (supporting interpolation expressions)?
考察点:字符串处理与正则表达式。
答案:
实现一个模板引擎涉及字符串解析、表达式求值、作用域管理等多个技术点。让我们从基础到高级,逐步实现一个功能完整的ES6模板引擎。
1. 基础版本 - 简单插值:
// 基础模板引擎实现
class SimpleTemplateEngine {
constructor() {
// 插值表达式的正则模式:{{ expression }}
this.interpolationPattern = /\{\{([^}]+)\}\}/g;
}
// 基础渲染方法
render(template, data = {}) {
return template.replace(this.interpolationPattern, (match, expression) => {
// 清理表达式(移除首尾空格)
const cleanExpression = expression.trim();
try {
// 简单的属性访问
return this.evaluateExpression(cleanExpression, data);
} catch (error) {
console.warn(`模板表达式错误: ${cleanExpression}`, error);
return match; // 返回原始表达式
}
});
}
// 简单的表达式求值
evaluateExpression(expression, data) {
// 支持点号访问:user.name
const properties = expression.split('.');
let result = data;
for (const prop of properties) {
if (result && typeof result === 'object' && prop in result) {
result = result[prop];
} else {
return ''; // 属性不存在返回空字符串
}
}
return result != null ? String(result) : '';
}
// 使用示例
static demo() {
console.log('=== 基础模板引擎演示 ===');
const engine = new SimpleTemplateEngine();
// 测试数据
const data = {
name: 'Alice',
age: 30,
user: {
profile: {
email: '[email protected]'
}
}
};
// 测试模板
const templates = [
'Hello, {{ name }}!',
'You are {{ age }} years old.',
'Email: {{ user.profile.email }}',
'Missing: {{ missing.property }}' // 测试不存在的属性
];
templates.forEach(template => {
const result = engine.render(template, data);
console.log(`模板: ${template}`);
console.log(`结果: ${result}\n`);
});
}
}
SimpleTemplateEngine.demo();
2. 进阶版本 - 支持表达式和过滤器:
// 进阶模板引擎 - 支持表达式计算和过滤器
class AdvancedTemplateEngine {
constructor() {
// 更复杂的插值模式,支持过滤器:{{ expression | filter }}
this.interpolationPattern = /\{\{([^}]+)\}\}/g;
this.filterPattern = /\|/;
// 内置过滤器
this.filters = new Map([
['upper', value => String(value).toUpperCase()],
['lower', value => String(value).toLowerCase()],
['capitalize', value => {
const str = String(value);
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}],
['json', value => JSON.stringify(value)],
['default', (value, defaultValue = '') => value != null ? value : defaultValue],
['truncate', (value, length = 50) => {
const str = String(value);
return str.length > length ? str.substring(0, length) + '...' : str;
}],
['date', value => {
if (value instanceof Date) return value.toLocaleDateString();
if (typeof value === 'string' || typeof value === 'number') {
return new Date(value).toLocaleDateString();
}
return String(value);
}]
]);
}
// 添加自定义过滤器
addFilter(name, filterFunction) {
this.filters.set(name, filterFunction);
return this;
}
// 渲染模板
render(template, data = {}) {
return template.replace(this.interpolationPattern, (match, expression) => {
try {
return this.processExpression(expression.trim(), data);
} catch (error) {
console.warn(`模板表达式错误: ${expression}`, error);
return match;
}
});
}
// 处理表达式(包括过滤器)
processExpression(expression, data) {
// 检查是否有过滤器
if (this.filterPattern.test(expression)) {
const [expr, ...filterChain] = expression.split('|').map(s => s.trim());
let result = this.evaluateExpression(expr, data);
// 应用过滤器链
for (const filterExpr of filterChain) {
result = this.applyFilter(result, filterExpr);
}
return result;
} else {
return this.evaluateExpression(expression, data);
}
}
// 应用过滤器
applyFilter(value, filterExpression) {
// 解析过滤器名称和参数:filter(arg1, arg2)
const filterMatch = filterExpression.match(/^([^(]+)(?:\(([^)]*)\))?$/);
if (!filterMatch) {
throw new Error(`无效的过滤器表达式: ${filterExpression}`);
}
const filterName = filterMatch[1].trim();
const argsString = filterMatch[2];
if (!this.filters.has(filterName)) {
throw new Error(`未知的过滤器: ${filterName}`);
}
const filter = this.filters.get(filterName);
// 解析参数
if (argsString) {
const args = this.parseArguments(argsString);
return filter(value, ...args);
} else {
return filter(value);
}
}
// 解析过滤器参数
parseArguments(argsString) {
const args = [];
const argPattern = /(?:[^,"]|"[^"]*")+/g;
const matches = argsString.match(argPattern) || [];
for (const match of matches) {
const arg = match.trim();
// 解析不同类型的参数
if (arg.startsWith('"') && arg.endsWith('"')) {
// 字符串参数
args.push(arg.slice(1, -1));
} else if (!isNaN(arg)) {
// 数字参数
args.push(Number(arg));
} else if (arg === 'true' || arg === 'false') {
// 布尔参数
args.push(arg === 'true');
} else {
// 其他类型保持字符串
args.push(arg);
}
}
return args;
}
// 支持更复杂的表达式求值
evaluateExpression(expression, data) {
// 支持数组索引:items[0],对象属性:user.name
try {
// 创建安全的求值环境
const safeEval = this.createSafeEvaluator(data);
return safeEval(expression);
} catch (error) {
// 如果安全求值失败,回退到简单属性访问
return this.simplePropertyAccess(expression, data);
}
}
// 创建安全的求值器
createSafeEvaluator(data) {
return (expression) => {
// 白名单安全的表达式模式
const safePatterns = [
/^[a-zA-Z_$][a-zA-Z0-9_$]*$/, // 简单变量名
/^[a-zA-Z_$][a-zA-Z0-9_$.[\]"']*$/ // 属性访问和数组访问
];
const isSafe = safePatterns.some(pattern => pattern.test(expression));
if (!isSafe) {
throw new Error(`不安全的表达式: ${expression}`);
}
// 使用 Function 构造器创建安全的求值环境
const keys = Object.keys(data);
const values = keys.map(key => data[key]);
try {
const func = new Function(...keys, `return ${expression};`);
return func(...values);
} catch (error) {
throw new Error(`表达式求值失败: ${expression}`);
}
};
}
// 简单属性访问(回退方案)
simplePropertyAccess(expression, data) {
const properties = expression.split('.');
let result = data;
for (const prop of properties) {
if (result && typeof result === 'object') {
// 处理数组索引:prop[index]
const arrayMatch = prop.match(/^([^[]+)\[(\d+)\]$/);
if (arrayMatch) {
const [, arrayName, index] = arrayMatch;
result = result[arrayName];
if (Array.isArray(result)) {
result = result[parseInt(index)];
} else {
return '';
}
} else {
result = result[prop];
}
} else {
return '';
}
}
return result != null ? String(result) : '';
}
// 使用示例
static demo() {
console.log('=== 进阶模板引擎演示 ===');
const engine = new AdvancedTemplateEngine();
// 添加自定义过滤器
engine.addFilter('repeat', (value, times = 2) =>
String(value).repeat(times));
// 测试数据
const data = {
name: 'alice johnson',
age: 25,
items: ['apple', 'banana', 'orange'],
user: {
profile: {
bio: 'This is a very long biography that should be truncated when displayed in the template to maintain readability.'
}
},
createdAt: new Date(),
description: null
};
// 测试模板
const templates = [
'{{ name | capitalize }}',
'{{ name | upper }}',
'{{ items | json }}',
'{{ items[1] | upper }}',
'{{ user.profile.bio | truncate(30) }}',
'{{ description | default("No description") }}',
'{{ createdAt | date }}',
'{{ name | repeat(3) }}',
'{{ age }} years old'
];
templates.forEach(template => {
const result = engine.render(template, data);
console.log(`模板: ${template}`);
console.log(`结果: ${result}\n`);
});
}
}
AdvancedTemplateEngine.demo();
3. 完整版本 - 支持条件语句和循环:
// 完整功能模板引擎
class FullFeaturedTemplateEngine {
constructor() {
// 各种模板语法的正则模式
this.patterns = {
interpolation: /\{\{([^}]+)\}\}/g,
ifBlock: /\{\{\s*#if\s+([^}]+)\s*\}\}([\s\S]*?)\{\{\s*\/if\s*\}\}/g,
ifElseBlock: /\{\{\s*#if\s+([^}]+)\s*\}\}([\s\S]*?)\{\{\s*#else\s*\}\}([\s\S]*?)\{\{\s*\/if\s*\}\}/g,
eachBlock: /\{\{\s*#each\s+([^}]+)\s*\}\}([\s\S]*?)\{\{\s*\/each\s*\}\}/g,
unlessBlock: /\{\{\s*#unless\s+([^}]+)\s*\}\}([\s\S]*?)\{\{\s*\/unless\s*\}\}/g
};
// 继承过滤器功能
this.filters = new Map([
['upper', value => String(value).toUpperCase()],
['lower', value => String(value).toLowerCase()],
['capitalize', value => {
const str = String(value);
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}],
['json', value => JSON.stringify(value, null, 2)],
['default', (value, defaultValue = '') => value != null ? value : defaultValue],
['truncate', (value, length = 50) => {
const str = String(value);
return str.length > length ? str.substring(0, length) + '...' : str;
}],
['length', value => Array.isArray(value) || typeof value === 'string' ? value.length : 0],
['join', (value, separator = ',') => Array.isArray(value) ? value.join(separator) : String(value)]
]);
}
// 主渲染方法
render(template, data = {}) {
let result = template;
// 1. 处理条件语句 (if/else)
result = this.processIfElseBlocks(result, data);
result = this.processIfBlocks(result, data);
result = this.processUnlessBlocks(result, data);
// 2. 处理循环语句 (each)
result = this.processEachBlocks(result, data);
// 3. 处理插值表达式
result = this.processInterpolation(result, data);
return result;
}
// 处理 if/else 条件块
processIfElseBlocks(template, data) {
return template.replace(this.patterns.ifElseBlock, (match, condition, ifContent, elseContent) => {
try {
const isTrue = this.evaluateCondition(condition.trim(), data);
return isTrue ? this.render(ifContent, data) : this.render(elseContent, data);
} catch (error) {
console.warn(`条件语句错误: ${condition}`, error);
return '';
}
});
}
// 处理 if 条件块
processIfBlocks(template, data) {
return template.replace(this.patterns.ifBlock, (match, condition, content) => {
try {
const isTrue = this.evaluateCondition(condition.trim(), data);
return isTrue ? this.render(content, data) : '';
} catch (error) {
console.warn(`条件语句错误: ${condition}`, error);
return '';
}
});
}
// 处理 unless 条件块
processUnlessBlocks(template, data) {
return template.replace(this.patterns.unlessBlock, (match, condition, content) => {
try {
const isTrue = this.evaluateCondition(condition.trim(), data);
return !isTrue ? this.render(content, data) : '';
} catch (error) {
console.warn(`Unless语句错误: ${condition}`, error);
return '';
}
});
}
// 处理 each 循环块
processEachBlocks(template, data) {
return template.replace(this.patterns.eachBlock, (match, expression, content) => {
try {
const [itemName, arrayExpr] = this.parseEachExpression(expression.trim());
const array = this.evaluateExpression(arrayExpr, data);
if (!Array.isArray(array)) {
return '';
}
return array.map((item, index) => {
// 为每个循环项创建新的数据作用域
const itemData = {
...data,
[itemName]: item,
[`${itemName}Index`]: index,
[`${itemName}First`]: index === 0,
[`${itemName}Last`]: index === array.length - 1
};
return this.render(content, itemData);
}).join('');
} catch (error) {
console.warn(`Each循环错误: ${expression}`, error);
return '';
}
});
}
// 解析 each 表达式:item in items
parseEachExpression(expression) {
const match = expression.match(/^(\w+)\s+in\s+(.+)$/);
if (!match) {
throw new Error(`无效的each表达式: ${expression}`);
}
return [match[1], match[2]];
}
// 处理插值表达式
processInterpolation(template, data) {
return template.replace(this.patterns.interpolation, (match, expression) => {
try {
return this.processExpression(expression.trim(), data);
} catch (error) {
console.warn(`插值表达式错误: ${expression}`, error);
return match;
}
});
}
// 处理表达式(包括过滤器)
processExpression(expression, data) {
if (expression.includes('|')) {
const [expr, ...filterChain] = expression.split('|').map(s => s.trim());
let result = this.evaluateExpression(expr, data);
for (const filterExpr of filterChain) {
result = this.applyFilter(result, filterExpr);
}
return result;
} else {
return this.evaluateExpression(expression, data);
}
}
// 应用过滤器
applyFilter(value, filterExpression) {
const filterMatch = filterExpression.match(/^([^(]+)(?:\(([^)]*)\))?$/);
if (!filterMatch) {
throw new Error(`无效的过滤器表达式: ${filterExpression}`);
}
const filterName = filterMatch[1].trim();
const argsString = filterMatch[2];
if (!this.filters.has(filterName)) {
throw new Error(`未知的过滤器: ${filterName}`);
}
const filter = this.filters.get(filterName);
if (argsString) {
const args = this.parseArguments(argsString);
return filter(value, ...args);
} else {
return filter(value);
}
}
// 解析参数
parseArguments(argsString) {
const args = [];
const argPattern = /(?:[^,"]|"[^"]*")+/g;
const matches = argsString.match(argPattern) || [];
for (const match of matches) {
const arg = match.trim();
if (arg.startsWith('"') && arg.endsWith('"')) {
args.push(arg.slice(1, -1));
} else if (!isNaN(arg)) {
args.push(Number(arg));
} else if (arg === 'true' || arg === 'false') {
args.push(arg === 'true');
} else {
args.push(arg);
}
}
return args;
}
// 条件求值
evaluateCondition(condition, data) {
// 处理比较操作符
const comparisonOperators = ['===', '!==', '==', '!=', '>=', '<=', '>', '<'];
for (const op of comparisonOperators) {
if (condition.includes(op)) {
const [left, right] = condition.split(op).map(s => s.trim());
const leftValue = this.evaluateExpression(left, data);
const rightValue = this.evaluateExpression(right, data);
switch (op) {
case '===': return leftValue === rightValue;
case '!==': return leftValue !== rightValue;
case '==': return leftValue == rightValue;
case '!=': return leftValue != rightValue;
case '>=': return leftValue >= rightValue;
case '<=': return leftValue <= rightValue;
case '>': return leftValue > rightValue;
case '<': return leftValue < rightValue;
}
}
}
// 简单的真值检查
const value = this.evaluateExpression(condition, data);
return Boolean(value);
}
// 表达式求值
evaluateExpression(expression, data) {
try {
// 处理字符串字面量
if ((expression.startsWith('"') && expression.endsWith('"')) ||
(expression.startsWith("'") && expression.endsWith("'"))) {
return expression.slice(1, -1);
}
// 处理数字字面量
if (!isNaN(expression) && !isNaN(parseFloat(expression))) {
return parseFloat(expression);
}
// 处理布尔字面量
if (expression === 'true') return true;
if (expression === 'false') return false;
if (expression === 'null') return null;
if (expression === 'undefined') return undefined;
// 处理属性访问
return this.safePropertyAccess(expression, data);
} catch (error) {
return '';
}
}
// 安全的属性访问
safePropertyAccess(expression, data) {
const properties = expression.split('.');
let result = data;
for (const prop of properties) {
if (result && typeof result === 'object') {
const arrayMatch = prop.match(/^([^[]+)\[(\d+)\]$/);
if (arrayMatch) {
const [, arrayName, index] = arrayMatch;
result = result[arrayName];
if (Array.isArray(result)) {
result = result[parseInt(index)];
} else {
return '';
}
} else {
result = result[prop];
}
} else {
return '';
}
}
return result != null ? String(result) : '';
}
// 添加自定义过滤器
addFilter(name, filterFunction) {
this.filters.set(name, filterFunction);
return this;
}
// 完整功能演示
static demo() {
console.log('=== 完整功能模板引擎演示 ===');
const engine = new FullFeaturedTemplateEngine();
// 测试数据
const data = {
title: 'My Blog',
user: {
name: 'Alice',
isAdmin: true,
posts: [
{ title: 'First Post', content: 'Hello World!', published: true },
{ title: 'Draft Post', content: 'Work in progress...', published: false },
{ title: 'Latest Post', content: 'This is the latest content.', published: true }
]
},
items: ['apple', 'banana', 'orange'],
showFooter: false
};
// 复杂模板示例
const template = `
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Welcome to {{ title }}</h1>
{{#if user.isAdmin}}
<p>Admin Panel: <a href="/admin">Manage Site</a></p>
{{/if}}
{{#unless showFooter}}
<p>Footer is hidden</p>
{{/unless}}
<h2>Published Posts:</h2>
{{#each post in user.posts}}
{{#if post.published}}
<article>
<h3>{{ post.title }}</h3>
<p>{{ post.content | truncate(20) }}</p>
<small>Post #{{ postIndex | default("unknown") }}</small>
{{#if postFirst}}
<span class="badge">First Post</span>
{{/if}}
{{#if postLast}}
<span class="badge">Latest Post</span>
{{/if}}
</article>
{{/if}}
{{/each}}
<h2>Available Items:</h2>
<ul>
{{#each item in items}}
<li>{{ item | capitalize }} ({{ itemIndex }})</li>
{{/each}}
</ul>
{{#if showFooter}}
<footer>
<p>© 2024 {{ title }}</p>
</footer>
{{else}}
<p>No footer to show.</p>
{{/if}}
</body>
</html>`;
const result = engine.render(template, data);
console.log('渲染结果:');
console.log(result);
}
}
FullFeaturedTemplateEngine.demo();
4. 性能优化和缓存版本:
// 高性能模板引擎(支持编译和缓存)
class OptimizedTemplateEngine extends FullFeaturedTemplateEngine {
constructor() {
super();
this.compiledCache = new Map();
this.compileMode = true;
}
// 编译模板为函数
compile(template) {
if (this.compiledCache.has(template)) {
return this.compiledCache.get(template);
}
const compiled = this.compileTemplate(template);
this.compiledCache.set(template, compiled);
return compiled;
}
// 模板编译器
compileTemplate(template) {
// 将模板转换为可执行的JavaScript代码
let code = 'let __output = "";\n';
// 预处理模板,提取所有的模板语法
const processed = this.preprocessTemplate(template);
code += processed;
code += 'return __output;';
// 创建编译后的函数
try {
return new Function('data', 'filters', 'engine', code);
} catch (error) {
console.error('模板编译失败:', error);
return () => '';
}
}
// 预处理模板
preprocessTemplate(template) {
let code = '';
let lastIndex = 0;
// 简化版:将插值表达式转换为代码
template.replace(this.patterns.interpolation, (match, expression, offset) => {
// 添加前面的静态文本
if (offset > lastIndex) {
const staticText = template.slice(lastIndex, offset);
code += `__output += ${JSON.stringify(staticText)};\n`;
}
// 添加动态表达式
code += `try {
const value = engine.processExpression(${JSON.stringify(expression.trim())}, data);
__output += String(value != null ? value : '');
} catch (error) {
__output += ${JSON.stringify(match)};
}\n`;
lastIndex = offset + match.length;
return match;
});
// 添加剩余的静态文本
if (lastIndex < template.length) {
const staticText = template.slice(lastIndex);
code += `__output += ${JSON.stringify(staticText)};\n`;
}
return code;
}
// 优化的渲染方法
render(template, data = {}) {
if (this.compileMode) {
const compiled = this.compile(template);
return compiled(data, this.filters, this);
} else {
return super.render(template, data);
}
}
// 性能测试
static performanceTest() {
console.log('=== 性能测试 ===');
const interpretedEngine = new FullFeaturedTemplateEngine();
const optimizedEngine = new OptimizedTemplateEngine();
const template = `
<h1>{{ title }}</h1>
{{#each item in items}}
<p>{{ item | upper }}</p>
{{/each}}
`;
const data = {
title: 'Performance Test',
items: Array.from({ length: 100 }, (_, i) => `Item ${i}`)
};
const iterations = 1000;
// 测试解释型引擎
console.time('解释型引擎');
for (let i = 0; i < iterations; i++) {
interpretedEngine.render(template, data);
}
console.timeEnd('解释型引擎');
// 测试编译型引擎(包含编译时间)
console.time('编译型引擎(含编译)');
for (let i = 0; i < iterations; i++) {
optimizedEngine.render(template, data);
}
console.timeEnd('编译型引擎(含编译)');
// 测试编译型引擎(预编译)
const compiled = optimizedEngine.compile(template);
console.time('编译型引擎(预编译)');
for (let i = 0; i < iterations; i++) {
compiled(data, optimizedEngine.filters, optimizedEngine);
}
console.timeEnd('编译型引擎(预编译)');
}
}
// OptimizedTemplateEngine.performanceTest();
5. 实际使用示例和最佳实践:
// 实用的模板引擎使用示例
class TemplateEngineUsage {
static blogExample() {
console.log('=== 博客系统示例 ===');
const engine = new FullFeaturedTemplateEngine();
// 添加自定义过滤器
engine.addFilter('markdown', (text) => {
// 简单的 markdown 处理
return text
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.*?)\*/g, '<em>$1</em>')
.replace(/\n/g, '<br>');
});
engine.addFilter('timeAgo', (date) => {
const now = new Date();
const diff = now - new Date(date);
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days}天前`;
if (hours > 0) return `${hours}小时前`;
if (minutes > 0) return `${minutes}分钟前`;
return '刚刚';
});
const blogData = {
site: {
title: 'Tech Blog',
description: 'A blog about web development'
},
user: {
name: 'John Doe',
avatar: '/avatars/john.jpg',
isLoggedIn: true
},
posts: [
{
id: 1,
title: 'Getting Started with ES6',
content: '**ES6** brings many *exciting* features to JavaScript...',
author: 'John Doe',
publishedAt: new Date(Date.now() - 86400000), // 1天前
tags: ['javascript', 'es6', 'tutorial']
},
{
id: 2,
title: 'Understanding Template Engines',
content: 'Template engines help us separate *logic* from **presentation**...',
author: 'Jane Smith',
publishedAt: new Date(Date.now() - 3600000), // 1小时前
tags: ['templates', 'web-dev']
}
]
};
const blogTemplate = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{ site.title }} - {{ site.description }}</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.header { border-bottom: 1px solid #eee; padding-bottom: 20px; margin-bottom: 30px; }
.post { margin-bottom: 40px; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
.tags { margin-top: 10px; }
.tag { background: #007cba; color: white; padding: 3px 8px; border-radius: 12px; font-size: 12px; margin-right: 5px; }
.meta { color: #666; font-size: 14px; margin-top: 10px; }
.user-info { background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0; }
</style>
</head>
<body>
<header class="header">
<h1>{{ site.title }}</h1>
<p>{{ site.description }}</p>
{{#if user.isLoggedIn}}
<div class="user-info">
<p>欢迎回来,<strong>{{ user.name }}</strong>!</p>
<a href="/logout">退出登录</a>
</div>
{{else}}
<div class="user-info">
<p><a href="/login">登录</a> 或 <a href="/register">注册</a></p>
</div>
{{/if}}
</header>
<main>
<h2>最新文章 (共{{ posts | length }}篇)</h2>
{{#each post in posts}}
<article class="post">
<h3>{{ post.title }}</h3>
<div class="content">{{ post.content | markdown }}</div>
<div class="tags">
标签:
{{#each tag in post.tags}}
<span class="tag">{{ tag }}</span>
{{/each}}
</div>
<div class="meta">
作者:{{ post.author }} |
发布时间:{{ post.publishedAt | timeAgo }} |
文章ID:{{ post.id }}
{{#if postFirst}}
| <strong>最新文章</strong>
{{/if}}
</div>
</article>
{{/each}}
{{#unless posts}}
<p>暂无文章,<a href="/write">立即写作</a></p>
{{/unless}}
</main>
<footer>
<p>© 2024 {{ site.title }}. All rights reserved.</p>
</footer>
</body>
</html>`;
const result = engine.render(blogTemplate, blogData);
console.log(result);
}
static emailTemplateExample() {
console.log('\n=== 邮件模板示例 ===');
const engine = new FullFeaturedTemplateEngine();
engine.addFilter('currency', (amount) =>
`¥${Number(amount).toFixed(2)}`);
const emailData = {
user: { name: 'Alice Wang' },
order: {
id: 'ORD-2024-001',
date: new Date().toLocaleDateString(),
items: [
{ name: 'MacBook Pro', price: 12999, quantity: 1 },
{ name: 'Magic Mouse', price: 649, quantity: 1 },
{ name: 'USB-C Cable', price: 149, quantity: 2 }
],
shipping: 29,
total: 13826
},
company: {
name: 'TechStore',
address: '北京市朝阳区科技园123号',
phone: '400-123-4567'
}
};
const emailTemplate = `
亲爱的 {{ user.name }},
感谢您在 {{ company.name }} 的购买!
订单详情:
订单号:{{ order.id }}
下单时间:{{ order.date }}
商品清单:
{{#each item in order.items}}
- {{ item.name }} × {{ item.quantity }} = {{ item.price | currency }}
{{/each}}
运费:{{ order.shipping | currency }}
总计:{{ order.total | currency }}
{{#if order.total > 10000}}
🎉 恭喜您!订单金额超过10,000元,享受免费配送服务!
{{/if}}
如有任何问题,请联系我们:
电话:{{ company.phone }}
地址:{{ company.address }}
祝您购物愉快!
{{ company.name }} 团队
`;
const result = engine.render(emailTemplate, emailData);
console.log(result);
}
}
// 运行示例
TemplateEngineUsage.blogExample();
TemplateEngineUsage.emailTemplateExample();
模板引擎核心特性总结:
基础功能:
{{ expression }}{{ user.name }}、{{ items[0] }}高级功能:
{{ name | upper }}{{#if}} {{else}} {{/if}}{{#each item in items}} {{/each}}itemIndex、itemFirst、itemLast性能优化:
实用特性:
这个模板引擎实现展示了如何使用ES6的新特性(如类、箭头函数、模板字符串、Map等)来构建一个功能完整且高性能的模板系统。
How does ES6 handle circular references? What are the solutions?
How does ES6 handle circular references? What are the solutions?
考察点:WeakMap、序列化算法。
答案:
循环引用是 JavaScript 中一个重要且复杂的问题,ES6 提供了多种工具和模式来处理这种情况。循环引用会影响对象的序列化、深拷贝、垃圾回收等多个方面。
1. 循环引用的基本概念和问题:
// 循环引用示例和问题演示
class CircularReferenceDemo {
// 创建循环引用的对象
static createCircularObject() {
console.log('=== 循环引用基本示例 ===');
const obj1 = { name: 'Object 1' };
const obj2 = { name: 'Object 2' };
// 创建循环引用
obj1.ref = obj2;
obj2.ref = obj1;
console.log('obj1.ref.name:', obj1.ref.name); // 'Object 2'
console.log('obj2.ref.name:', obj2.ref.name); // 'Object 1'
console.log('obj1.ref.ref === obj1:', obj1.ref.ref === obj1); // true
return { obj1, obj2 };
}
// 演示循环引用导致的问题
static demonstrateProblems() {
console.log('\n=== 循环引用导致的问题 ===');
const { obj1 } = this.createCircularObject();
// 1. JSON.stringify 问题
try {
JSON.stringify(obj1);
} catch (error) {
console.log('JSON.stringify 错误:', error.message);
}
// 2. 使用 WeakSet 检测循环引用
function safeTraverse(obj, visited = new WeakSet()) {
console.log('访问对象:', obj.name);
if (visited.has(obj)) {
console.log('检测到循环引用!');
return;
}
visited.add(obj);
if (obj.ref) {
safeTraverse(obj.ref, visited);
}
}
console.log('\n使用 WeakSet 检测循环引用:');
safeTraverse(obj1);
console.log();
}
}
CircularReferenceDemo.createCircularObject();
CircularReferenceDemo.demonstrateProblems();
2. 使用 WeakMap 实现深拷贝:
// 使用 WeakMap 解决循环引用的深拷贝
class DeepCloneWithWeakMap {
static deepClone(obj, cache = new WeakMap()) {
// 处理非对象类型
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 检查是否已经克隆过(处理循环引用)
if (cache.has(obj)) {
return cache.get(obj);
}
// 处理特殊对象类型
if (obj instanceof Date) {
return new Date(obj.getTime());
}
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 创建新对象
const cloned = Array.isArray(obj) ? [] : {};
// 提前设置缓存,防止循环引用
cache.set(obj, cloned);
// 递归克隆属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = this.deepClone(obj[key], cache);
}
}
return cloned;
}
// 演示
static demo() {
console.log('=== WeakMap 深拷贝演示 ===');
// 创建循环引用对象
const parent = { name: 'Parent', children: [] };
const child1 = { name: 'Child1', parent: parent };
const child2 = { name: 'Child2', parent: parent };
parent.children.push(child1, child2);
// 深拷贝
const clonedParent = this.deepClone(parent);
console.log('原对象:', parent.children[0].parent === parent);
console.log('克隆对象:', clonedParent.children[0].parent === clonedParent);
console.log('不是同一对象:', clonedParent !== parent);
console.log();
}
}
DeepCloneWithWeakMap.demo();
3. 循环引用安全序列化:
// 循环引用安全序列化
class SafeSerializer {
static stringify(obj, visited = new WeakSet()) {
if (obj === null) return 'null';
if (typeof obj !== 'object') return JSON.stringify(obj);
// 检查循环引用
if (visited.has(obj)) {
return '"[Circular Reference]"';
}
visited.add(obj);
try {
if (Array.isArray(obj)) {
const items = obj.map(item => this.stringify(item, visited));
return '[' + items.join(',') + ']';
} else {
const pairs = [];
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = this.stringify(obj[key], visited);
pairs.push(`"${key}":${value}`);
}
}
return '{' + pairs.join(',') + '}';
}
} finally {
visited.delete(obj);
}
}
// 演示
static demo() {
console.log('=== 安全序列化演示 ===');
const obj1 = { name: 'A' };
const obj2 = { name: 'B' };
obj1.ref = obj2;
obj2.ref = obj1;
const serialized = this.stringify(obj1);
console.log('序列化结果:', serialized);
console.log();
}
}
SafeSerializer.demo();
How to implement class mixins? What should be noted?
How to implement class mixins? What should be noted?
考察点:对象合成与继承。
答案:
Mixin 是一种允许类从多个源继承行为的设计模式。ES6 虽然原生支持单继承,但我们可以通过多种技术实现 Mixin 模式来达到多继承的效果。
1. 基础 Mixin 实现:
// 基础 Mixin 模式实现
class BasicMixinExample {
// 简单的 mixin 函数
static mixin(BaseClass, ...mixins) {
// 创建一个新类继承自 BaseClass
class Mixed extends BaseClass {}
// 将所有 mixin 的方法复制到新类的原型上
mixins.forEach(mixin => {
Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
if (name !== 'constructor') {
Mixed.prototype[name] = mixin.prototype[name];
}
});
});
return Mixed;
}
// 演示基础用法
static demo() {
console.log('=== 基础 Mixin 演示 ===');
// 定义基类
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
// 定义 mixin 类
class Flyable {
fly() {
console.log(`${this.name} is flying`);
}
land() {
console.log(`${this.name} has landed`);
}
}
class Swimmable {
swim() {
console.log(`${this.name} is swimming`);
}
dive() {
console.log(`${this.name} is diving`);
}
}
// 创建混合类
const FlyingSwimmingAnimal = this.mixin(Animal, Flyable, Swimmable);
// 测试
const duck = new FlyingSwimmingAnimal('Duck');
duck.speak(); // 继承自 Animal
duck.fly(); // 来自 Flyable mixin
duck.swim(); // 来自 Swimmable mixin
duck.land();
duck.dive();
console.log('duck instanceof Animal:', duck instanceof Animal);
console.log('duck instanceof FlyingSwimmingAnimal:',
duck instanceof FlyingSwimmingAnimal);
console.log();
}
}
BasicMixinExample.demo();
2. 工厂函数 Mixin 模式:
// 工厂函数 Mixin 模式
class FactoryMixinPattern {
// 可配置的 mixin 工厂
static createMixin(mixinDefinition) {
return (SuperClass) => {
class MixedClass extends SuperClass {
constructor(...args) {
super(...args);
// 调用 mixin 的初始化方法
if (mixinDefinition.initialize) {
mixinDefinition.initialize.call(this);
}
}
}
// 复制 mixin 的方法
Object.keys(mixinDefinition).forEach(key => {
if (key !== 'initialize' && typeof mixinDefinition[key] === 'function') {
MixedClass.prototype[key] = mixinDefinition[key];
}
});
// 复制静态方法
Object.getOwnPropertyNames(mixinDefinition).forEach(key => {
if (typeof mixinDefinition[key] === 'function' && key !== 'initialize') {
MixedClass[key] = mixinDefinition[key];
}
});
return MixedClass;
};
}
// 链式 mixin 组合
static compose(...mixins) {
return (BaseClass) => {
return mixins.reduce((currentClass, mixin) => {
return mixin(currentClass);
}, BaseClass);
};
}
// 演示工厂模式
static demo() {
console.log('=== 工厂函数 Mixin 演示 ===');
// 定义基类
class Vehicle {
constructor(brand) {
this.brand = brand;
this.speed = 0;
}
start() {
console.log(`${this.brand} vehicle started`);
}
}
// 定义 mixin
const Motorized = this.createMixin({
initialize() {
this.engine = 'V8';
this.fuel = 100;
},
accelerate() {
if (this.fuel > 0) {
this.speed += 10;
this.fuel -= 1;
console.log(`Accelerating to ${this.speed} km/h, fuel: ${this.fuel}%`);
} else {
console.log('No fuel left!');
}
},
refuel() {
this.fuel = 100;
console.log('Refueled to 100%');
}
});
const Trackable = this.createMixin({
initialize() {
this.position = { x: 0, y: 0 };
this.history = [];
},
moveTo(x, y) {
this.history.push({ ...this.position });
this.position = { x, y };
console.log(`Moved to position (${x}, ${y})`);
},
getHistory() {
return this.history;
}
});
const Communicable = this.createMixin({
initialize() {
this.messages = [];
},
sendMessage(message) {
this.messages.push({
message,
timestamp: new Date(),
type: 'sent'
});
console.log(`Message sent: ${message}`);
},
receiveMessage(message) {
this.messages.push({
message,
timestamp: new Date(),
type: 'received'
});
console.log(`Message received: ${message}`);
}
});
// 使用组合创建最终类
const SmartCar = this.compose(
Motorized,
Trackable,
Communicable
)(Vehicle);
// 测试
const tesla = new SmartCar('Tesla');
tesla.start();
tesla.accelerate();
tesla.moveTo(100, 200);
tesla.sendMessage('Arriving at destination');
tesla.refuel();
console.log('Position:', tesla.position);
console.log('Engine:', tesla.engine);
console.log('Messages:', tesla.messages.length);
console.log();
}
}
FactoryMixinPattern.demo();
3. Symbol-based Mixin(避免命名冲突):
// 使用 Symbol 避免命名冲突的 Mixin
class SymbolBasedMixin {
// 创建带命名空间的 mixin
static createNamespacedMixin(namespace, methods) {
const symbols = {};
const mixin = {};
// 为每个方法创建 Symbol
Object.keys(methods).forEach(methodName => {
const symbol = Symbol(`${namespace}.${methodName}`);
symbols[methodName] = symbol;
mixin[symbol] = methods[methodName];
});
// 返回 mixin 工厂
return {
symbols,
apply: (targetClass) => {
Object.keys(mixin).forEach(symbol => {
targetClass.prototype[symbol] = mixin[symbol];
});
return targetClass;
}
};
}
// 高级 Symbol mixin 实现
static createAdvancedMixin(mixinConfig) {
const mixinSymbols = new Map();
return (BaseClass) => {
class MixedClass extends BaseClass {
constructor(...args) {
super(...args);
// 初始化所有 mixin 的状态
Object.keys(mixinConfig).forEach(mixinName => {
const config = mixinConfig[mixinName];
if (config.initialize) {
config.initialize.call(this);
}
// 为每个 mixin 创建命名空间
const namespaceSymbol = Symbol(mixinName);
mixinSymbols.set(mixinName, namespaceSymbol);
this[namespaceSymbol] = {};
});
}
// 调用 mixin 方法的辅助函数
callMixin(mixinName, methodName, ...args) {
const config = mixinConfig[mixinName];
if (config && config[methodName]) {
return config[methodName].apply(this, args);
}
throw new Error(`Method ${methodName} not found in mixin ${mixinName}`);
}
// 获取 mixin 状态
getMixinState(mixinName) {
const symbol = mixinSymbols.get(mixinName);
return symbol ? this[symbol] : undefined;
}
}
// 注册所有 mixin 方法
Object.keys(mixinConfig).forEach(mixinName => {
const config = mixinConfig[mixinName];
Object.keys(config).forEach(methodName => {
if (methodName !== 'initialize' && typeof config[methodName] === 'function') {
const methodSymbol = Symbol(`${mixinName}.${methodName}`);
MixedClass.prototype[methodSymbol] = config[methodName];
// 创建便捷访问方法
const accessorName = `${mixinName}_${methodName}`;
MixedClass.prototype[accessorName] = function(...args) {
return this[methodSymbol](...args);
};
}
});
});
return MixedClass;
};
}
// 演示 Symbol-based Mixin
static demo() {
console.log('=== Symbol-based Mixin 演示 ===');
// 定义基类
class DataProcessor {
constructor(data) {
this.data = data;
}
}
// 定义 mixin 配置
const mixinConfig = {
validator: {
initialize() {
this.validationRules = [];
},
addRule(rule) {
this.validationRules.push(rule);
},
validate() {
return this.validationRules.every(rule => rule(this.data));
},
getErrors() {
return this.validationRules
.filter(rule => !rule(this.data))
.map(rule => rule.name || 'Validation error');
}
},
formatter: {
initialize() {
this.formatters = new Map();
},
addFormatter(name, formatter) {
this.formatters.set(name, formatter);
},
format(formatterName) {
const formatter = this.formatters.get(formatterName);
if (formatter) {
return formatter(this.data);
}
throw new Error(`Formatter ${formatterName} not found`);
}
},
serializer: {
initialize() {
this.serializers = new Map([
['json', JSON.stringify],
['string', String],
['csv', (data) => Array.isArray(data) ? data.join(',') : String(data)]
]);
},
serialize(format = 'json') {
const serializer = this.serializers.get(format);
if (serializer) {
return serializer(this.data);
}
throw new Error(`Serializer ${format} not found`);
},
addSerializer(name, serializer) {
this.serializers.set(name, serializer);
}
}
};
// 创建混合类
const EnhancedProcessor = this.createAdvancedMixin(mixinConfig)(DataProcessor);
// 测试
const processor = new EnhancedProcessor([1, 2, 3, 4, 5]);
// 使用 validator mixin
processor.validator_addRule(data => data.length > 0);
processor.validator_addRule(data => data.every(x => typeof x === 'number'));
console.log('数据验证通过:', processor.validator_validate());
// 使用 formatter mixin
processor.formatter_addFormatter('doubled', data => data.map(x => x * 2));
processor.formatter_addFormatter('sum', data => data.reduce((a, b) => a + b, 0));
console.log('格式化结果 (doubled):', processor.formatter_format('doubled'));
console.log('格式化结果 (sum):', processor.formatter_format('sum'));
// 使用 serializer mixin
console.log('JSON 序列化:', processor.serializer_serialize('json'));
console.log('CSV 序列化:', processor.serializer_serialize('csv'));
// 使用通用调用方法
console.log('通用调用:', processor.callMixin('serializer', 'serialize', 'string'));
console.log();
}
}
SymbolBasedMixin.demo();
4. 处理方法冲突的高级 Mixin:
// 处理方法冲突的高级 Mixin 实现
class ConflictResolutionMixin {
static createSmartMixin(...mixins) {
return (BaseClass) => {
class SmartMixed extends BaseClass {
constructor(...args) {
super(...args);
// 存储所有 mixin 的方法
this.__mixinMethods__ = new Map();
this.__mixinConflicts__ = new Map();
// 分析方法冲突
this.__analyzeMixins__(mixins);
}
__analyzeMixins__(mixins) {
const methodMap = new Map(); // methodName -> [mixin1, mixin2, ...]
mixins.forEach((mixin, index) => {
Object.getOwnPropertyNames(mixin.prototype).forEach(methodName => {
if (methodName !== 'constructor' &&
typeof mixin.prototype[methodName] === 'function') {
if (!methodMap.has(methodName)) {
methodMap.set(methodName, []);
}
methodMap.get(methodName).push({ mixin, index });
}
});
});
// 处理每个方法
methodMap.forEach((sources, methodName) => {
if (sources.length === 1) {
// 无冲突,直接使用
const { mixin } = sources[0];
this[methodName] = mixin.prototype[methodName];
this.__mixinMethods__.set(methodName, [mixin.name || 'Anonymous']);
} else {
// 有冲突,创建冲突解析方案
this.__handleConflict__(methodName, sources);
}
});
}
__handleConflict__(methodName, sources) {
// 记录冲突
const conflictInfo = sources.map(s => s.mixin.name || 'Anonymous');
this.__mixinConflicts__.set(methodName, conflictInfo);
// 创建组合方法(调用所有同名方法)
this[methodName] = (...args) => {
const results = sources.map(({ mixin }) => {
return mixin.prototype[methodName].apply(this, args);
});
console.log(`方法冲突 ${methodName}: 执行了 ${sources.length} 个实现`);
return results;
};
// 创建特定 mixin 的方法访问器
sources.forEach(({ mixin, index }) => {
const mixinName = mixin.name || `Mixin${index}`;
const specificMethodName = `${methodName}_from_${mixinName}`;
this[specificMethodName] = (...args) => {
return mixin.prototype[methodName].apply(this, args);
};
});
}
// 获取方法来源信息
getMethodSources(methodName) {
return this.__mixinMethods__.get(methodName) ||
this.__mixinConflicts__.get(methodName) ||
['BaseClass'];
}
// 获取所有冲突信息
getConflicts() {
return Array.from(this.__mixinConflicts__.entries());
}
// 列出所有可用方法
listMethods() {
const methods = [];
// 基类方法
Object.getOwnPropertyNames(BaseClass.prototype).forEach(name => {
if (name !== 'constructor' && typeof this[name] === 'function') {
methods.push({ name, source: 'BaseClass', conflict: false });
}
});
// Mixin 方法
this.__mixinMethods__.forEach((sources, name) => {
methods.push({ name, source: sources, conflict: false });
});
// 冲突方法
this.__mixinConflicts__.forEach((sources, name) => {
methods.push({ name, source: sources, conflict: true });
});
return methods;
}
}
return SmartMixed;
};
}
// 演示冲突解析
static demo() {
console.log('=== 冲突解析 Mixin 演示 ===');
// 基类
class Shape {
constructor(name) {
this.name = name;
}
describe() {
return `This is a ${this.name}`;
}
}
// Mixin 1
class Colorable {
static get name() { return 'Colorable'; }
setColor(color) {
this.color = color;
console.log(`${this.name} color set to ${color}`);
}
describe() { // 冲突方法
return `A ${this.color || 'colorless'} ${this.name}`;
}
}
// Mixin 2
class Sizeable {
static get name() { return 'Sizeable'; }
setSize(width, height) {
this.width = width;
this.height = height;
console.log(`${this.name} size set to ${width}x${height}`);
}
describe() { // 冲突方法
return `A ${this.width || 'unknown'}x${this.height || 'unknown'} ${this.name}`;
}
getArea() {
return (this.width || 0) * (this.height || 0);
}
}
// Mixin 3
class Drawable {
static get name() { return 'Drawable'; }
draw() {
console.log(`Drawing ${this.describe()}`);
}
setColor(color) { // 与 Colorable.setColor 冲突
this.strokeColor = color;
console.log(`${this.name} stroke color set to ${color}`);
}
}
// 创建智能混合类
const SmartShape = this.createSmartMixin(Colorable, Sizeable, Drawable)(Shape);
// 测试
const rectangle = new SmartShape('Rectangle');
console.log('=== 方法列表 ===');
rectangle.listMethods().forEach(method => {
console.log(`${method.name}: ${method.source} ${method.conflict ? '(冲突)' : ''}`);
});
console.log('\n=== 冲突信息 ===');
rectangle.getConflicts().forEach(([method, sources]) => {
console.log(`${method}: ${sources.join(', ')}`);
});
console.log('\n=== 方法调用测试 ===');
// 调用无冲突方法
rectangle.setSize(10, 20);
console.log('Area:', rectangle.getArea());
// 调用冲突方法(会执行所有实现)
console.log('\n调用冲突方法 setColor:');
rectangle.setColor('red');
console.log('\n调用冲突方法 describe:');
const descriptions = rectangle.describe();
console.log('所有描述:', descriptions);
// 调用特定 mixin 的方法
console.log('\n调用特定实现:');
console.log('Colorable describe:', rectangle.describe_from_Colorable());
console.log('Sizeable describe:', rectangle.describe_from_Sizeable());
rectangle.draw();
console.log();
}
}
ConflictResolutionMixin.demo();
5. Mixin 的最佳实践和注意事项:
// Mixin 最佳实践指南
class MixinBestPractices {
// 检查 Mixin 兼容性
static checkMixinCompatibility(BaseClass, ...mixins) {
const issues = [];
const allMethods = new Set();
// 检查基类方法
Object.getOwnPropertyNames(BaseClass.prototype).forEach(name => {
if (name !== 'constructor') {
allMethods.add(name);
}
});
// 检查 Mixin 之间的冲突
mixins.forEach((mixin, index) => {
const mixinName = mixin.name || `Mixin${index}`;
Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
if (name !== 'constructor') {
if (allMethods.has(name)) {
issues.push({
type: 'method_conflict',
method: name,
mixin: mixinName,
message: `Method ${name} conflicts with existing implementation`
});
} else {
allMethods.add(name);
}
}
});
// 检查必要的依赖方法
if (mixin.requiredMethods) {
mixin.requiredMethods.forEach(requiredMethod => {
if (!allMethods.has(requiredMethod)) {
issues.push({
type: 'missing_dependency',
method: requiredMethod,
mixin: mixinName,
message: `Mixin ${mixinName} requires method ${requiredMethod}`
});
}
});
}
});
return {
compatible: issues.length === 0,
issues
};
}
// 创建文档化的 Mixin
static createDocumentedMixin(config) {
const mixin = (BaseClass) => {
class DocumentedMixin extends BaseClass {
constructor(...args) {
super(...args);
if (config.initialize) {
config.initialize.call(this);
}
// 存储 mixin 元信息
if (!this.__mixinInfo__) {
this.__mixinInfo__ = [];
}
this.__mixinInfo__.push({
name: config.name,
version: config.version,
description: config.description,
methods: Object.keys(config.methods || {}),
dependencies: config.dependencies || []
});
}
// 获取 mixin 信息
getMixinInfo() {
return this.__mixinInfo__ || [];
}
}
// 添加方法
if (config.methods) {
Object.keys(config.methods).forEach(methodName => {
DocumentedMixin.prototype[methodName] = config.methods[methodName];
});
}
// 添加静态方法
if (config.staticMethods) {
Object.keys(config.staticMethods).forEach(methodName => {
DocumentedMixin[methodName] = config.staticMethods[methodName];
});
}
// 存储依赖信息
if (config.dependencies) {
DocumentedMixin.requiredMethods = config.dependencies;
}
return DocumentedMixin;
};
// 添加 mixin 元信息
mixin.mixinName = config.name;
mixin.mixinVersion = config.version;
mixin.mixinDescription = config.description;
return mixin;
}
// 演示最佳实践
static demo() {
console.log('=== Mixin 最佳实践演示 ===');
// 定义基类
class EventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, ...args) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(callback => {
callback(...args);
});
}
}
}
// 创建文档化的 Mixin
const LoggableMixin = this.createDocumentedMixin({
name: 'Loggable',
version: '1.0.0',
description: '为类添加日志记录功能',
dependencies: ['emit'], // 依赖 emit 方法
initialize() {
this.logs = [];
this.logLevel = 'info';
},
methods: {
log(level, message) {
const logEntry = {
level,
message,
timestamp: new Date()
};
this.logs.push(logEntry);
this.emit('log', logEntry); // 使用依赖的方法
console.log(`[${level.toUpperCase()}] ${message}`);
},
info(message) {
this.log('info', message);
},
warn(message) {
this.log('warn', message);
},
error(message) {
this.log('error', message);
},
getLogs(level = null) {
return level ?
this.logs.filter(log => log.level === level) :
this.logs;
}
}
});
const CacheableMixin = this.createDocumentedMixin({
name: 'Cacheable',
version: '1.1.0',
description: '为类添加缓存功能',
initialize() {
this.cache = new Map();
this.cacheStats = { hits: 0, misses: 0 };
},
methods: {
getCached(key) {
if (this.cache.has(key)) {
this.cacheStats.hits++;
return { found: true, value: this.cache.get(key) };
} else {
this.cacheStats.misses++;
return { found: false, value: null };
}
},
setCached(key, value, ttl = null) {
const entry = { value, timestamp: Date.now() };
if (ttl) {
entry.expires = Date.now() + ttl;
}
this.cache.set(key, entry);
},
clearCache() {
this.cache.clear();
this.cacheStats = { hits: 0, misses: 0 };
},
getCacheStats() {
return { ...this.cacheStats };
}
}
});
// 检查兼容性
const compatibility = this.checkMixinCompatibility(
EventEmitter,
LoggableMixin,
CacheableMixin
);
console.log('兼容性检查:', compatibility.compatible);
if (!compatibility.compatible) {
compatibility.issues.forEach(issue => {
console.log(`- ${issue.type}: ${issue.message}`);
});
}
// 创建混合类
const EnhancedEmitter = CacheableMixin(LoggableMixin(EventEmitter));
// 测试
const emitter = new EnhancedEmitter();
// 设置日志监听器
emitter.on('log', (logEntry) => {
console.log(`日志事件: ${logEntry.level} - ${logEntry.message}`);
});
// 测试功能
emitter.info('系统启动');
emitter.setCached('user:123', { name: 'Alice', age: 30 });
const cached = emitter.getCached('user:123');
if (cached.found) {
emitter.info(`从缓存获取用户: ${cached.value.value.name}`);
}
emitter.warn('内存使用率较高');
emitter.error('数据库连接失败');
// 显示统计信息
console.log('\n=== Mixin 信息 ===');
emitter.getMixinInfo().forEach(info => {
console.log(`${info.name} v${info.version}: ${info.description}`);
console.log(` 方法: ${info.methods.join(', ')}`);
if (info.dependencies.length > 0) {
console.log(` 依赖: ${info.dependencies.join(', ')}`);
}
});
console.log('\n=== 缓存统计 ===');
console.log(emitter.getCacheStats());
console.log('\n=== 日志记录 ===');
emitter.getLogs().forEach(log => {
console.log(`${log.timestamp.toISOString()} [${log.level}] ${log.message}`);
});
console.log();
}
}
MixinBestPractices.demo();
// Mixin 使用指南总结
console.log('=== Mixin 使用指南总结 ===');
console.log('1. 优先使用组合而非继承');
console.log('2. 避免方法名冲突,使用命名空间');
console.log('3. 明确声明 mixin 的依赖关系');
console.log('4. 提供清晰的文档和版本控制');
console.log('5. 实现冲突解析机制');
console.log('6. 使用 Symbol 来避免意外的属性覆盖');
console.log('7. 考虑性能影响,避免过度复杂的 mixin 链');
console.log('8. 提供运行时的 mixin 信息查询能力');
Mixin 模式核心要点:
实现方式:
最佳实践:
注意事项:
应用场景:
How does ES6 implement private methods and properties? What are the limitations?
How does ES6 implement private methods and properties? What are the limitations?
考察点:#语法、闭包、Symbol、WeakMap等多种实现方式。
答案:
ES6 本身并没有提供真正的私有成员语法,但可以通过多种技术手段来模拟私有属性和方法。ES2022 引入了真正的私有字段语法(#),让我们全面了解各种实现方式及其限制。
1. Symbol 方式实现私有成员:
// 使用 Symbol 模拟私有属性和方法
class SymbolPrivateExample {
constructor(name, balance) {
this[_name] = name;
this[_balance] = balance;
this[_transactions] = [];
}
// 公有方法
deposit(amount) {
if (this[_validateAmount](amount)) {
this[_balance] += amount;
this[_addTransaction]('deposit', amount);
return this[_balance];
}
throw new Error('Invalid amount');
}
withdraw(amount) {
if (this[_validateAmount](amount) && this[_balance] >= amount) {
this[_balance] -= amount;
this[_addTransaction]('withdraw', amount);
return this[_balance];
}
throw new Error('Invalid operation');
}
getBalance() {
return this[_balance];
}
getName() {
return this[_name];
}
// 私有方法
[_validateAmount](amount) {
return typeof amount === 'number' && amount > 0;
}
[_addTransaction](type, amount) {
this[_transactions].push({
type, amount, timestamp: new Date(), balance: this[_balance]
});
}
getTransactions() {
return [...this[_transactions]]; // 返回副本
}
}
// 私有 Symbol 定义
const _name = Symbol('name');
const _balance = Symbol('balance');
const _transactions = Symbol('transactions');
const _validateAmount = Symbol('validateAmount');
const _addTransaction = Symbol('addTransaction');
console.log('=== Symbol 私有成员演示 ===');
const account = new SymbolPrivateExample('Alice', 1000);
console.log('姓名:', account.getName());
console.log('余额:', account.getBalance());
account.deposit(500);
console.log('存款后:', account.getBalance());
console.log('交易记录:', account.getTransactions().length);
// Symbol 的局限性
console.log('对象键:', Object.keys(account)); // 不显示 Symbol 属性
console.log('Symbol 属性数量:', Object.getOwnPropertySymbols(account).length); // 但可以发现
console.log();
2. WeakMap 方式实现真正私有:
// 使用 WeakMap 实现真正的私有成员
const privateData = new WeakMap();
const privateMethods = new WeakMap();
class WeakMapPrivateExample {
constructor(username, email, role = 'user') {
// 私有数据
privateData.set(this, {
username,
email,
role,
loginAttempts: 0,
isLocked: false,
sessions: [],
permissions: this._getDefaultPermissions(role)
});
// 私有方法
privateMethods.set(this, {
validateEmail: (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
},
hashPassword: (password) => {
// 简化的哈希实现
return btoa(password + 'salt');
},
generateSessionId: () => {
return Math.random().toString(36).substr(2, 16);
},
checkPermission: (action) => {
const data = privateData.get(this);
return data.permissions.includes(action);
}
});
// 公有属性
this.id = Math.random().toString(36).substr(2, 9);
this.createdAt = new Date();
this.lastLogin = null;
}
// 辅助方法获取私有数据和方法
_getPrivateData() {
return privateData.get(this);
}
_getPrivateMethods() {
return privateMethods.get(this);
}
_getDefaultPermissions(role) {
const permissions = {
'user': ['read', 'comment'],
'editor': ['read', 'comment', 'write', 'edit'],
'admin': ['read', 'comment', 'write', 'edit', 'delete', 'manage']
};
return permissions[role] || permissions['user'];
}
// 公有方法
login(password) {
const data = this._getPrivateData();
const methods = this._getPrivateMethods();
if (data.isLocked) {
throw new Error('Account is locked');
}
// 简化的登录验证
const hashedPassword = methods.hashPassword(password);
if (hashedPassword) { // 简化验证
const sessionId = methods.generateSessionId();
data.sessions.push({
id: sessionId,
createdAt: new Date(),
isActive: true
});
this.lastLogin = new Date();
data.loginAttempts = 0;
return sessionId;
} else {
data.loginAttempts++;
if (data.loginAttempts >= 3) {
data.isLocked = true;
}
throw new Error('Invalid credentials');
}
}
logout(sessionId) {
const data = this._getPrivateData();
const session = data.sessions.find(s => s.id === sessionId);
if (session) {
session.isActive = false;
session.endedAt = new Date();
return true;
}
return false;
}
changeEmail(newEmail) {
const data = this._getPrivateData();
const methods = this._getPrivateMethods();
if (!methods.validateEmail(newEmail)) {
throw new Error('Invalid email format');
}
const oldEmail = data.email;
data.email = newEmail;
return { oldEmail, newEmail };
}
canPerform(action) {
const methods = this._getPrivateMethods();
return methods.checkPermission(action);
}
// 只读访问器
get username() {
return this._getPrivateData().username;
}
get email() {
const email = this._getPrivateData().email;
// 隐藏部分邮箱
const [user, domain] = email.split('@');
return user.slice(0, 2) + '*'.repeat(user.length - 2) + '@' + domain;
}
get role() {
return this._getPrivateData().role;
}
get isLocked() {
return this._getPrivateData().isLocked;
}
getActiveSessions() {
const data = this._getPrivateData();
return data.sessions.filter(s => s.isActive).length;
}
getPermissions() {
return [...this._getPrivateData().permissions]; // 返回副本
}
}
console.log('=== WeakMap 私有成员演示 ===');
const user = new WeakMapPrivateExample('john_doe', '[email protected]', 'editor');
console.log('用户信息:', user.username, user.email, user.role);
console.log('权限:', user.getPermissions());
console.log('可以写入:', user.canPerform('write'));
console.log('可以删除:', user.canPerform('delete'));
const sessionId = user.login('password123');
console.log('登录成功,会话ID:', sessionId);
console.log('活跃会话数:', user.getActiveSessions());
user.changeEmail('[email protected]');
console.log('邮箱已更改:', user.email);
console.log('尝试直接访问私有数据:', user.username); // 只能通过 getter
console.log();
3. 闭包方式实现私有成员:
// 使用闭包实现私有成员
function createSecureDataStore(initialData = {}) {
// 私有变量
let data = new Map(Object.entries(initialData));
let accessLog = [];
let isReadOnly = false;
let encryptionKey = Math.random().toString(36);
// 私有方法
function logAccess(operation, key, success) {
accessLog.push({
operation,
key,
success,
timestamp: new Date()
});
}
function encrypt(value) {
// 简化的加密
return btoa(JSON.stringify(value) + encryptionKey);
}
function decrypt(encryptedValue) {
try {
const decrypted = atob(encryptedValue);
return JSON.parse(decrypted.replace(encryptionKey, ''));
} catch {
return null;
}
}
function validateKey(key) {
return typeof key === 'string' && key.length > 0;
}
// 返回公有接口
return {
// 数据操作
set(key, value, encrypted = false) {
if (isReadOnly) {
logAccess('set', key, false);
throw new Error('Data store is read-only');
}
if (!validateKey(key)) {
logAccess('set', key, false);
throw new Error('Invalid key');
}
const finalValue = encrypted ? encrypt(value) : value;
data.set(key, finalValue);
logAccess('set', key, true);
return this;
},
get(key, encrypted = false) {
if (!validateKey(key)) {
logAccess('get', key, false);
return undefined;
}
const value = data.get(key);
logAccess('get', key, value !== undefined);
if (encrypted && value) {
return decrypt(value);
}
return value;
},
has(key) {
const exists = data.has(key);
logAccess('has', key, exists);
return exists;
},
delete(key) {
if (isReadOnly) {
logAccess('delete', key, false);
throw new Error('Data store is read-only');
}
const existed = data.has(key);
if (existed) {
data.delete(key);
}
logAccess('delete', key, existed);
return existed;
},
clear() {
if (isReadOnly) {
logAccess('clear', 'all', false);
throw new Error('Data store is read-only');
}
const size = data.size;
data.clear();
logAccess('clear', 'all', true);
return size;
},
// 状态管理
setReadOnly(readonly = true) {
isReadOnly = readonly;
logAccess('setReadOnly', 'state', true);
return this;
},
isReadOnly() {
return isReadOnly;
},
size() {
return data.size;
},
keys() {
return Array.from(data.keys());
},
// 批量操作
setBatch(entries, encrypted = false) {
if (isReadOnly) {
throw new Error('Data store is read-only');
}
const results = [];
for (const [key, value] of entries) {
try {
this.set(key, value, encrypted);
results.push({ key, success: true });
} catch (error) {
results.push({ key, success: false, error: error.message });
}
}
return results;
},
// 安全操作
backup() {
const backup = {};
for (const [key, value] of data) {
backup[key] = value;
}
logAccess('backup', 'all', true);
return backup;
},
restore(backupData) {
if (isReadOnly) {
throw new Error('Data store is read-only');
}
data.clear();
for (const [key, value] of Object.entries(backupData)) {
data.set(key, value);
}
logAccess('restore', 'all', true);
return this;
},
// 日志和统计
getAccessLog() {
return [...accessLog]; // 返回副本
},
clearLog() {
accessLog = [];
return this;
},
getStats() {
const stats = {
totalOperations: accessLog.length,
successfulOperations: accessLog.filter(log => log.success).length,
dataSize: data.size,
isReadOnly: isReadOnly,
operationTypes: {}
};
accessLog.forEach(log => {
stats.operationTypes[log.operation] =
(stats.operationTypes[log.operation] || 0) + 1;
});
return stats;
}
};
}
console.log('=== 闭包私有成员演示 ===');
const dataStore = createSecureDataStore({ initial: 'value' });
console.log('初始数据大小:', dataStore.size());
dataStore.set('name', 'Alice');
dataStore.set('secret', 'confidential data', true); // 加密存储
console.log('普通数据:', dataStore.get('name'));
console.log('加密数据:', dataStore.get('secret', true));
console.log('所有键:', dataStore.keys());
// 设置只读模式
dataStore.setReadOnly(true);
try {
dataStore.set('new', 'value'); // 会失败
} catch (error) {
console.log('只读模式错误:', error.message);
}
console.log('操作统计:', dataStore.getStats());
console.log();
4. ES2022 原生私有字段(如果支持):
// ES2022 私有字段和方法
class ModernPrivateExample {
// 私有字段声明
#userId;
#password;
#permissions = new Set();
#loginHistory = [];
#maxLoginAttempts = 3;
#currentAttempts = 0;
// 私有静态字段
static #instanceCounter = 0;
static #activeUsers = new Map();
constructor(username, password, role = 'user') {
this.#userId = username;
this.#password = this.#hashPassword(password);
this.#setupPermissions(role);
// 公有属性
this.id = `user_${++ModernPrivateExample.#instanceCounter}`;
this.createdAt = new Date();
this.isActive = true;
ModernPrivateExample.#activeUsers.set(this.id, this);
}
// 私有方法
#hashPassword(password) {
return btoa(password + 'salt' + this.#userId);
}
#setupPermissions(role) {
const rolePermissions = {
'user': ['read'],
'editor': ['read', 'write'],
'admin': ['read', 'write', 'delete']
};
const permissions = rolePermissions[role] || rolePermissions['user'];
permissions.forEach(p => this.#permissions.add(p));
}
#validatePassword(password) {
return this.#hashPassword(password) === this.#password;
}
#logLogin(success, ip = 'unknown') {
this.#loginHistory.push({
success,
ip,
timestamp: new Date(),
attempts: this.#currentAttempts
});
}
#resetLoginAttempts() {
this.#currentAttempts = 0;
}
#incrementLoginAttempts() {
this.#currentAttempts++;
return this.#currentAttempts >= this.#maxLoginAttempts;
}
// 公有方法
authenticate(password, ip) {
if (this.#currentAttempts >= this.#maxLoginAttempts) {
throw new Error('Account temporarily locked');
}
if (this.#validatePassword(password)) {
this.#resetLoginAttempts();
this.#logLogin(true, ip);
this.lastLogin = new Date();
return true;
} else {
const isLocked = this.#incrementLoginAttempts();
this.#logLogin(false, ip);
if (isLocked) {
throw new Error('Too many failed attempts. Account locked.');
} else {
throw new Error('Invalid password');
}
}
}
hasPermission(permission) {
return this.#permissions.has(permission);
}
addPermission(permission) {
this.#permissions.add(permission);
return this;
}
removePermission(permission) {
this.#permissions.delete(permission);
return this;
}
getPermissions() {
return Array.from(this.#permissions);
}
changePassword(oldPassword, newPassword) {
if (!this.#validatePassword(oldPassword)) {
throw new Error('Current password is incorrect');
}
this.#password = this.#hashPassword(newPassword);
return true;
}
getLoginHistory() {
return this.#loginHistory.map(entry => ({
...entry,
// 不暴露敏感信息
password: undefined
}));
}
unlock() {
this.#resetLoginAttempts();
return this;
}
// 静态方法
static getActiveUserCount() {
return ModernPrivateExample.#activeUsers.size;
}
static findUser(id) {
return ModernPrivateExample.#activeUsers.get(id);
}
// 私有字段检查
static hasPrivateFields(obj) {
try {
return #userId in obj && #password in obj;
} catch {
return false;
}
}
}
// 演示现代私有字段(注意:需要支持 ES2022 的环境)
console.log('=== ES2022 私有字段演示 ===');
try {
const user = new ModernPrivateExample('alice', 'secret123', 'editor');
console.log('用户ID:', user.id);
console.log('权限:', user.getPermissions());
// 认证测试
user.authenticate('secret123', '192.168.1.1');
console.log('认证成功');
// 权限测试
console.log('可以读取:', user.hasPermission('read'));
console.log('可以写入:', user.hasPermission('write'));
console.log('可以删除:', user.hasPermission('delete'));
// 修改密码
user.changePassword('secret123', 'newPassword456');
console.log('密码已更改');
// 尝试访问私有字段(语法错误)
try {
console.log(user.#userId); // 这会导致语法错误
} catch {
console.log('私有字段无法直接访问');
}
} catch (error) {
console.log('ES2022 私有字段可能不被支持:', error.message);
}
console.log();
5. 不同实现方式的对比和最佳实践:
// 私有成员实现方式对比
class PrivateImplementationComparison {
static analyze() {
console.log('=== 私有成员实现方式对比 ===\n');
const comparisons = [
{
method: 'Symbol',
trulyPrivate: false,
performance: 'high',
memoryEfficient: true,
debuggable: 'partial',
compatibility: 'ES6+',
syntax: 'complex',
pros: ['兼容性好', '性能高', '支持元编程'],
cons: ['不是真正私有', '语法复杂', 'Symbol 可被发现']
},
{
method: 'WeakMap',
trulyPrivate: true,
performance: 'medium',
memoryEfficient: true,
debuggable: 'difficult',
compatibility: 'ES6+',
syntax: 'complex',
pros: ['真正私有', '内存安全', '垃圾回收友好'],
cons: ['语法复杂', '性能较低', '调试困难']
},
{
method: 'Closure',
trulyPrivate: true,
performance: 'low',
memoryEfficient: false,
debuggable: 'good',
compatibility: 'ES5+',
syntax: 'natural',
pros: ['真正私有', '语法自然', '兼容性极好'],
cons: ['内存消耗高', '性能较低', '每实例都创建函数']
},
{
method: 'ES2022 Private Fields',
trulyPrivate: true,
performance: 'high',
memoryEfficient: true,
debuggable: 'good',
compatibility: 'ES2022+',
syntax: 'simple',
pros: ['原生支持', '真正私有', '性能最佳', '语法简洁'],
cons: ['兼容性限制', '需要转译器支持']
}
];
comparisons.forEach((comp, index) => {
console.log(`${index + 1}. ${comp.method}`);
console.log(` 真正私有: ${comp.trulyPrivate ? '✅' : '❌'}`);
console.log(` 性能: ${comp.performance}`);
console.log(` 内存效率: ${comp.memoryEfficient ? '✅' : '❌'}`);
console.log(` 可调试性: ${comp.debuggable}`);
console.log(` 兼容性: ${comp.compatibility}`);
console.log(` 语法复杂度: ${comp.syntax}`);
console.log(' 优点:', comp.pros.join(', '));
console.log(' 缺点:', comp.cons.join(', '));
console.log('');
});
console.log('=== 选择建议 ===');
console.log('🎯 现代项目 → ES2022 私有字段');
console.log('🔄 需要兼容性 → WeakMap 方式');
console.log('⚡ 性能优先 → Symbol 方式');
console.log('🛠️ 特殊需求 → 闭包方式');
console.log('\n=== 使用场景 ===');
console.log('• 敏感数据保护 → WeakMap 或 ES2022');
console.log('• 内部状态管理 → Symbol 或 ES2022');
console.log('• 库/框架开发 → 考虑兼容性,使用 WeakMap');
console.log('• 现代应用开发 → 优先使用 ES2022 私有字段');
}
}
PrivateImplementationComparison.analyze();
私有成员实现总结:
实现方式对比:
#:原生私有,最佳性能,现代语法选择原则:
限制和注意事项:
How to implement an async queue? What are the application scenarios?
How to implement an async queue? What are the application scenarios?
考察点:Promise、Generator、事件循环。
答案:
异步队列是一种重要的并发控制机制,用于管理异步任务的执行顺序、限制并发数量、处理任务依赖等。让我们实现几种不同类型的异步队列并探讨其应用场景。
1. 基础异步队列实现:
// 基础异步队列类
class AsyncQueue {
constructor(options = {}) {
this.queue = [];
this.running = false;
this.maxConcurrency = options.maxConcurrency || 1;
this.currentConcurrency = 0;
this.results = [];
this.errors = [];
this.onProgress = options.onProgress || null;
this.onComplete = options.onComplete || null;
this.onError = options.onError || null;
this.retryCount = options.retryCount || 0;
this.retryDelay = options.retryDelay || 1000;
}
// 添加任务到队列
add(task, priority = 0) {
return new Promise((resolve, reject) => {
const taskWrapper = {
id: Math.random().toString(36).substr(2, 9),
task,
priority,
resolve,
reject,
attempts: 0,
createdAt: new Date()
};
// 按优先级插入
const insertIndex = this.queue.findIndex(item => item.priority < priority);
if (insertIndex === -1) {
this.queue.push(taskWrapper);
} else {
this.queue.splice(insertIndex, 0, taskWrapper);
}
// 自动开始处理
this.process();
});
}
// 批量添加任务
addBatch(tasks, priority = 0) {
const promises = tasks.map(task => this.add(task, priority));
return Promise.all(promises);
}
// 处理队列
async process() {
if (this.running && this.currentConcurrency >= this.maxConcurrency) {
return;
}
this.running = true;
while (this.queue.length > 0 && this.currentConcurrency < this.maxConcurrency) {
const taskWrapper = this.queue.shift();
this.currentConcurrency++;
// 异步执行任务
this.executeTask(taskWrapper);
}
// 检查是否所有任务完成
if (this.queue.length === 0 && this.currentConcurrency === 0) {
this.running = false;
if (this.onComplete) {
this.onComplete({
results: this.results,
errors: this.errors,
totalTasks: this.results.length + this.errors.length
});
}
}
}
// 执行单个任务
async executeTask(taskWrapper) {
try {
taskWrapper.startedAt = new Date();
taskWrapper.attempts++;
const result = await taskWrapper.task();
taskWrapper.completedAt = new Date();
taskWrapper.duration = taskWrapper.completedAt - taskWrapper.startedAt;
this.results.push({
id: taskWrapper.id,
result,
duration: taskWrapper.duration,
attempts: taskWrapper.attempts
});
taskWrapper.resolve(result);
// 触发进度回调
if (this.onProgress) {
this.onProgress({
completed: this.results.length,
errors: this.errors.length,
remaining: this.queue.length,
current: this.currentConcurrency
});
}
} catch (error) {
// 重试逻辑
if (taskWrapper.attempts <= this.retryCount) {
console.log(`Task ${taskWrapper.id} failed, retrying... (${taskWrapper.attempts}/${this.retryCount + 1})`);
// 延迟重试
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
// 重新加入队列
this.queue.unshift(taskWrapper);
} else {
// 重试次数用完,记录错误
this.errors.push({
id: taskWrapper.id,
error: error.message,
attempts: taskWrapper.attempts,
task: taskWrapper.task.toString()
});
taskWrapper.reject(error);
if (this.onError) {
this.onError(error, taskWrapper);
}
}
} finally {
this.currentConcurrency--;
// 继续处理队列
setTimeout(() => this.process(), 0);
}
}
// 清空队列
clear() {
this.queue.forEach(taskWrapper => {
taskWrapper.reject(new Error('Queue cleared'));
});
this.queue = [];
return this;
}
// 暂停队列
pause() {
this.running = false;
return this;
}
// 恢复队列
resume() {
this.running = true;
this.process();
return this;
}
// 获取队列状态
getStatus() {
return {
queueLength: this.queue.length,
running: this.running,
currentConcurrency: this.currentConcurrency,
maxConcurrency: this.maxConcurrency,
completed: this.results.length,
failed: this.errors.length
};
}
// 等待所有任务完成
async waitForCompletion() {
return new Promise(resolve => {
if (this.queue.length === 0 && this.currentConcurrency === 0) {
resolve({
results: this.results,
errors: this.errors
});
return;
}
const originalOnComplete = this.onComplete;
this.onComplete = (result) => {
if (originalOnComplete) {
originalOnComplete(result);
}
resolve(result);
};
});
}
}
// 使用示例
console.log('=== 基础异步队列演示 ===');
async function demoBasicQueue() {
const queue = new AsyncQueue({
maxConcurrency: 3,
retryCount: 2,
retryDelay: 500,
onProgress: (progress) => {
console.log(`进度: ${progress.completed}/${progress.completed + progress.remaining + progress.current} 完成`);
}
});
// 创建测试任务
const createTask = (id, duration, shouldFail = false) => {
return () => new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail && Math.random() < 0.3) {
reject(new Error(`Task ${id} failed`));
} else {
resolve(`Task ${id} completed in ${duration}ms`);
}
}, duration);
});
};
// 添加任务
const tasks = [
queue.add(createTask(1, 1000), 1), // 高优先级
queue.add(createTask(2, 500)),
queue.add(createTask(3, 800)),
queue.add(createTask(4, 300), 2), // 最高优先级
queue.add(createTask(5, 600))
];
try {
const results = await Promise.all(tasks);
console.log('所有任务完成:', results.length);
console.log('队列状态:', queue.getStatus());
} catch (error) {
console.error('任务执行错误:', error.message);
}
}
await demoBasicQueue();
console.log();
2. 支持任务依赖的异步队列:
// 支持任务依赖的异步队列
class DependencyAsyncQueue {
constructor(options = {}) {
this.tasks = new Map();
this.dependencies = new Map();
this.completed = new Set();
this.running = new Set();
this.failed = new Set();
this.maxConcurrency = options.maxConcurrency || 5;
this.currentConcurrency = 0;
this.results = new Map();
this.errors = new Map();
this.onTaskComplete = options.onTaskComplete || null;
this.onTaskError = options.onTaskError || null;
this.onAllComplete = options.onAllComplete || null;
}
// 添加任务及其依赖
addTask(id, taskFn, dependencies = []) {
if (this.tasks.has(id)) {
throw new Error(`Task ${id} already exists`);
}
this.tasks.set(id, taskFn);
this.dependencies.set(id, new Set(dependencies));
// 验证依赖是否存在
for (const dep of dependencies) {
if (!this.tasks.has(dep) && !this.completed.has(dep)) {
console.warn(`Dependency ${dep} for task ${id} not found`);
}
}
return this;
}
// 批量添加任务
addTasks(taskMap) {
for (const [id, config] of Object.entries(taskMap)) {
this.addTask(id, config.task, config.dependencies || []);
}
return this;
}
// 检查任务是否可以执行
canExecute(taskId) {
if (this.running.has(taskId) || this.completed.has(taskId) || this.failed.has(taskId)) {
return false;
}
const dependencies = this.dependencies.get(taskId);
if (!dependencies) return false;
// 检查所有依赖是否已完成
for (const dep of dependencies) {
if (!this.completed.has(dep)) {
return false;
}
}
return true;
}
// 获取可执行的任务
getExecutableTasks() {
const executable = [];
for (const taskId of this.tasks.keys()) {
if (this.canExecute(taskId)) {
executable.push(taskId);
}
}
return executable;
}
// 执行任务
async executeTask(taskId) {
if (!this.canExecute(taskId)) {
throw new Error(`Task ${taskId} cannot be executed`);
}
this.running.add(taskId);
this.currentConcurrency++;
try {
const taskFn = this.tasks.get(taskId);
const dependencies = this.dependencies.get(taskId);
// 获取依赖的结果
const dependencyResults = {};
for (const dep of dependencies) {
dependencyResults[dep] = this.results.get(dep);
}
console.log(`开始执行任务: ${taskId}`);
const startTime = Date.now();
// 执行任务,传入依赖结果
const result = await taskFn(dependencyResults);
const duration = Date.now() - startTime;
console.log(`任务 ${taskId} 完成,耗时 ${duration}ms`);
// 记录结果
this.results.set(taskId, result);
this.completed.add(taskId);
this.running.delete(taskId);
if (this.onTaskComplete) {
this.onTaskComplete(taskId, result, duration);
}
return result;
} catch (error) {
console.error(`任务 ${taskId} 失败:`, error.message);
this.errors.set(taskId, error);
this.failed.add(taskId);
this.running.delete(taskId);
if (this.onTaskError) {
this.onTaskError(taskId, error);
}
throw error;
} finally {
this.currentConcurrency--;
// 尝试执行更多任务
setImmediate(() => this.processQueue());
}
}
// 处理队列
async processQueue() {
const executableTasks = this.getExecutableTasks();
const availableSlots = this.maxConcurrency - this.currentConcurrency;
const tasksToRun = executableTasks.slice(0, availableSlots);
// 并行执行可用任务
const promises = tasksToRun.map(taskId =>
this.executeTask(taskId).catch(error => {
// 错误处理已在 executeTask 中完成
return null;
})
);
if (promises.length > 0) {
await Promise.all(promises);
// 检查是否还有更多任务可以执行
const remainingExecutable = this.getExecutableTasks();
if (remainingExecutable.length > 0 && this.currentConcurrency < this.maxConcurrency) {
await this.processQueue();
}
}
// 检查是否所有任务都已完成或失败
const totalTasks = this.tasks.size;
const finishedTasks = this.completed.size + this.failed.size;
if (finishedTasks === totalTasks && this.currentConcurrency === 0) {
if (this.onAllComplete) {
this.onAllComplete({
completed: this.completed.size,
failed: this.failed.size,
results: Object.fromEntries(this.results),
errors: Object.fromEntries(this.errors)
});
}
}
}
// 开始执行所有任务
async execute() {
console.log('开始执行依赖队列...');
console.log(`总任务数: ${this.tasks.size}`);
const startTime = Date.now();
await this.processQueue();
// 等待所有任务完成
return new Promise((resolve) => {
const checkCompletion = () => {
const totalTasks = this.tasks.size;
const finishedTasks = this.completed.size + this.failed.size;
if (finishedTasks === totalTasks && this.currentConcurrency === 0) {
const duration = Date.now() - startTime;
resolve({
duration,
completed: this.completed.size,
failed: this.failed.size,
results: Object.fromEntries(this.results),
errors: Object.fromEntries(this.errors)
});
} else {
setTimeout(checkCompletion, 100);
}
};
checkCompletion();
});
}
// 获取任务状态
getStatus() {
return {
total: this.tasks.size,
completed: this.completed.size,
running: this.running.size,
failed: this.failed.size,
pending: this.tasks.size - this.completed.size - this.running.size - this.failed.size
};
}
// 可视化依赖图(简单版本)
printDependencyGraph() {
console.log('\n=== 任务依赖图 ===');
for (const [taskId, dependencies] of this.dependencies) {
const deps = Array.from(dependencies);
const status = this.completed.has(taskId) ? '✅' :
this.running.has(taskId) ? '🔄' :
this.failed.has(taskId) ? '❌' : '⏳';
console.log(`${status} ${taskId} ${deps.length > 0 ? `← [${deps.join(', ')}]` : '(no deps)'}`);
}
console.log('');
}
}
// 使用示例
console.log('=== 依赖异步队列演示 ===');
async function demoDependencyQueue() {
const queue = new DependencyAsyncQueue({
maxConcurrency: 3,
onTaskComplete: (id, result, duration) => {
console.log(`✅ 任务 ${id} 完成: ${result}`);
},
onTaskError: (id, error) => {
console.log(`❌ 任务 ${id} 失败: ${error.message}`);
}
});
// 模拟复杂的任务依赖关系
const tasks = {
'init': {
task: async () => {
await new Promise(resolve => setTimeout(resolve, 500));
return '初始化完成';
},
dependencies: []
},
'load-config': {
task: async (deps) => {
await new Promise(resolve => setTimeout(resolve, 300));
return { config: 'loaded', init: deps.init };
},
dependencies: ['init']
},
'load-data': {
task: async (deps) => {
await new Promise(resolve => setTimeout(resolve, 800));
return { data: 'user-data', config: deps['load-config'] };
},
dependencies: ['load-config']
},
'setup-auth': {
task: async (deps) => {
await new Promise(resolve => setTimeout(resolve, 400));
return { auth: 'setup', config: deps['load-config'] };
},
dependencies: ['load-config']
},
'process-data': {
task: async (deps) => {
await new Promise(resolve => setTimeout(resolve, 600));
return { processed: true, data: deps['load-data'], auth: deps['setup-auth'] };
},
dependencies: ['load-data', 'setup-auth']
},
'generate-report': {
task: async (deps) => {
await new Promise(resolve => setTimeout(resolve, 400));
return { report: 'generated', processedData: deps['process-data'] };
},
dependencies: ['process-data']
},
'send-notification': {
task: async (deps) => {
await new Promise(resolve => setTimeout(resolve, 200));
return { notification: 'sent', report: deps['generate-report'] };
},
dependencies: ['generate-report']
}
};
// 添加所有任务
queue.addTasks(tasks);
// 显示依赖图
queue.printDependencyGraph();
// 执行队列
const result = await queue.execute();
console.log('\n=== 执行结果 ===');
console.log(`总耗时: ${result.duration}ms`);
console.log(`成功: ${result.completed}, 失败: ${result.failed}`);
console.log('最终结果:', result.results['send-notification']);
}
await demoDependencyQueue();
console.log();
3. 支持优先级和延迟执行的异步队列:
// 高级异步队列 - 支持优先级、延迟、取消等功能
class AdvancedAsyncQueue {
constructor(options = {}) {
this.queues = {
high: [],
normal: [],
low: []
};
this.running = new Map();
this.completed = new Map();
this.cancelled = new Set();
this.delayed = new Map();
this.maxConcurrency = options.maxConcurrency || 3;
this.currentConcurrency = 0;
this.taskTimeout = options.taskTimeout || 30000;
this.retryAttempts = options.retryAttempts || 3;
this.retryDelay = options.retryDelay || 1000;
this.metrics = {
totalAdded: 0,
totalCompleted: 0,
totalFailed: 0,
totalCancelled: 0,
averageExecutionTime: 0
};
// 事件处理
this.listeners = new Map();
// 定时检查延迟任务
this.delayCheckInterval = setInterval(() => {
this.processDelayedTasks();
}, 100);
}
// 事件系统
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
return this;
}
emit(event, data) {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Event callback error for ${event}:`, error);
}
});
}
}
// 生成唯一任务ID
generateTaskId() {
return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 添加任务
add(taskFn, options = {}) {
const {
priority = 'normal',
delay = 0,
timeout = this.taskTimeout,
retries = this.retryAttempts,
context = {},
onProgress = null
} = options;
const taskId = this.generateTaskId();
const task = {
id: taskId,
fn: taskFn,
priority,
timeout,
retries,
attempts: 0,
context,
onProgress,
createdAt: Date.now(),
scheduledAt: Date.now() + delay
};
if (delay > 0) {
// 延迟任务
this.delayed.set(taskId, task);
} else {
// 立即加入队列
this.enqueueTask(task);
}
this.metrics.totalAdded++;
this.emit('task-added', { taskId, priority, delay });
// 返回可以取消的 Promise
return {
taskId,
promise: new Promise((resolve, reject) => {
task.resolve = resolve;
task.reject = reject;
}),
cancel: () => this.cancelTask(taskId)
};
}
// 将任务加入优先级队列
enqueueTask(task) {
const queue = this.queues[task.priority] || this.queues.normal;
// 按创建时间排序(FIFO)
const insertIndex = queue.findIndex(t => t.createdAt > task.createdAt);
if (insertIndex === -1) {
queue.push(task);
} else {
queue.splice(insertIndex, 0, task);
}
// 尝试开始处理
this.processQueue();
}
// 处理延迟任务
processDelayedTasks() {
const now = Date.now();
const readyTasks = [];
for (const [taskId, task] of this.delayed.entries()) {
if (now >= task.scheduledAt) {
readyTasks.push(taskId);
}
}
readyTasks.forEach(taskId => {
const task = this.delayed.get(taskId);
this.delayed.delete(taskId);
if (!this.cancelled.has(taskId)) {
this.enqueueTask(task);
}
});
}
// 获取下一个要执行的任务
getNextTask() {
// 优先级顺序:high -> normal -> low
for (const priority of ['high', 'normal', 'low']) {
const queue = this.queues[priority];
if (queue.length > 0) {
return queue.shift();
}
}
return null;
}
// 处理队列
async processQueue() {
while (this.currentConcurrency < this.maxConcurrency) {
const task = this.getNextTask();
if (!task) break;
if (this.cancelled.has(task.id)) {
this.handleTaskCancellation(task);
continue;
}
// 异步执行任务
this.executeTask(task);
}
}
// 执行单个任务
async executeTask(task) {
this.currentConcurrency++;
this.running.set(task.id, task);
const startTime = Date.now();
task.startedAt = startTime;
this.emit('task-started', {
taskId: task.id,
priority: task.priority,
attempt: task.attempts + 1
});
try {
task.attempts++;
// 创建任务执行 Promise
const taskPromise = this.createTaskPromise(task);
// 创建超时 Promise
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Task ${task.id} timed out after ${task.timeout}ms`));
}, task.timeout);
});
// 竞争执行
const result = await Promise.race([taskPromise, timeoutPromise]);
// 任务成功
const executionTime = Date.now() - startTime;
task.completedAt = Date.now();
task.executionTime = executionTime;
this.completed.set(task.id, { task, result, executionTime });
this.running.delete(task.id);
// 更新统计
this.metrics.totalCompleted++;
this.updateAverageExecutionTime(executionTime);
task.resolve(result);
this.emit('task-completed', {
taskId: task.id,
result,
executionTime,
attempts: task.attempts
});
} catch (error) {
await this.handleTaskError(task, error, startTime);
} finally {
this.currentConcurrency--;
// 继续处理队列
setTimeout(() => this.processQueue(), 0);
}
}
// 创建任务执行 Promise
createTaskPromise(task) {
return new Promise(async (resolve, reject) => {
try {
// 创建任务上下文
const taskContext = {
...task.context,
taskId: task.id,
attempt: task.attempts,
progress: (data) => {
if (task.onProgress) {
task.onProgress(data);
}
this.emit('task-progress', { taskId: task.id, ...data });
}
};
const result = await task.fn(taskContext);
resolve(result);
} catch (error) {
reject(error);
}
});
}
// 处理任务错误
async handleTaskError(task, error, startTime) {
const executionTime = Date.now() - startTime;
// 检查是否应该重试
if (task.attempts < task.retries && !this.cancelled.has(task.id)) {
console.log(`Task ${task.id} failed (attempt ${task.attempts}/${task.retries}), retrying...`);
this.emit('task-retry', {
taskId: task.id,
error: error.message,
attempt: task.attempts,
maxRetries: task.retries
});
// 延迟重试
await new Promise(resolve => setTimeout(resolve, this.retryDelay * task.attempts));
// 重新加入队列
this.running.delete(task.id);
this.enqueueTask(task);
} else {
// 重试次数用完或任务被取消
this.running.delete(task.id);
this.metrics.totalFailed++;
task.reject(error);
this.emit('task-failed', {
taskId: task.id,
error: error.message,
attempts: task.attempts,
executionTime
});
}
}
// 取消任务
cancelTask(taskId) {
this.cancelled.add(taskId);
// 从延迟队列移除
if (this.delayed.has(taskId)) {
const task = this.delayed.get(taskId);
this.delayed.delete(taskId);
task.reject(new Error('Task cancelled'));
}
// 从普通队列移除
for (const queue of Object.values(this.queues)) {
const index = queue.findIndex(task => task.id === taskId);
if (index !== -1) {
const task = queue.splice(index, 1)[0];
task.reject(new Error('Task cancelled'));
break;
}
}
// 如果任务正在运行,标记为取消(实际取消需要任务配合)
if (this.running.has(taskId)) {
const task = this.running.get(taskId);
task.cancelled = true;
}
this.metrics.totalCancelled++;
this.emit('task-cancelled', { taskId });
return true;
}
// 处理任务取消
handleTaskCancellation(task) {
task.reject(new Error('Task cancelled'));
this.emit('task-cancelled', { taskId: task.id });
}
// 更新平均执行时间
updateAverageExecutionTime(newTime) {
const total = this.metrics.totalCompleted;
const currentAvg = this.metrics.averageExecutionTime;
this.metrics.averageExecutionTime = ((currentAvg * (total - 1)) + newTime) / total;
}
// 获取队列状态
getStatus() {
const totalQueued = Object.values(this.queues).reduce((sum, queue) => sum + queue.length, 0);
return {
queued: {
total: totalQueued,
high: this.queues.high.length,
normal: this.queues.normal.length,
low: this.queues.low.length
},
delayed: this.delayed.size,
running: this.running.size,
completed: this.completed.size,
cancelled: this.cancelled.size,
concurrency: {
current: this.currentConcurrency,
max: this.maxConcurrency
},
metrics: { ...this.metrics }
};
}
// 暂停队列
pause() {
this.paused = true;
return this;
}
// 恢复队列
resume() {
this.paused = false;
this.processQueue();
return this;
}
// 清空队列
clear() {
// 取消所有待处理任务
for (const queue of Object.values(this.queues)) {
queue.forEach(task => {
task.reject(new Error('Queue cleared'));
});
queue.length = 0;
}
// 取消所有延迟任务
for (const [taskId, task] of this.delayed) {
task.reject(new Error('Queue cleared'));
}
this.delayed.clear();
this.emit('queue-cleared', {});
return this;
}
// 销毁队列
destroy() {
this.clear();
clearInterval(this.delayCheckInterval);
this.listeners.clear();
this.emit('queue-destroyed', {});
}
// 等待所有任务完成
async waitForCompletion() {
return new Promise(resolve => {
const checkCompletion = () => {
const status = this.getStatus();
const totalPending = status.queued.total + status.delayed + status.running;
if (totalPending === 0) {
resolve(this.getStatus());
} else {
setTimeout(checkCompletion, 100);
}
};
checkCompletion();
});
}
}
// 使用示例
console.log('=== 高级异步队列演示 ===');
async function demoAdvancedQueue() {
const queue = new AdvancedAsyncQueue({
maxConcurrency: 2,
taskTimeout: 5000,
retryAttempts: 2
});
// 监听事件
queue.on('task-started', ({ taskId, priority, attempt }) => {
console.log(`🚀 任务 ${taskId} 开始执行 (优先级: ${priority}, 尝试: ${attempt})`);
});
queue.on('task-completed', ({ taskId, executionTime }) => {
console.log(`✅ 任务 ${taskId} 完成,耗时 ${executionTime}ms`);
});
queue.on('task-failed', ({ taskId, error, attempts }) => {
console.log(`❌ 任务 ${taskId} 失败: ${error} (尝试了 ${attempts} 次)`);
});
queue.on('task-retry', ({ taskId, attempt, maxRetries }) => {
console.log(`🔄 任务 ${taskId} 重试 ${attempt}/${maxRetries}`);
});
// 创建不同类型的任务
const tasks = [];
// 高优先级任务
tasks.push(queue.add(async (ctx) => {
ctx.progress({ step: 1, message: '开始高优先级任务' });
await new Promise(resolve => setTimeout(resolve, 1000));
ctx.progress({ step: 2, message: '高优先级任务完成' });
return '高优先级任务结果';
}, { priority: 'high' }));
// 普通任务带进度
tasks.push(queue.add(async (ctx) => {
for (let i = 1; i <= 5; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
ctx.progress({ step: i, total: 5, message: `处理步骤 ${i}/5` });
}
return '普通任务结果';
}, { priority: 'normal' }));
// 低优先级延迟任务
tasks.push(queue.add(async () => {
await new Promise(resolve => setTimeout(resolve, 500));
return '低优先级延迟任务结果';
}, { priority: 'low', delay: 2000 }));
// 可能失败的任务
tasks.push(queue.add(async () => {
if (Math.random() < 0.7) {
throw new Error('模拟任务失败');
}
return '幸运任务成功';
}, { retries: 3 }));
// 可取消的任务
const cancellableTask = queue.add(async () => {
await new Promise(resolve => setTimeout(resolve, 3000));
return '长时间运行的任务';
}, { priority: 'normal' });
// 2秒后取消任务
setTimeout(() => {
console.log('取消长时间运行的任务...');
cancellableTask.cancel();
}, 2000);
tasks.push(cancellableTask);
// 显示初始状态
console.log('初始队列状态:', queue.getStatus());
// 等待一些任务完成
try {
const results = await Promise.allSettled(tasks.map(t => t.promise));
console.log('\n=== 任务执行结果 ===');
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`任务 ${index + 1}: ✅ ${result.value}`);
} else {
console.log(`任务 ${index + 1}: ❌ ${result.reason.message}`);
}
});
} catch (error) {
console.error('任务执行出错:', error);
}
// 最终状态
const finalStatus = queue.getStatus();
console.log('\n=== 最终队列状态 ===');
console.log(`已完成: ${finalStatus.completed}`);
console.log(`已取消: ${finalStatus.cancelled}`);
console.log(`平均执行时间: ${finalStatus.metrics.averageExecutionTime.toFixed(2)}ms`);
// 清理
queue.destroy();
}
await demoAdvancedQueue();
console.log();
4. 异步队列的实际应用场景:
// 应用场景演示
class RealWorldAsyncQueue {
// 场景1:文件上传队列
static createFileUploadQueue() {
console.log('=== 文件上传队列场景 ===');
const uploadQueue = new AsyncQueue({
maxConcurrency: 3, // 限制并发上传数
onProgress: (progress) => {
console.log(`上传进度: ${progress.completed}/${progress.completed + progress.remaining} 文件`);
}
});
// 模拟文件上传任务
const createUploadTask = (fileName, fileSize) => {
return () => new Promise((resolve, reject) => {
console.log(`开始上传: ${fileName} (${fileSize}MB)`);
// 模拟上传时间(文件越大耗时越长)
const uploadTime = fileSize * 100 + Math.random() * 500;
setTimeout(() => {
if (Math.random() < 0.1) { // 10% 失败率
reject(new Error(`${fileName} 上传失败`));
} else {
resolve({
fileName,
fileSize,
uploadTime,
url: `https://cdn.example.com/${fileName}`
});
}
}, uploadTime);
});
};
// 添加上传任务
const files = [
{ name: 'avatar.jpg', size: 2 },
{ name: 'document.pdf', size: 15 },
{ name: 'video.mp4', size: 50 },
{ name: 'image1.png', size: 5 },
{ name: 'image2.png', size: 3 }
];
const uploadPromises = files.map(file =>
uploadQueue.add(createUploadTask(file.name, file.size), 1) // 高优先级
);
return Promise.all(uploadPromises);
}
// 场景2:API请求限流
static createAPIRateLimitQueue() {
console.log('\n=== API请求限流场景 ===');
class RateLimitedAPIQueue extends AsyncQueue {
constructor(options) {
super(options);
this.rateLimit = options.rateLimit || 10; // 每秒请求数
this.requestTimes = [];
}
async add(apiCall, priority = 0) {
// 检查速率限制
await this.checkRateLimit();
return super.add(apiCall, priority);
}
async checkRateLimit() {
const now = Date.now();
const oneSecondAgo = now - 1000;
// 清理超过1秒的请求记录
this.requestTimes = this.requestTimes.filter(time => time > oneSecondAgo);
// 如果达到速率限制,等待
if (this.requestTimes.length >= this.rateLimit) {
const oldestRequest = Math.min(...this.requestTimes);
const waitTime = 1000 - (now - oldestRequest);
if (waitTime > 0) {
console.log(`速率限制,等待 ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
this.requestTimes.push(now);
}
}
const apiQueue = new RateLimitedAPIQueue({
maxConcurrency: 5,
rateLimit: 3 // 每秒最多3个请求
});
// 模拟API请求
const createAPIRequest = (endpoint, params) => {
return () => new Promise((resolve, reject) => {
console.log(`API请求: ${endpoint}`);
setTimeout(() => {
if (Math.random() < 0.05) { // 5% 失败率
reject(new Error(`API ${endpoint} 请求失败`));
} else {
resolve({
endpoint,
params,
data: `${endpoint} 的响应数据`,
timestamp: new Date().toISOString()
});
}
}, 200 + Math.random() * 300);
});
};
// 添加多个API请求
const requests = [
'/api/users',
'/api/posts',
'/api/comments',
'/api/notifications',
'/api/settings',
'/api/analytics',
'/api/reports'
].map(endpoint =>
apiQueue.add(createAPIRequest(endpoint, {}))
);
return Promise.all(requests);
}
// 场景3:数据处理管道
static createDataProcessingQueue() {
console.log('\n=== 数据处理管道场景 ===');
const processingQueue = new DependencyAsyncQueue({
maxConcurrency: 4
});
// 数据处理任务
const tasks = {
'fetch-raw-data': {
task: async () => {
console.log('获取原始数据...');
await new Promise(resolve => setTimeout(resolve, 500));
return Array.from({ length: 1000 }, (_, i) => ({
id: i + 1,
value: Math.random() * 100,
timestamp: Date.now() + i
}));
},
dependencies: []
},
'validate-data': {
task: async (deps) => {
console.log('验证数据...');
const rawData = deps['fetch-raw-data'];
await new Promise(resolve => setTimeout(resolve, 300));
return rawData.filter(item =>
item.value > 0 && item.value < 100
);
},
dependencies: ['fetch-raw-data']
},
'transform-data': {
task: async (deps) => {
console.log('转换数据格式...');
const validData = deps['validate-data'];
await new Promise(resolve => setTimeout(resolve, 400));
return validData.map(item => ({
...item,
normalized: item.value / 100,
category: item.value > 50 ? 'high' : 'low'
}));
},
dependencies: ['validate-data']
},
'aggregate-data': {
task: async (deps) => {
console.log('聚合数据...');
const transformedData = deps['transform-data'];
await new Promise(resolve => setTimeout(resolve, 200));
const stats = {
total: transformedData.length,
high: transformedData.filter(item => item.category === 'high').length,
low: transformedData.filter(item => item.category === 'low').length,
average: transformedData.reduce((sum, item) => sum + item.value, 0) / transformedData.length
};
return { data: transformedData, stats };
},
dependencies: ['transform-data']
},
'save-to-database': {
task: async (deps) => {
console.log('保存到数据库...');
const { data, stats } = deps['aggregate-data'];
await new Promise(resolve => setTimeout(resolve, 600));
return {
saved: true,
recordCount: data.length,
stats
};
},
dependencies: ['aggregate-data']
},
'generate-report': {
task: async (deps) => {
console.log('生成报告...');
const saveResult = deps['save-to-database'];
await new Promise(resolve => setTimeout(resolve, 300));
return {
reportId: `RPT_${Date.now()}`,
summary: saveResult.stats,
generatedAt: new Date().toISOString()
};
},
dependencies: ['save-to-database']
}
};
processingQueue.addTasks(tasks);
return processingQueue.execute();
}
// 场景4:微服务调用编排
static createMicroserviceOrchestration() {
console.log('\n=== 微服务调用编排场景 ===');
const orchestrationQueue = new AdvancedAsyncQueue({
maxConcurrency: 6,
taskTimeout: 10000
});
// 微服务调用模拟
const callMicroservice = (serviceName, method, data, options = {}) => {
return async (ctx) => {
const { timeout = 2000, shouldFail = false } = options;
ctx.progress({ service: serviceName, status: 'calling', method });
console.log(`调用微服务: ${serviceName}.${method}`);
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail && Math.random() < 0.2) {
reject(new Error(`${serviceName} 服务调用失败`));
} else {
ctx.progress({ service: serviceName, status: 'completed', method });
resolve({
service: serviceName,
method,
data: `${serviceName} 的响应`,
timestamp: Date.now()
});
}
}, timeout);
});
};
};
// 编排微服务调用
const serviceCallsHigh = [
orchestrationQueue.add(
callMicroservice('user-service', 'getProfile', { userId: 123 }),
{ priority: 'high', retries: 3 }
),
orchestrationQueue.add(
callMicroservice('auth-service', 'validateToken', { token: 'xxx' }),
{ priority: 'high', retries: 3 }
)
];
const serviceCallsNormal = [
orchestrationQueue.add(
callMicroservice('product-service', 'getRecommendations', {}),
{ priority: 'normal', retries: 2 }
),
orchestrationQueue.add(
callMicroservice('inventory-service', 'checkStock', { productIds: [1, 2, 3] }),
{ priority: 'normal', retries: 2 }
),
orchestrationQueue.add(
callMicroservice('pricing-service', 'calculatePrices', {}),
{ priority: 'normal', retries: 2 }
)
];
const serviceCallsLow = [
orchestrationQueue.add(
callMicroservice('analytics-service', 'trackEvent', { event: 'page_view' }),
{ priority: 'low', retries: 1 }
),
orchestrationQueue.add(
callMicroservice('notification-service', 'sendEmail', { template: 'welcome' }),
{ priority: 'low', delay: 1000, retries: 1 }
)
];
return Promise.allSettled([
...serviceCallsHigh.map(call => call.promise),
...serviceCallsNormal.map(call => call.promise),
...serviceCallsLow.map(call => call.promise)
]);
}
}
// 运行所有应用场景演示
async function runRealWorldExamples() {
try {
// 文件上传队列
const uploadResults = await RealWorldAsyncQueue.createFileUploadQueue();
console.log('上传完成,成功文件数:', uploadResults.filter(r => r).length);
// API限流队列
const apiResults = await RealWorldAsyncQueue.createAPIRateLimitQueue();
console.log('API请求完成,成功请求数:', apiResults.filter(r => r).length);
// 数据处理管道
const processingResult = await RealWorldAsyncQueue.createDataProcessingQueue();
console.log('数据处理完成:', processingResult.completed, '个任务');
// 微服务编排
const orchestrationResults = await RealWorldAsyncQueue.createMicroserviceOrchestration();
const successfulCalls = orchestrationResults.filter(r => r.status === 'fulfilled').length;
console.log('微服务调用完成,成功:', successfulCalls, '个调用');
} catch (error) {
console.error('演示过程中出错:', error);
}
}
await runRealWorldExamples();
异步队列应用场景总结:
并发控制场景:
任务调度场景:
资源管理场景:
用户体验场景:
系统集成场景:
How to use Proxy to implement data validation and automatic formatting?
How to use Proxy to implement data validation and automatic formatting?
考察点:拦截器与数据处理。
答案:
Proxy 是 ES6 提供的强大元编程工具,可以拦截和自定义对象操作。通过 Proxy 的各种 trap(拦截器),我们可以实现数据校验、自动格式化、类型转换等功能。让我们通过具体实现来探索 Proxy 在数据处理中的应用。
1. 基础数据校验 Proxy:
// 基础数据校验系统
class DataValidator {
constructor(schema = {}) {
this.schema = schema;
this.errors = new Map();
this.formatters = new Map();
this.validators = new Map();
// 内置验证器
this.setupBuiltInValidators();
// 内置格式化器
this.setupBuiltInFormatters();
}
setupBuiltInValidators() {
this.validators.set('required', (value) => {
return value !== undefined && value !== null && value !== '';
});
this.validators.set('string', (value) => {
return typeof value === 'string';
});
this.validators.set('number', (value) => {
return typeof value === 'number' && !isNaN(value);
});
this.validators.set('email', (value) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
});
this.validators.set('phone', (value) => {
return /^[\d\s\-\+\(\)]+$/.test(value);
});
this.validators.set('minLength', (value, min) => {
return value && value.length >= min;
});
this.validators.set('maxLength', (value, max) => {
return value && value.length <= max;
});
this.validators.set('range', (value, min, max) => {
return value >= min && value <= max;
});
}
setupBuiltInFormatters() {
this.formatters.set('trim', (value) => {
return typeof value === 'string' ? value.trim() : value;
});
this.formatters.set('lowercase', (value) => {
return typeof value === 'string' ? value.toLowerCase() : value;
});
this.formatters.set('uppercase', (value) => {
return typeof value === 'string' ? value.toUpperCase() : value;
});
this.formatters.set('capitalize', (value) => {
if (typeof value !== 'string') return value;
return value.charAt(0).toUpperCase() + value.slice(1).toLowerCase();
});
this.formatters.set('phone', (value) => {
if (typeof value !== 'string') return value;
// 格式化电话号码:去除非数字字符,添加分隔符
const digits = value.replace(/\D/g, '');
if (digits.length === 11) {
return digits.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
}
return value;
});
this.formatters.set('currency', (value) => {
if (typeof value !== 'number') return value;
return `¥${value.toFixed(2)}`;
});
}
// 验证单个值
validateField(key, value, fieldSchema) {
const errors = [];
// 应用格式化器
if (fieldSchema.format) {
const formatters = Array.isArray(fieldSchema.format) ?
fieldSchema.format : [fieldSchema.format];
for (const formatterName of formatters) {
const formatter = this.formatters.get(formatterName);
if (formatter) {
value = formatter(value);
} else if (typeof formatterName === 'function') {
value = formatterName(value);
}
}
}
// 应用验证规则
if (fieldSchema.rules) {
for (const rule of fieldSchema.rules) {
const { validator, params = [], message } = rule;
const validatorFn = this.validators.get(validator) || validator;
if (typeof validatorFn === 'function') {
const isValid = validatorFn(value, ...params);
if (!isValid) {
errors.push(message || `${key} validation failed for ${validator}`);
}
} else {
console.warn(`Validator ${validator} not found`);
}
}
}
return { value, errors };
}
// 创建代理对象
createProxy(target = {}) {
const validator = this;
return new Proxy(target, {
set(obj, key, value) {
const fieldSchema = validator.schema[key];
if (fieldSchema) {
const result = validator.validateField(key, value, fieldSchema);
if (result.errors.length > 0) {
validator.errors.set(key, result.errors);
// 根据配置决定是否阻止设置
if (fieldSchema.strict === true) {
throw new Error(`Validation failed for ${key}: ${result.errors.join(', ')}`);
}
} else {
validator.errors.delete(key);
}
// 使用格式化后的值
obj[key] = result.value;
} else {
// 没有定义schema的字段直接设置
obj[key] = value;
}
return true;
},
get(obj, key) {
// 特殊方法
if (key === '$validate') {
return () => validator.validateAll(obj);
}
if (key === '$errors') {
return Object.fromEntries(validator.errors);
}
if (key === '$isValid') {
return validator.errors.size === 0;
}
if (key === '$reset') {
return () => {
validator.errors.clear();
return true;
};
}
return obj[key];
},
has(obj, key) {
return key in obj || ['$validate', '$errors', '$isValid', '$reset'].includes(key);
},
ownKeys(obj) {
return [...Reflect.ownKeys(obj), '$validate', '$errors', '$isValid', '$reset'];
},
deleteProperty(obj, key) {
if (validator.schema[key] && validator.schema[key].required) {
throw new Error(`Cannot delete required field: ${key}`);
}
validator.errors.delete(key);
delete obj[key];
return true;
}
});
}
// 验证整个对象
validateAll(obj) {
const results = {};
for (const [key, fieldSchema] of Object.entries(this.schema)) {
const value = obj[key];
const result = this.validateField(key, value, fieldSchema);
results[key] = result;
if (result.errors.length > 0) {
this.errors.set(key, result.errors);
} else {
this.errors.delete(key);
}
}
return {
isValid: this.errors.size === 0,
errors: Object.fromEntries(this.errors),
results
};
}
}
// 使用示例
console.log('=== 基础数据校验演示 ===');
const userSchema = {
name: {
rules: [
{ validator: 'required', message: '姓名是必填的' },
{ validator: 'string', message: '姓名必须是字符串' },
{ validator: 'minLength', params: [2], message: '姓名至少2个字符' }
],
format: ['trim', 'capitalize']
},
email: {
rules: [
{ validator: 'required', message: '邮箱是必填的' },
{ validator: 'email', message: '邮箱格式不正确' }
],
format: ['trim', 'lowercase']
},
phone: {
rules: [
{ validator: 'phone', message: '电话号码格式不正确' }
],
format: 'phone'
},
age: {
rules: [
{ validator: 'number', message: '年龄必须是数字' },
{ validator: 'range', params: [0, 150], message: '年龄必须在0-150之间' }
]
},
bio: {
rules: [
{ validator: 'maxLength', params: [200], message: '简介不能超过200字符' }
],
format: 'trim'
}
};
const validator = new DataValidator(userSchema);
const user = validator.createProxy();
try {
// 设置用户数据
user.name = ' john doe '; // 会被格式化为 "John doe"
user.email = ' [email protected] '; // 会被格式化为 "[email protected]"
user.phone = '13812345678'; // 会被格式化为 "138-1234-5678"
user.age = 25;
user.bio = ' 一个热爱编程的开发者 '; // 会被trim
console.log('用户数据:', user);
console.log('验证状态:', user.$isValid);
console.log('错误信息:', user.$errors);
// 尝试设置无效数据
try {
user.email = 'invalid-email';
} catch (error) {
console.log('设置无效邮箱被阻止');
}
console.log('最终错误:', user.$errors);
} catch (error) {
console.error('数据校验错误:', error.message);
}
console.log();
2. 高级数据模型 Proxy:
// 高级数据模型系统
class SmartModel {
constructor(schema = {}, options = {}) {
this.schema = schema;
this.options = {
strict: false,
autoSave: false,
history: false,
computed: true,
...options
};
this.data = {};
this.computed = new Map();
this.watchers = new Map();
this.history = [];
this.changeListeners = [];
this.validationErrors = new Map();
// 设置计算属性
this.setupComputedProperties();
}
setupComputedProperties() {
for (const [key, config] of Object.entries(this.schema)) {
if (config.computed) {
this.computed.set(key, config.computed);
}
if (config.watch) {
this.watchers.set(key, config.watch);
}
}
}
// 类型转换和格式化
transformValue(key, value, config) {
let result = value;
// 类型转换
if (config.type) {
switch (config.type) {
case 'string':
result = String(result);
break;
case 'number':
result = Number(result);
break;
case 'boolean':
result = Boolean(result);
break;
case 'date':
result = new Date(result);
break;
case 'array':
result = Array.isArray(result) ? result : [result];
break;
case 'object':
result = typeof result === 'object' && result !== null ? result : {};
break;
}
}
// 自定义转换器
if (config.transform && typeof config.transform === 'function') {
result = config.transform(result, key);
}
// 格式化器
if (config.formatters) {
const formatters = Array.isArray(config.formatters) ?
config.formatters : [config.formatters];
for (const formatter of formatters) {
if (typeof formatter === 'function') {
result = formatter(result);
} else if (typeof formatter === 'string') {
result = this.applyBuiltInFormatter(formatter, result);
}
}
}
return result;
}
applyBuiltInFormatter(formatter, value) {
const formatters = {
trim: (val) => typeof val === 'string' ? val.trim() : val,
uppercase: (val) => typeof val === 'string' ? val.toUpperCase() : val,
lowercase: (val) => typeof val === 'string' ? val.toLowerCase() : val,
capitalize: (val) => {
if (typeof val !== 'string') return val;
return val.charAt(0).toUpperCase() + val.slice(1);
},
currency: (val) => {
if (typeof val !== 'number') return val;
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
}).format(val);
},
percentage: (val) => {
if (typeof val !== 'number') return val;
return (val * 100).toFixed(2) + '%';
},
dateFormat: (val) => {
if (!(val instanceof Date)) return val;
return val.toLocaleDateString('zh-CN');
}
};
return formatters[formatter] ? formatters[formatter](value) : value;
}
// 数据验证
validate(key, value, config) {
const errors = [];
// 必填验证
if (config.required && (value === undefined || value === null || value === '')) {
errors.push(`${key} is required`);
}
// 类型验证
if (value !== undefined && value !== null && config.type) {
const expectedType = config.type;
const actualType = Array.isArray(value) ? 'array' : typeof value;
if (expectedType === 'date' && !(value instanceof Date)) {
errors.push(`${key} must be a valid date`);
} else if (expectedType !== 'date' && expectedType !== actualType) {
errors.push(`${key} must be of type ${expectedType}`);
}
}
// 自定义验证器
if (config.validators) {
for (const validator of config.validators) {
const result = validator(value, key);
if (result !== true) {
errors.push(typeof result === 'string' ? result : `${key} validation failed`);
}
}
}
// 长度验证
if (config.minLength && value && value.length < config.minLength) {
errors.push(`${key} must be at least ${config.minLength} characters`);
}
if (config.maxLength && value && value.length > config.maxLength) {
errors.push(`${key} cannot exceed ${config.maxLength} characters`);
}
// 数值范围验证
if (typeof value === 'number') {
if (config.min !== undefined && value < config.min) {
errors.push(`${key} must be at least ${config.min}`);
}
if (config.max !== undefined && value > config.max) {
errors.push(`${key} cannot exceed ${config.max}`);
}
}
return errors;
}
// 计算属性值
getComputedValue(key) {
const computedFn = this.computed.get(key);
if (computedFn) {
return computedFn.call(this, this.data);
}
return undefined;
}
// 触发变更事件
triggerChange(key, oldValue, newValue) {
// 记录历史
if (this.options.history) {
this.history.push({
key,
oldValue,
newValue,
timestamp: new Date()
});
}
// 触发监听器
const watcher = this.watchers.get(key);
if (watcher) {
watcher.call(this, newValue, oldValue, key);
}
// 触发全局变更监听器
this.changeListeners.forEach(listener => {
try {
listener({ key, oldValue, newValue, model: this });
} catch (error) {
console.error('Change listener error:', error);
}
});
// 自动保存
if (this.options.autoSave && typeof this.save === 'function') {
this.save();
}
}
// 创建代理
createProxy() {
const model = this;
return new Proxy(this.data, {
set(target, key, value) {
const config = model.schema[key] || {};
const oldValue = target[key];
try {
// 转换和格式化值
let transformedValue = model.transformValue(key, value, config);
// 验证数据
const errors = model.validate(key, transformedValue, config);
if (errors.length > 0) {
model.validationErrors.set(key, errors);
if (model.options.strict) {
throw new Error(`Validation failed for ${key}: ${errors.join(', ')}`);
}
} else {
model.validationErrors.delete(key);
}
// 设置值
target[key] = transformedValue;
// 触发变更事件
if (oldValue !== transformedValue) {
model.triggerChange(key, oldValue, transformedValue);
}
return true;
} catch (error) {
console.error(`Error setting ${key}:`, error);
if (model.options.strict) {
throw error;
}
return false;
}
},
get(target, key) {
// 特殊属性
if (key === '$model') return model;
if (key === '$errors') return Object.fromEntries(model.validationErrors);
if (key === '$isValid') return model.validationErrors.size === 0;
if (key === '$history') return [...model.history];
if (key === '$schema') return model.schema;
// 特殊方法
if (key === '$validate') {
return () => model.validateAll();
}
if (key === '$reset') {
return () => model.reset();
}
if (key === '$toJSON') {
return () => model.toJSON();
}
if (key === '$watch') {
return (callback) => model.addChangeListener(callback);
}
// 计算属性
if (model.computed.has(key)) {
return model.getComputedValue(key);
}
return target[key];
},
has(target, key) {
return key in target ||
model.computed.has(key) ||
['$model', '$errors', '$isValid', '$history', '$schema',
'$validate', '$reset', '$toJSON', '$watch'].includes(key);
},
ownKeys(target) {
return [
...Reflect.ownKeys(target),
...model.computed.keys(),
'$model', '$errors', '$isValid', '$history', '$schema'
];
},
deleteProperty(target, key) {
const config = model.schema[key];
if (config && config.required) {
throw new Error(`Cannot delete required field: ${key}`);
}
const oldValue = target[key];
delete target[key];
model.validationErrors.delete(key);
model.triggerChange(key, oldValue, undefined);
return true;
}
});
}
// 验证所有字段
validateAll() {
const errors = {};
let isValid = true;
for (const [key, config] of Object.entries(this.schema)) {
const value = this.data[key];
const fieldErrors = this.validate(key, value, config);
if (fieldErrors.length > 0) {
errors[key] = fieldErrors;
this.validationErrors.set(key, fieldErrors);
isValid = false;
} else {
this.validationErrors.delete(key);
}
}
return { isValid, errors };
}
// 重置模型
reset() {
for (const key of Object.keys(this.data)) {
delete this.data[key];
}
this.validationErrors.clear();
this.history = [];
}
// 添加变更监听器
addChangeListener(callback) {
this.changeListeners.push(callback);
// 返回取消函数
return () => {
const index = this.changeListeners.indexOf(callback);
if (index > -1) {
this.changeListeners.splice(index, 1);
}
};
}
// 序列化为JSON
toJSON() {
const result = { ...this.data };
// 包含计算属性
for (const key of this.computed.keys()) {
result[key] = this.getComputedValue(key);
}
return result;
}
}
// 使用示例
console.log('=== 高级数据模型演示 ===');
const productSchema = {
name: {
type: 'string',
required: true,
minLength: 2,
maxLength: 50,
formatters: ['trim', 'capitalize'],
validators: [
(value) => value.length > 0 || 'Product name cannot be empty'
]
},
price: {
type: 'number',
required: true,
min: 0,
transform: (value) => parseFloat(value) || 0,
watch: function(newPrice, oldPrice) {
console.log(`价格变更: ${oldPrice} -> ${newPrice}`);
}
},
category: {
type: 'string',
formatters: ['lowercase'],
validators: [
(value) => ['electronics', 'clothing', 'books', 'home'].includes(value) ||
'Invalid category'
]
},
description: {
type: 'string',
formatters: ['trim'],
maxLength: 500
},
inStock: {
type: 'boolean',
transform: (value) => Boolean(value)
},
createdAt: {
type: 'date',
transform: (value) => value instanceof Date ? value : new Date()
},
// 计算属性
displayPrice: {
computed: function(data) {
return data.price ? `¥${data.price.toFixed(2)}` : '价格待定';
}
},
status: {
computed: function(data) {
if (!data.inStock) return '缺货';
if (data.price > 100) return '高价商品';
return '普通商品';
}
},
summary: {
computed: function(data) {
return `${data.name || '未命名'} - ${this.displayPrice} (${this.status})`;
}
}
};
const productModel = new SmartModel(productSchema, {
strict: false,
history: true,
computed: true
});
const product = productModel.createProxy();
// 监听变更
const unwatch = product.$watch((change) => {
console.log(`字段变更: ${change.key} = ${change.newValue}`);
});
try {
// 设置商品数据
product.name = ' iPhone 15 Pro '; // 会被格式化
product.price = '999.99'; // 会被转换为数字
product.category = 'ELECTRONICS'; // 会被转换为小写
product.description = ' 最新款iPhone,性能强劲 '; // 会被trim
product.inStock = 'true'; // 会被转换为boolean
console.log('商品信息:');
console.log('- 名称:', product.name);
console.log('- 价格:', product.price);
console.log('- 分类:', product.category);
console.log('- 库存:', product.inStock);
console.log('- 显示价格:', product.displayPrice);
console.log('- 状态:', product.status);
console.log('- 摘要:', product.summary);
console.log('\n验证状态:', product.$isValid);
console.log('错误信息:', product.$errors);
// 尝试设置无效数据
product.category = 'invalid-category';
console.log('设置无效分类后的错误:', product.$errors);
// 查看历史记录
console.log('\n变更历史:');
product.$history.forEach((change, index) => {
console.log(`${index + 1}. ${change.key}: ${change.oldValue} -> ${change.newValue}`);
});
} catch (error) {
console.error('模型操作错误:', error);
} finally {
unwatch(); // 取消监听
}
console.log();
3. 复杂表单验证 Proxy:
// 复杂表单验证系统
class FormValidator {
constructor() {
this.forms = new Map();
this.globalValidators = new Map();
this.setupGlobalValidators();
}
setupGlobalValidators() {
// 全局验证器
this.globalValidators.set('required', {
validate: (value) => value !== undefined && value !== null && value !== '',
message: '此字段是必填的'
});
this.globalValidators.set('email', {
validate: (value) => !value || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
message: '请输入有效的邮箱地址'
});
this.globalValidators.set('phone', {
validate: (value) => !value || /^1[3-9]\d{9}$/.test(value),
message: '请输入有效的手机号码'
});
this.globalValidators.set('idCard', {
validate: (value) => {
if (!value) return true;
return /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(value);
},
message: '请输入有效的身份证号码'
});
this.globalValidators.set('url', {
validate: (value) => !value || /^https?:\/\/[^\s/$.?#].[^\s]*$/.test(value),
message: '请输入有效的URL地址'
});
this.globalValidators.set('minLength', {
validate: (value, min) => !value || value.length >= min,
message: (min) => `最少需要${min}个字符`
});
this.globalValidators.set('maxLength', {
validate: (value, max) => !value || value.length <= max,
message: (max) => `最多允许${max}个字符`
});
this.globalValidators.set('range', {
validate: (value, min, max) => {
const num = Number(value);
return isNaN(num) || (num >= min && num <= max);
},
message: (min, max) => `值必须在${min}到${max}之间`
});
this.globalValidators.set('match', {
validate: (value, targetField, formData) => {
return !value || value === formData[targetField];
},
message: (targetField) => `必须与${targetField}字段一致`
});
}
// 创建表单验证器
createFormValidator(formSchema, options = {}) {
const formId = options.formId || Math.random().toString(36).substr(2, 9);
const formValidator = {
id: formId,
schema: formSchema,
data: {},
errors: new Map(),
touched: new Set(),
submitted: false,
options: {
validateOnChange: true,
validateOnBlur: true,
showErrorsImmediately: false,
...options
},
// 字段级验证
validateField: (key, value, formData) => {
const fieldSchema = formSchema[key];
if (!fieldSchema) return [];
const errors = [];
const validators = fieldSchema.validators || [];
for (const validatorConfig of validators) {
const { name, params = [], message } = validatorConfig;
const validator = this.globalValidators.get(name);
if (!validator) {
console.warn(`Validator ${name} not found`);
continue;
}
const isValid = validator.validate(value, ...params, formData);
if (!isValid) {
const errorMessage = message ||
(typeof validator.message === 'function' ?
validator.message(...params) : validator.message);
errors.push(errorMessage);
}
}
return errors;
},
// 表单级验证
validateForm: () => {
const allErrors = new Map();
let isValid = true;
for (const key of Object.keys(formSchema)) {
const value = formValidator.data[key];
const fieldErrors = formValidator.validateField(key, value, formValidator.data);
if (fieldErrors.length > 0) {
allErrors.set(key, fieldErrors);
isValid = false;
}
}
formValidator.errors = allErrors;
return { isValid, errors: Object.fromEntries(allErrors) };
},
// 重置表单
reset: () => {
for (const key of Object.keys(formValidator.data)) {
delete formValidator.data[key];
}
formValidator.errors.clear();
formValidator.touched.clear();
formValidator.submitted = false;
},
// 设置字段为已触碰
touch: (key) => {
formValidator.touched.add(key);
},
// 获取表单状态
getState: () => ({
isValid: formValidator.errors.size === 0,
errors: Object.fromEntries(formValidator.errors),
touched: Array.from(formValidator.touched),
submitted: formValidator.submitted,
data: { ...formValidator.data }
})
};
this.forms.set(formId, formValidator);
// 创建代理
const proxy = new Proxy(formValidator.data, {
set(target, key, value) {
const oldValue = target[key];
// 应用格式化器
const fieldSchema = formSchema[key];
let processedValue = value;
if (fieldSchema && fieldSchema.formatters) {
for (const formatter of fieldSchema.formatters) {
if (typeof formatter === 'function') {
processedValue = formatter(processedValue);
} else {
processedValue = applyBuiltInFormatter(formatter, processedValue);
}
}
}
target[key] = processedValue;
// 实时验证
if (formValidator.options.validateOnChange) {
const fieldErrors = formValidator.validateField(key, processedValue, target);
if (fieldErrors.length > 0) {
formValidator.errors.set(key, fieldErrors);
} else {
formValidator.errors.delete(key);
}
}
// 触发变更事件
if (formValidator.options.onChange) {
formValidator.options.onChange(key, processedValue, oldValue);
}
return true;
},
get(target, key) {
// 特殊方法和属性
if (key === '$validate') return formValidator.validateForm;
if (key === '$reset') return formValidator.reset;
if (key === '$touch') return formValidator.touch;
if (key === '$state') return formValidator.getState();
if (key === '$errors') return Object.fromEntries(formValidator.errors);
if (key === '$isValid') return formValidator.errors.size === 0;
if (key === '$touched') return Array.from(formValidator.touched);
if (key === '$submit') {
return (onSubmit) => {
formValidator.submitted = true;
const validation = formValidator.validateForm();
if (validation.isValid && onSubmit) {
return onSubmit(formValidator.data);
} else if (!validation.isValid && formValidator.options.onError) {
formValidator.options.onError(validation.errors);
}
return validation;
};
}
return target[key];
},
has(target, key) {
return key in target ||
['$validate', '$reset', '$touch', '$state', '$errors',
'$isValid', '$touched', '$submit'].includes(key);
}
});
return proxy;
}
}
// 内置格式化器
function applyBuiltInFormatter(formatter, value) {
const formatters = {
trim: (val) => typeof val === 'string' ? val.trim() : val,
uppercase: (val) => typeof val === 'string' ? val.toUpperCase() : val,
lowercase: (val) => typeof val === 'string' ? val.toLowerCase() : val,
removeSpaces: (val) => typeof val === 'string' ? val.replace(/\s+/g, '') : val,
phone: (val) => {
if (typeof val !== 'string') return val;
const digits = val.replace(/\D/g, '');
return digits.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
},
currency: (val) => {
const num = parseFloat(val);
return isNaN(num) ? val : num.toFixed(2);
}
};
return formatters[formatter] ? formatters[formatter](value) : value;
}
// 使用示例
console.log('=== 复杂表单验证演示 ===');
const formValidator = new FormValidator();
// 注册表单模式
const registrationSchema = {
username: {
validators: [
{ name: 'required' },
{ name: 'minLength', params: [3] },
{ name: 'maxLength', params: [20] }
],
formatters: ['trim', 'lowercase']
},
email: {
validators: [
{ name: 'required' },
{ name: 'email' }
],
formatters: ['trim', 'lowercase']
},
password: {
validators: [
{ name: 'required' },
{ name: 'minLength', params: [8], message: '密码至少需要8位字符' }
]
},
confirmPassword: {
validators: [
{ name: 'required' },
{ name: 'match', params: ['password'], message: '两次输入的密码不一致' }
]
},
phone: {
validators: [
{ name: 'phone' }
],
formatters: ['removeSpaces', 'phone']
},
age: {
validators: [
{ name: 'required' },
{ name: 'range', params: [18, 100] }
]
},
website: {
validators: [
{ name: 'url' }
]
}
};
// 创建表单实例
const form = formValidator.createFormValidator(registrationSchema, {
formId: 'registration',
validateOnChange: true,
onChange: (field, newValue, oldValue) => {
console.log(`字段 ${field} 变更: ${oldValue} -> ${newValue}`);
},
onError: (errors) => {
console.log('表单验证失败:', errors);
}
});
try {
// 填写表单数据
console.log('开始填写注册表单...\n');
form.username = ' JohnDoe123 '; // 会被格式化为 "johndoe123"
form.email = ' [email protected] '; // 会被格式化为 "[email protected]"
form.password = 'password123';
form.confirmPassword = 'password123';
form.phone = '138 1234 5678'; // 会被格式化为 "138 1234 5678"
form.age = 25;
form.website = 'https://johndoe.com';
console.log('\n表单数据:');
console.log('- 用户名:', form.username);
console.log('- 邮箱:', form.email);
console.log('- 手机:', form.phone);
console.log('- 年龄:', form.age);
console.log('- 网站:', form.website);
console.log('\n表单状态:', form.$state);
// 提交表单
const submitResult = form.$submit((data) => {
console.log('\n表单提交成功,数据:', data);
return { success: true, message: '注册成功' };
});
console.log('提交结果:', submitResult);
// 测试验证错误
console.log('\n测试验证错误...');
form.email = 'invalid-email';
form.confirmPassword = 'different-password';
console.log('验证错误:', form.$errors);
} catch (error) {
console.error('表单操作错误:', error);
}
console.log();
4. 实际应用场景总结:
// 实际应用场景汇总
class ProxyApplicationSummary {
static demonstrateUseCases() {
console.log('=== Proxy 数据处理应用场景汇总 ===\n');
const useCases = [
{
name: '数据校验',
description: '实时验证用户输入,防止无效数据',
examples: ['表单验证', 'API数据校验', '配置文件验证'],
benefits: ['提高数据质量', '减少错误', '用户体验优化']
},
{
name: '自动格式化',
description: '自动转换和格式化数据格式',
examples: ['电话号码格式化', '货币格式化', '日期格式化'],
benefits: ['数据一致性', '显示标准化', '减少手动处理']
},
{
name: '类型转换',
description: '自动进行数据类型转换',
examples: ['字符串转数字', '日期转换', '布尔值转换'],
benefits: ['类型安全', '开发便利', '错误减少']
},
{
name: '计算属性',
description: '基于其他属性动态计算值',
examples: ['全名计算', '总价计算', '状态派生'],
benefits: ['数据同步', '逻辑集中', '性能优化']
},
{
name: '变更监听',
description: '监听数据变化并响应',
examples: ['UI更新', '数据同步', '日志记录'],
benefits: ['响应式编程', '解耦合', '事件驱动']
},
{
name: '访问控制',
description: '控制对象属性的读写权限',
examples: ['只读属性', '私有字段', '权限控制'],
benefits: ['数据安全', '接口设计', '封装性']
},
{
name: '缓存优化',
description: '缓存计算结果和昂贵操作',
examples: ['计算缓存', '请求缓存', '结果记忆'],
benefits: ['性能提升', '资源节约', '响应加速']
},
{
name: '调试辅助',
description: '增强开发和调试体验',
examples: ['操作日志', '性能监控', '错误追踪'],
benefits: ['开发效率', '问题定位', '质量保证']
}
];
useCases.forEach((useCase, index) => {
console.log(`${index + 1}. ${useCase.name}`);
console.log(` 描述: ${useCase.description}`);
console.log(` 示例: ${useCase.examples.join(', ')}`);
console.log(` 优势: ${useCase.benefits.join(', ')}`);
console.log('');
});
console.log('=== 最佳实践建议 ===');
console.log('✅ 合理使用 - Proxy 有性能开销,避免过度使用');
console.log('✅ 错误处理 - 在trap中妥善处理异常情况');
console.log('✅ 类型安全 - 结合TypeScript提供更好的类型支持');
console.log('✅ 文档说明 - 清晰记录Proxy的行为和限制');
console.log('✅ 测试覆盖 - 充分测试各种边界情况');
console.log('✅ 兼容性考虑 - 注意浏览器兼容性要求');
console.log('\n=== 注意事项 ===');
console.log('⚠️ 性能影响 - Proxy会增加属性访问的开销');
console.log('⚠️ 调试复杂 - 可能会干扰调试器的正常工作');
console.log('⚠️ 兼容性限制 - 不支持IE,需要考虑polyfill');
console.log('⚠️ 序列化问题 - Proxy对象可能无法正确序列化');
console.log('⚠️ 内存泄漏 - 需要注意清理引用,防止内存泄漏');
}
}
ProxyApplicationSummary.demonstrateUseCases();
Proxy 数据校验和格式化总结:
核心优势:
主要应用场景:
实现要点:
最佳实践:
What are the differences between TypeScript and JavaScript?
What are the differences between TypeScript and JavaScript?
答案:
TypeScript 是 JavaScript 的超集,在 JavaScript 基础上增加了静态类型检查系统。主要区别体现在类型系统、编译时检查、开发体验和代码质量保证等方面。
主要区别:
类型系统:
// JavaScript - 动态类型,运行时确定
let name = "张三";
name = 123; // 运行时才发现问题
// TypeScript - 静态类型,编译时检查
let name: string = "张三";
name = 123; // 编译时就会报错
编译过程:
错误检测:
// TypeScript 能在编译时发现错误
function greet(person: { name: string, age: number }) {
return `Hello ${person.name}`;
}
greet({ name: "李四" }); // Error: Property 'age' is missing
开发优势:
What are the basic data types in TypeScript?
What are the basic data types in TypeScript?
答案:
TypeScript 的基本数据类型包括 JavaScript 原有类型和 TypeScript 特有类型,提供了完整的类型约束能力。
基本类型:
原始类型:
// 数字类型
let age: number = 25;
let price: number = 99.99;
// 字符串类型
let name: string = "张三";
let template: string = `Hello, ${name}`;
// 布尔类型
let isActive: boolean = true;
let isCompleted: boolean = false;
空值类型:
// undefined 和 null
let u: undefined = undefined;
let n: null = null;
// void - 通常用于函数无返回值
function logMessage(): void {
console.log("Hello");
}
TypeScript 特有类型:
// any - 任意类型,失去类型检查
let anything: any = 42;
anything = "hello";
anything = true;
// unknown - 安全的 any
let userInput: unknown;
if (typeof userInput === "string") {
console.log(userInput.toUpperCase()); // 需要类型检查
}
// never - 永不存在的值的类型
function error(): never {
throw new Error("Something went wrong");
}
复合类型:
// 数组
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ["a", "b", "c"];
// 元组
let tuple: [string, number] = ["hello", 42];
// 对象
let user: { name: string; age: number } = {
name: "李四",
age: 30
};
What are type annotations? How to use them?
What are type annotations? How to use them?
答案:
类型注解是 TypeScript 中显式指定变量、函数参数、返回值等的类型的语法。通过在变量名后使用冒号 : 加类型名的方式来声明类型。
基本语法:
变量类型注解:
// 基本变量类型注解
let userName: string = "张三";
let userAge: number = 25;
let isActive: boolean = true;
// 可以省略初始值
let email: string;
email = "[email protected]";
函数类型注解:
// 参数和返回值类型注解
function add(a: number, b: number): number {
return a + b;
}
// 箭头函数类型注解
const multiply = (x: number, y: number): number => x * y;
// 可选参数
function greet(name: string, title?: string): string {
return title ? `Hello, ${title} ${name}` : `Hello, ${name}`;
}
对象类型注解:
// 对象类型注解
let user: {
id: number;
name: string;
email?: string; // 可选属性
} = {
id: 1,
name: "李四"
};
// 函数作为对象属性
let calculator: {
add: (a: number, b: number) => number;
subtract: (a: number, b: number) => number;
} = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
使用场景:
What is type inference?
What is type inference?
答案:
类型推断是 TypeScript 编译器根据代码上下文自动推断变量类型的能力,无需显式类型注解就能确定类型。这让代码更简洁的同时保持类型安全。
推断规则:
初始化推断:
// TypeScript 自动推断类型
let name = "张三"; // 推断为 string
let age = 25; // 推断为 number
let isActive = true; // 推断为 boolean
// 等价于显式注解
let name: string = "张三";
let age: number = 25;
let isActive: boolean = true;
函数返回值推断:
// 返回值类型自动推断
function add(a: number, b: number) {
return a + b; // 推断返回值为 number
}
function getUser() {
return {
id: 1,
name: "李四",
age: 30
}; // 推断返回值为 { id: number; name: string; age: number }
}
数组类型推断:
// 数组元素类型推断
let numbers = [1, 2, 3]; // 推断为 number[]
let mixed = [1, "hello", true]; // 推断为 (string | number | boolean)[]
let users = [
{ name: "张三", age: 25 },
{ name: "李四", age: 30 }
]; // 推断为 { name: string; age: number }[]
上下文推断:
// 基于上下文推断参数类型
const numbers = [1, 2, 3];
numbers.map(num => num.toString()); // num 推断为 number
// 事件处理中的推断
button.addEventListener('click', (event) => {
// event 推断为 MouseEvent
console.log(event.clientX);
});
推断优势:
What is an interface? How to define and use it?
What is an interface? How to define and use it?
答案:
接口(interface)是 TypeScript 中定义对象结构的方式,用于约束对象的形状。它只描述类型结构,不包含实现,是实现类型契约和代码规范的重要工具。
基本定义与使用:
基础接口:
// 定义用户接口
interface User {
id: number;
name: string;
email: string;
}
// 使用接口约束对象
const user: User = {
id: 1,
name: "张三",
email: "[email protected]"
};
可选属性和只读属性:
interface Product {
readonly id: number; // 只读属性
name: string;
price: number;
description?: string; // 可选属性
}
const product: Product = {
id: 1,
name: "笔记本电脑",
price: 5999
// description 可选,可以不提供
};
// product.id = 2; // 错误:无法修改只读属性
函数接口:
// 定义函数类型接口
interface Calculator {
(a: number, b: number): number;
}
const add: Calculator = (x, y) => x + y;
const multiply: Calculator = (x, y) => x * y;
// 包含方法的接口
interface MathOperations {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
}
接口继承:
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
const myDog: Dog = {
name: "旺财",
age: 3,
breed: "金毛",
bark() {
console.log("汪汪汪!");
}
};
应用场景:
How to use optional chaining operator (?.) and nullish coalescing operator (??) in TypeScript?
How to use optional chaining operator (?.) and nullish coalescing operator (??) in TypeScript?
答案:
可选链操作符(?.)和空值合并操作符(??)是 TypeScript 提供的安全访问语法,用于处理可能为 null 或 undefined 的值,避免运行时错误。
可选链操作符(?.):
对象属性访问:
interface User {
name: string;
profile?: {
avatar?: string;
address?: {
city: string;
street?: string;
};
};
}
const user: User = { name: "张三" };
// 传统方式 - 容易出错
// const avatar = user.profile.avatar; // 运行时错误
// 使用可选链 - 安全访问
const avatar = user.profile?.avatar; // undefined
const city = user.profile?.address?.city; // undefined
const street = user.profile?.address?.street; // undefined
方法调用:
interface Api {
getData?: () => Promise<any>;
utils?: {
format?: (data: any) => string;
};
}
const api: Api = {};
// 安全的方法调用
const data = await api.getData?.(); // undefined
const formatted = api.utils?.format?.(data); // undefined
数组访问:
const users: User[] | undefined = getUsers();
// 安全的数组访问
const firstUser = users?.[0];
const firstName = users?.[0]?.name;
空值合并操作符(??):
基本用法:
// 只有当左侧为 null 或 undefined 时才使用右侧值
const username = user.name ?? "匿名用户";
const port = process.env.PORT ?? 3000;
const config = savedConfig ?? defaultConfig;
// 与 || 的区别
const count1 = 0 || 10; // 10 (0 被认为是假值)
const count2 = 0 ?? 10; // 0 (0 不是 null/undefined)
结合使用:
// 可选链 + 空值合并的组合
const userCity = user.profile?.address?.city ?? "未知城市";
const avatar = user.profile?.avatar ?? "/default-avatar.png";
// 复杂对象的安全访问和默认值
const theme = config?.ui?.theme ?? "light";
const apiUrl = settings?.api?.baseUrl ?? "https://api.example.com";
实际应用场景:
What is enum in TypeScript?
What is enum in TypeScript?
答案:
枚举(enum)是 TypeScript 提供的一种数据类型,用于定义一组命名的常量。它让代码更具可读性和维护性,常用于表示状态、类型、配置等固定的值集合。
枚举类型:
数字枚举:
// 默认从 0 开始递增
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 自定义起始值
enum Status {
Pending = 1, // 1
Processing, // 2
Completed, // 3
Failed // 4
}
// 使用枚举
const currentDirection = Direction.Up;
const orderStatus = Status.Processing;
console.log(currentDirection); // 0
console.log(Status.Completed); // 3
字符串枚举:
enum Color {
Red = "red",
Green = "green",
Blue = "blue"
}
enum ApiEndpoint {
Users = "/api/users",
Products = "/api/products",
Orders = "/api/orders"
}
// 使用字符串枚举
const userColor = Color.Red; // "red"
const endpoint = ApiEndpoint.Users; // "/api/users"
异构枚举(混合类型):
enum Mixed {
No = 0,
Yes = "YES"
}
// 不推荐使用,容易混淆
常量枚举(const enum):
const enum Theme {
Light = "light",
Dark = "dark"
}
// 编译时会被内联,不会生成额外代码
const currentTheme = Theme.Dark; // 编译后直接是 "dark"
实际应用:
// HTTP 状态码
enum HttpStatus {
OK = 200,
NotFound = 404,
InternalServerError = 500
}
// 用户角色
enum UserRole {
Admin = "admin",
User = "user",
Guest = "guest"
}
// 在函数中使用
function handleResponse(status: HttpStatus) {
switch (status) {
case HttpStatus.OK:
return "请求成功";
case HttpStatus.NotFound:
return "资源未找到";
default:
return "未知错误";
}
}
// 检查用户权限
function checkPermission(role: UserRole): boolean {
return role === UserRole.Admin;
}
使用场景:
How does TypeScript define arrays and tuples?
How does TypeScript define arrays and tuples?
答案:
TypeScript 提供了数组(Array)和元组(Tuple)两种集合类型。数组是相同类型元素的集合,元组是固定长度和类型的有序列表。
数组定义:
基础数组类型:
// 两种声明方式
let numbers1: number[] = [1, 2, 3, 4, 5];
let numbers2: Array<number> = [1, 2, 3, 4, 5];
let names: string[] = ["张三", "李四", "王五"];
let flags: boolean[] = [true, false, true];
// 混合类型数组(联合类型)
let mixed: (string | number)[] = [1, "hello", 2, "world"];
对象数组:
interface User {
id: number;
name: string;
}
let users: User[] = [
{ id: 1, name: "张三" },
{ id: 2, name: "李四" }
];
// 或使用 Array<T> 语法
let products: Array<{ name: string; price: number }> = [
{ name: "笔记本", price: 5999 },
{ name: "手机", price: 3999 }
];
元组定义:
基础元组:
// 固定长度和类型的数组
let point: [number, number] = [10, 20];
let user: [string, number, boolean] = ["张三", 25, true];
// 访问元组元素
console.log(point[0]); // 10 (number)
console.log(user[1]); // 25 (number)
// 解构赋值
const [x, y] = point;
const [name, age, isActive] = user;
可选元素和剩余元素:
// 可选元素
let optional: [string, number?] = ["hello"]; // 第二个元素可选
let optional2: [string, number?] = ["hello", 42];
// 剩余元素
let rest: [string, ...number[]] = ["prefix", 1, 2, 3, 4];
// 命名元组(TypeScript 4.0+)
type Point3D = [x: number, y: number, z: number];
let position: Point3D = [10, 20, 30];
只读元组:
// 只读元组
let readonlyTuple: readonly [number, string] = [1, "hello"];
// readonlyTuple[0] = 2; // 错误:无法修改只读元组
// 只读数组
let readonlyArray: readonly number[] = [1, 2, 3];
// readonlyArray.push(4); // 错误:只读数组不能修改
实际应用场景:
// 坐标点
type Coordinate = [number, number];
const origin: Coordinate = [0, 0];
// 函数返回多个值
function getNameAndAge(): [string, number] {
return ["张三", 25];
}
const [userName, userAge] = getNameAndAge();
// 状态管理(类似 React useState)
type State<T> = [T, (value: T) => void];
function useState<T>(initial: T): State<T> {
let state = initial;
const setState = (value: T) => { state = value; };
return [state, setState];
}
// 键值对
type KeyValue = [string, any];
const config: KeyValue[] = [
["theme", "dark"],
["language", "zh-CN"],
["debug", true]
];
数组 vs 元组的区别:
What are Union Types and Intersection Types?
What are Union Types and Intersection Types?
答案:
联合类型和交叉类型是 TypeScript 中组合多个类型的方式。联合类型表示"或"的关系(A 或 B),交叉类型表示"和"的关系(A 和 B)。
联合类型(Union Types):
基础联合类型:
// 使用 | 操作符定义联合类型
let value: string | number;
value = "hello"; // 有效
value = 42; // 有效
// value = true; // 错误:boolean 不在联合类型中
// 函数参数的联合类型
function formatValue(input: string | number): string {
if (typeof input === "string") {
return input.toUpperCase();
}
return input.toString();
}
console.log(formatValue("hello")); // "HELLO"
console.log(formatValue(123)); // "123"
对象联合类型:
interface Cat {
type: "cat";
meow(): void;
}
interface Dog {
type: "dog";
bark(): void;
}
type Pet = Cat | Dog;
function handlePet(pet: Pet) {
// 使用判别联合类型
switch (pet.type) {
case "cat":
pet.meow(); // TypeScript 知道这是 Cat
break;
case "dog":
pet.bark(); // TypeScript 知道这是 Dog
break;
}
}
字面量联合类型:
// 字符串字面量联合
type Theme = "light" | "dark" | "auto";
type Size = "small" | "medium" | "large";
let currentTheme: Theme = "dark"; // 有效
// let invalidTheme: Theme = "blue"; // 错误
// 数字字面量联合
type HttpStatus = 200 | 404 | 500;
function handleStatus(status: HttpStatus) {
switch (status) {
case 200:
return "成功";
case 404:
return "未找到";
case 500:
return "服务器错误";
}
}
交叉类型(Intersection Types):
基础交叉类型:
// 使用 & 操作符定义交叉类型
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: string;
department: string;
}
// 交叉类型包含两个接口的所有属性
type PersonEmployee = Person & Employee;
const worker: PersonEmployee = {
name: "张三",
age: 30,
employeeId: "EMP001",
department: "开发部"
};
Mixin 模式:
interface Timestamped {
timestamp: Date;
}
interface Tagged {
tags: string[];
}
// 组合多个能力
type BlogPost = {
title: string;
content: string;
} & Timestamped & Tagged;
const post: BlogPost = {
title: "TypeScript 学习",
content: "今天学习了联合类型和交叉类型",
timestamp: new Date(),
tags: ["TypeScript", "编程"]
};
函数类型交叉:
type Logger = {
log: (message: string) => void;
};
type Formatter = {
format: (data: any) => string;
};
type LoggerFormatter = Logger & Formatter;
const utility: LoggerFormatter = {
log: (message: string) => console.log(message),
format: (data: any) => JSON.stringify(data)
};
实际应用场景:
// API 响应类型
type ApiResponse<T> =
| { success: true; data: T }
| { success: false; error: string };
// 事件类型
type MouseEvent = {
type: "click" | "hover";
target: Element;
} & ({ type: "click"; button: number } | { type: "hover" });
// 配置对象
type Config = BaseConfig & (DevConfig | ProdConfig);
interface BaseConfig {
appName: string;
version: string;
}
interface DevConfig {
env: "development";
debug: true;
}
interface ProdConfig {
env: "production";
debug: false;
apiUrl: string;
}
使用区别:
What is type alias in TypeScript?
What is type alias in TypeScript?
- 考察点:类型别名概念与应用场景。
答案:
类型别名(type alias)是使用 type 关键字为现有类型创建新名称的方式。它不创建新类型,而是为类型提供一个更具描述性或更简洁的名称。
基本语法:
基础类型别名:
// 为基本类型创建别名
type ID = string | number;
type Name = string;
type Age = number;
// 使用类型别名
let userId: ID = "user_123";
let userName: Name = "张三";
let userAge: Age = 25;
// 函数参数使用别名
function getUser(id: ID): { name: Name; age: Age } {
return { name: "张三", age: 25 };
}
对象类型别名:
// 复杂对象类型别名
type User = {
id: number;
name: string;
email: string;
profile?: {
avatar: string;
bio: string;
};
};
type Product = {
id: number;
name: string;
price: number;
category: string;
};
// 使用类型别名
const user: User = {
id: 1,
name: "李四",
email: "[email protected]"
};
函数类型别名:
// 函数类型别名
type EventHandler = (event: Event) => void;
type Validator<T> = (value: T) => boolean;
type Mapper<T, R> = (input: T) => R;
// 使用函数类型别名
const clickHandler: EventHandler = (e) => console.log("clicked");
const isValidEmail: Validator<string> = (email) => email.includes("@");
const toString: Mapper<number, string> = (num) => num.toString();
泛型类型别名:
// 泛型类型别名
type ApiResponse<T> = {
success: boolean;
data: T;
message?: string;
};
type Optional<T> = T | undefined;
type Nullable<T> = T | null;
// 使用泛型别名
type UserResponse = ApiResponse<User>;
type OptionalString = Optional<string>;
const response: UserResponse = {
success: true,
data: { id: 1, name: "王五", email: "[email protected]" }
};
type vs interface 的区别:
语法差异:
// type - 使用 = 赋值
type Point = {
x: number;
y: number;
};
// interface - 直接定义
interface Point2 {
x: number;
y: number;
}
扩展方式:
// type 使用交叉类型扩展
type Animal = {
name: string;
};
type Dog = Animal & {
breed: string;
};
// interface 使用 extends 扩展
interface Animal2 {
name: string;
}
interface Dog2 extends Animal2 {
breed: string;
}
能力差异:
// type 可以表示联合类型、基本类型等
type Status = "loading" | "success" | "error";
type StringOrNumber = string | number;
// interface 不能表示联合类型或基本类型
// interface Status = "loading" | "success" | "error"; // 错误
实际应用场景:
// 状态管理
type AppState = {
user: User | null;
theme: "light" | "dark";
loading: boolean;
};
// 事件系统
type EventType = "click" | "scroll" | "resize";
type EventListener<T = any> = (data: T) => void;
// API 类型定义
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type RequestConfig = {
method: HttpMethod;
url: string;
headers?: Record<string, string>;
body?: any;
};
// 工具类型组合
type PartialUser = Partial<User>; // 所有属性可选
type RequiredUser = Required<User>; // 所有属性必需
type UserKeys = keyof User; // 获取所有属性名
使用建议:
interface 定义对象结构type 定义联合类型、基本类型别名type 进行复杂类型组合How to define function types in TypeScript?
How to define function types in TypeScript?
- 考察点:函数类型声明方式与应用。
答案:
TypeScript 提供了多种方式来定义函数类型,包括函数声明、函数表达式、箭头函数和函数类型别名等,可以精确控制参数类型和返回值类型。
函数类型定义方式:
函数声明:
// 基础函数声明
function add(a: number, b: number): number {
return a + b;
}
// 可选参数
function greet(name: string, title?: string): string {
return title ? `Hello, ${title} ${name}` : `Hello, ${name}`;
}
// 默认参数
function createUser(name: string, age: number = 18): User {
return { id: Date.now(), name, age };
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
函数表达式:
// 完整的函数类型注解
const multiply: (a: number, b: number) => number = function(a, b) {
return a * b;
};
// 箭头函数
const divide: (a: number, b: number) => number = (a, b) => {
if (b === 0) throw new Error("Division by zero");
return a / b;
};
// 简化写法(类型推断)
const subtract = (a: number, b: number): number => a - b;
函数类型别名:
// 定义函数类型别名
type Calculator = (a: number, b: number) => number;
type Predicate<T> = (item: T) => boolean;
type EventHandler = (event: Event) => void;
// 使用类型别名
const add: Calculator = (a, b) => a + b;
const isEven: Predicate<number> = (num) => num % 2 === 0;
const handleClick: EventHandler = (e) => console.log("clicked");
接口定义函数类型:
// 使用接口定义函数类型
interface SearchFunction {
(source: string, subString: string): boolean;
}
interface MathOperations {
add: (a: number, b: number) => number;
subtract: (a: number, b: number) => number;
multiply?: (a: number, b: number) => number; // 可选方法
}
// 实现接口
const search: SearchFunction = (src, sub) => src.indexOf(sub) > -1;
const math: MathOperations = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
高级函数类型:
泛型函数:
// 泛型函数定义
function identity<T>(arg: T): T {
return arg;
}
// 泛型函数类型别名
type GenericFunction<T, R> = (arg: T) => R;
type ArrayMapper<T, R> = (arr: T[]) => R[];
// 使用泛型函数
const stringIdentity = identity<string>("hello");
const numberIdentity = identity<number>(42);
// 类型推断
const autoIdentity = identity("world"); // 推断为 string
函数重载:
// 函数重载签名
function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;
// 实现签名
function format(value: string | number | boolean): string {
return String(value);
}
// 使用时有准确的类型检查
const str1 = format("hello"); // string
const str2 = format(42); // string
const str3 = format(true); // string
高阶函数:
// 接受函数作为参数
function withLogging<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
return (...args) => {
console.log("Function called with:", args);
const result = fn(...args);
console.log("Function returned:", result);
return result;
};
}
// 返回函数的函数
function createValidator<T>(
validate: (value: T) => boolean
): (value: T) => { isValid: boolean; value: T } {
return (value: T) => ({
isValid: validate(value),
value
});
}
// 使用高阶函数
const loggedAdd = withLogging(add);
const emailValidator = createValidator<string>(email => email.includes("@"));
实际应用场景:
// 异步函数
async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// 事件处理器
type ButtonClickHandler = (event: MouseEvent) => void;
// 中间件模式
type Middleware<T> = (
data: T,
next: (data: T) => void
) => void;
// 回调函数
function processData<T, R>(
data: T[],
processor: (item: T) => R,
onComplete: (results: R[]) => void
): void {
const results = data.map(processor);
onComplete(results);
}
最佳实践:
any 类型How does TypeScript handle JavaScript files? What is the role of allowJs configuration?
How does TypeScript handle JavaScript files? What is the role of allowJs configuration?
- 考察点:TS 与 JS 混合开发理解。
答案:
TypeScript 可以与现有的 JavaScript 代码无缝集成,allowJs 配置选项控制是否允许在 TypeScript 项目中包含和编译 JavaScript 文件,支持渐进式迁移。
allowJs 配置详解:
基本配置:
// tsconfig.json
{
"compilerOptions": {
"allowJs": true, // 允许编译 JavaScript 文件
"checkJs": false, // 是否检查 JavaScript 文件中的错误
"outDir": "./dist", // 输出目录
"rootDir": "./src" // 源代码目录
},
"include": [
"src/**/*.ts",
"src/**/*.js" // 包含 JavaScript 文件
]
}
JavaScript 类型检查:
// 启用 JavaScript 类型检查
{
"compilerOptions": {
"allowJs": true,
"checkJs": true, // 对 JS 文件进行类型检查
"noImplicitAny": false, // JS 文件中允许隐式 any
"strict": false // 对 JS 文件不启用严格模式
}
}
JavaScript 文件处理方式:
JSDoc 类型注解:
// user.js - 使用 JSDoc 提供类型信息
/**
* @typedef {Object} User
* @property {number} id - 用户ID
* @property {string} name - 用户名
* @property {string} email - 邮箱
*/
/**
* 获取用户信息
* @param {number} id - 用户ID
* @returns {Promise<User>} 用户信息
*/
async function getUser(id) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
/**
* @param {User} user - 用户对象
* @param {string} [title] - 可选标题
* @returns {string}
*/
function formatUser(user, title) {
return title ? `${title}: ${user.name}` : user.name;
}
module.exports = { getUser, formatUser };
TypeScript 中导入 JavaScript:
// types.ts - 定义类型
export interface User {
id: number;
name: string;
email: string;
}
// main.ts - 导入 JavaScript 模块
import { getUser, formatUser } from './user.js';
import type { User } from './types';
async function main() {
const user: User = await getUser(1);
const formatted: string = formatUser(user, "管理员");
console.log(formatted);
}
声明文件支持:
// user.d.ts - 为 JavaScript 文件提供类型声明
export interface User {
id: number;
name: string;
email: string;
}
export function getUser(id: number): Promise<User>;
export function formatUser(user: User, title?: string): string;
渐进式迁移策略:
项目结构:
src/
├── legacy/ # 现有 JavaScript 代码
│ ├── utils.js
│ ├── api.js
│ └── components/
├── new/ # 新的 TypeScript 代码
│ ├── services.ts
│ ├── types.ts
│ └── components/
└── types/ # 类型声明文件
├── legacy.d.ts
└── global.d.ts
迁移配置:
// tsconfig.json - 渐进式迁移配置
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": false, // 对 JS 文件宽松检查
"noImplicitReturns": false,
"noImplicitAny": false
},
"include": ["src/**/*"],
"exclude": [
"src/legacy/old-components/**/*.js" // 暂时排除难以迁移的文件
]
}
文件级别控制:
// legacy-module.js
// @ts-check // 启用类型检查
// @ts-nocheck // 跳过类型检查
// @ts-ignore // 忽略下一行错误
/**
* @param {string} message
*/
function log(message) {
// @ts-ignore - 故意使用错误的类型用于演示
console.log(message.toUpperCase());
}
实际应用场景:
现有项目迁移:
// 步骤1:启用 allowJs
// 步骤2:逐个文件重命名 .js -> .ts
// 步骤3:添加类型注解
// 步骤4:解决类型错误
// 原 JavaScript 文件
// utils.js -> utils.ts
export function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// TypeScript 版本
export function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return function(...args: Parameters<T>) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
第三方库集成:
// 对于没有类型定义的 JavaScript 库
declare module 'legacy-library' {
export function doSomething(param: string): number;
export const config: {
version: string;
debug: boolean;
};
}
// 使用
import { doSomething, config } from 'legacy-library';
const result: number = doSomething("test");
console.log(config.version);
配置建议:
allowJs: falseallowJs: true,配合 checkJs 逐步检查.d.ts 声明文件描述 JavaScript 模块What is Type Assertion? What's the difference from type conversion?
What is Type Assertion? What’s the difference from type conversion?
答案:
类型断言是 TypeScript 提供的一种告诉编译器"相信我,我知道这是什么类型"的语法。它只在编译时起作用,不进行实际的类型转换或运行时检查。
类型断言语法:
两种语法形式:
// 尖括号语法(在 JSX 中不推荐)
let someValue: unknown = "hello world";
let strLength1: number = (<string>someValue).length;
// as 语法(推荐)
let strLength2: number = (someValue as string).length;
// DOM 元素断言
const inputElement = document.getElementById("username") as HTMLInputElement;
inputElement.value = "张三"; // 现在可以安全访问 value 属性
常见使用场景:
// API 响应处理
interface User {
id: number;
name: string;
email: string;
}
async function fetchUser(): Promise<User> {
const response = await fetch("/api/user");
const data = await response.json();
// 类型断言 - 假设 API 返回正确格式
return data as User;
}
// 联合类型缩窄
function processInput(input: string | number) {
if (typeof input === "string") {
// 这里不需要断言,TypeScript 已经知道是 string
return input.toUpperCase();
}
// 明确断言为 number(实际上这里也不需要)
return (input as number) * 2;
}
双重断言:
// 有时需要先断言为 unknown 再断言为目标类型
const input = "123";
// 直接断言可能报错
// const num = input as number; // Error: 不能直接从 string 断言为 number
// 双重断言(不推荐,但有时必要)
const num = (input as unknown) as number;
// 更好的方式是实际转换
const actualNum = parseInt(input, 10);
类型断言 vs 类型转换:
类型断言(编译时):
// 类型断言 - 仅告诉 TypeScript 编译器类型
let value: unknown = "42";
let num1 = value as number;
console.log(typeof num1); // "string" - 运行时仍是字符串
console.log(num1.toFixed(2)); // 运行时错误!
// 类型断言不会改变运行时的值
interface Cat {
meow(): void;
}
let pet: any = { bark: () => console.log("woof") };
let cat = pet as Cat;
// cat.meow(); // 运行时错误 - 对象实际上没有 meow 方法
类型转换(运行时):
// 实际的类型转换 - 改变运行时的值
let str = "42";
let num2 = Number(str); // 实际转换
let num3 = parseInt(str, 10); // 实际转换
let num4 = +str; // 实际转换
console.log(typeof num2); // "number" - 运行时确实是数字
console.log(num2.toFixed(2)); // "42.00" - 正常工作
// 对象转换
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
bark() { console.log("woof"); }
}
let animal: Animal = new Dog("旺财");
// 实际检查类型后转换
if (animal instanceof Dog) {
animal.bark(); // 安全,因为进行了运行时检查
}
安全的类型断言实践:
类型守卫结合断言:
// 自定义类型守卫
function isUser(obj: any): obj is User {
return obj &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string';
}
// 安全使用
async function safeGetUser(): Promise<User | null> {
const response = await fetch("/api/user");
const data = await response.json();
if (isUser(data)) {
return data; // TypeScript 知道这是 User 类型
}
return null; // 数据格式不正确
}
非空断言(!):
// 非空断言操作符
function processElement(id: string) {
// 明确知道元素存在时使用
const element = document.getElementById(id)!; // 断言不为 null
element.style.display = "none";
}
// 可选链更安全
function safeProcessElement(id: string) {
const element = document.getElementById(id);
if (element) {
element.style.display = "none";
}
}
使用建议:
as 语法而不是尖括号语法How to use optional properties and readonly properties in TypeScript?
How to use optional properties and readonly properties in TypeScript?
答案:
可选属性(Optional Properties)和只读属性(Readonly Properties)是 TypeScript 中控制对象属性访问和修改的重要特性,提供了更精确的类型约束和数据保护。
可选属性(?):
基础语法:
interface User {
id: number; // 必需属性
name: string; // 必需属性
email?: string; // 可选属性
phone?: string; // 可选属性
profile?: { // 嵌套可选属性
avatar?: string;
bio?: string;
};
}
// 有效的对象创建
const user1: User = {
id: 1,
name: "张三"
// email 和 phone 可以省略
};
const user2: User = {
id: 2,
name: "李四",
email: "[email protected]",
profile: {
avatar: "/avatar.jpg"
// bio 可以省略
}
};
函数参数中的可选属性:
// 配置对象模式
interface ApiConfig {
baseUrl: string;
timeout?: number;
retries?: number;
headers?: Record<string, string>;
}
function createApiClient(config: ApiConfig) {
const {
baseUrl,
timeout = 5000, // 默认值
retries = 3, // 默认值
headers = {} // 默认值
} = config;
return {
baseUrl,
timeout,
retries,
headers
};
}
// 使用时只需提供必要属性
const api = createApiClient({
baseUrl: "https://api.example.com"
});
只读属性(readonly):
接口中的只读属性:
interface Point {
readonly x: number;
readonly y: number;
}
interface Config {
readonly apiKey: string;
readonly debug: boolean;
features?: readonly string[]; // 只读数组
}
const point: Point = { x: 10, y: 20 };
// point.x = 30; // 错误:无法修改只读属性
const config: Config = {
apiKey: "secret-key",
debug: true,
features: ["auth", "cache"]
};
// config.apiKey = "new-key"; // 错误:无法修改只读属性
类中的只读属性:
class Circle {
readonly pi: number = 3.14159;
readonly radius: number;
constructor(radius: number) {
this.radius = radius; // 构造函数中可以赋值
}
get area(): number {
return this.pi * this.radius ** 2;
}
// setRadius(newRadius: number) {
// this.radius = newRadius; // 错误:无法修改只读属性
// }
}
const circle = new Circle(5);
console.log(circle.area);
// circle.pi = 3.14; // 错误:无法修改只读属性
组合使用:
可选且只读的属性:
interface UserPreferences {
readonly id: number;
readonly userId: number;
theme?: readonly ("light" | "dark");
readonly createdAt?: Date; // 可选的只读属性
settings?: {
readonly notifications: boolean;
language?: string;
};
}
const preferences: UserPreferences = {
id: 1,
userId: 123,
theme: "dark",
settings: {
notifications: true,
language: "zh-CN"
}
};
// preferences.id = 2; // 错误:只读
// preferences.theme = "light"; // 错误:只读
// preferences.settings.notifications = false; // 错误:只读
preferences.settings.language = "en"; // 正确:language 不是只读
工具类型的应用:
// 使用内置工具类型
interface Product {
id: number;
name: string;
price: number;
description: string;
}
// 全部属性可选
type PartialProduct = Partial<Product>;
/*
{
id?: number;
name?: string;
price?: number;
description?: string;
}
*/
// 全部属性只读
type ReadonlyProduct = Readonly<Product>;
/*
{
readonly id: number;
readonly name: string;
readonly price: number;
readonly description: string;
}
*/
// 选择特定属性为可选
type ProductUpdate = Partial<Pick<Product, 'name' | 'price' | 'description'>> &
Pick<Product, 'id'>;
/*
{
id: number;
name?: string;
price?: number;
description?: string;
}
*/
实际应用场景:
状态管理:
interface AppState {
readonly user: User | null;
readonly isLoading: boolean;
readonly error?: string;
preferences?: UserPreferences;
}
// 状态更新函数
function updateState(
currentState: AppState,
updates: Partial<AppState>
): AppState {
return {
...currentState,
...updates
};
}
API 响应类型:
interface ApiResponse<T> {
readonly success: boolean;
readonly data?: T;
readonly error?: {
readonly code: string;
readonly message: string;
};
readonly timestamp: Date;
}
interface CreateUserRequest {
name: string;
email: string;
phone?: string; // 可选字段
}
interface UserResponse {
readonly id: number; // 服务器生成,只读
readonly name: string;
readonly email: string;
readonly phone?: string;
readonly createdAt: Date; // 服务器时间戳,只读
}
配置对象:
interface DatabaseConfig {
readonly host: string;
readonly port: number;
readonly database: string;
readonly ssl?: boolean;
readonly timeout?: number;
readonly pool?: {
readonly min: number;
readonly max: number;
readonly idleTimeout?: number;
};
}
// 运行时不可修改的配置
const dbConfig: DatabaseConfig = {
host: "localhost",
port: 5432,
database: "myapp",
ssl: true,
pool: {
min: 2,
max: 10
}
};
最佳实践:
readonly? 可选标记What are Generics? How to use generic functions and generic interfaces?
What are Generics? How to use generic functions and generic interfaces?
答案:
泛型(Generics)是 TypeScript 中实现代码重用和类型安全的重要特性,允许在定义函数、接口或类时使用类型参数,在使用时再指定具体类型。
泛型基础概念:
泛型函数:
// 基础泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用时指定类型
const numberResult = identity<number>(42); // T = number
const stringResult = identity<string>("hello"); // T = string
// 类型推断
const autoResult = identity("world"); // 自动推断 T = string
// 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const coords = pair<number, number>(10, 20); // [number, number]
const userInfo = pair("张三", 25); // [string, number] - 推断
泛型接口:
// 基础泛型接口
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}
// 实现泛型接口
class NumberContainer implements Container<number> {
constructor(public value: number) {}
getValue(): number {
return this.value;
}
setValue(value: number): void {
this.value = value;
}
}
class StringContainer implements Container<string> {
constructor(public value: string) {}
getValue(): string {
return this.value;
}
setValue(value: string): void {
this.value = value;
}
}
复杂泛型应用:
API 响应泛型:
// 通用 API 响应接口
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
errors?: string[];
}
interface User {
id: number;
name: string;
email: string;
}
interface Product {
id: number;
name: string;
price: number;
}
// 使用泛型接口
type UserResponse = ApiResponse<User>;
type ProductListResponse = ApiResponse<Product[]>;
type LoginResponse = ApiResponse<{ token: string; user: User }>;
// 泛型 API 函数
async function apiCall<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json() as ApiResponse<T>;
}
// 使用
const userResponse = await apiCall<User>("/api/users/1");
const products = await apiCall<Product[]>("/api/products");
数据操作泛型:
// 泛型数组操作
interface Repository<T> {
items: T[];
add(item: T): void;
remove(id: string | number): boolean;
find(predicate: (item: T) => boolean): T | undefined;
filter(predicate: (item: T) => boolean): T[];
map<R>(mapper: (item: T) => R): R[];
}
class InMemoryRepository<T extends { id: string | number }> implements Repository<T> {
public items: T[] = [];
add(item: T): void {
this.items.push(item);
}
remove(id: string | number): boolean {
const index = this.items.findIndex(item => item.id === id);
if (index > -1) {
this.items.splice(index, 1);
return true;
}
return false;
}
find(predicate: (item: T) => boolean): T | undefined {
return this.items.find(predicate);
}
filter(predicate: (item: T) => boolean): T[] {
return this.items.filter(predicate);
}
map<R>(mapper: (item: T) => R): R[] {
return this.items.map(mapper);
}
}
// 使用
const userRepository = new InMemoryRepository<User>();
userRepository.add({ id: 1, name: "张三", email: "[email protected]" });
const productRepository = new InMemoryRepository<Product>();
productRepository.add({ id: 1, name: "笔记本", price: 5999 });
高级泛型特性:
泛型约束:
// 约束泛型必须有特定属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(`Length: ${arg.length}`);
return arg;
}
logLength("hello"); // 正确:string 有 length
logLength([1, 2, 3]); // 正确:array 有 length
// logLength(123); // 错误:number 没有 length
// 键约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "张三", age: 25 };
const userName = getProperty(user, "name"); // string
const userId = getProperty(user, "id"); // number
// const invalid = getProperty(user, "salary"); // 错误:'salary' 不存在
条件类型与泛型:
// 条件类型
type NonNull<T> = T extends null | undefined ? never : T;
type ArrayElement<T> = T extends (infer U)[] ? U : never;
// 实用工具类型
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
// 使用示例
interface CreateUserRequest {
name: string;
email: string;
age?: number;
phone?: string;
}
// 使age字段必需,其他保持原样
type CreateUserWithAge = RequiredFields<CreateUserRequest, 'age'>;
实际项目应用:
状态管理:
// 泛型状态管理
interface State<T> {
data: T | null;
loading: boolean;
error: string | null;
}
interface Actions<T> {
setLoading(): void;
setData(data: T): void;
setError(error: string): void;
reset(): void;
}
function createStore<T>(): [State<T>, Actions<T>] {
let state: State<T> = {
data: null,
loading: false,
error: null
};
const actions: Actions<T> = {
setLoading: () => { state.loading = true; },
setData: (data: T) => {
state.data = data;
state.loading = false;
state.error = null;
},
setError: (error: string) => {
state.error = error;
state.loading = false;
},
reset: () => {
state = { data: null, loading: false, error: null };
}
};
return [state, actions];
}
// 使用
const [userState, userActions] = createStore<User>();
const [productState, productActions] = createStore<Product[]>();
表单处理:
// 泛型表单处理
interface FormField<T> {
value: T;
error?: string;
touched: boolean;
validate?: (value: T) => string | undefined;
}
type FormState<T> = {
[K in keyof T]: FormField<T[K]>;
};
interface LoginForm {
email: string;
password: string;
}
const loginFormState: FormState<LoginForm> = {
email: {
value: "",
touched: false,
validate: (email) => email.includes("@") ? undefined : "Invalid email"
},
password: {
value: "",
touched: false,
validate: (pwd) => pwd.length >= 6 ? undefined : "Password too short"
}
};
最佳实践:
What are Generic Constraints? How to use the extends keyword?
What are Generic Constraints? How to use the extends keyword?
答案:
泛型约束(Generic Constraints)通过 extends 关键字限制泛型参数必须符合特定条件,提供了更精确的类型控制和更好的类型安全性。
基础泛型约束:
接口约束:
// 约束泛型必须具有特定属性
interface HasLength {
length: number;
}
function logWithLength<T extends HasLength>(arg: T): T {
console.log(`Item length: ${arg.length}`);
return arg;
}
// 有效调用
logWithLength("hello"); // string 有 length 属性
logWithLength([1, 2, 3]); // array 有 length 属性
logWithLength({ length: 5 }); // 对象有 length 属性
// 无效调用
// logWithLength(123); // 错误:number 没有 length 属性
// logWithLength({ name: "test" }); // 错误:对象没有 length 属性
keyof 约束:
// 约束泛型 K 必须是 T 的属性键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = {
id: 1,
name: "张三",
age: 25,
email: "[email protected]"
};
// 类型安全的属性访问
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
const id = getProperty(person, "id"); // number
// 编译时错误防护
// const invalid = getProperty(person, "salary"); // 错误:'salary' 不存在于类型中
// 批量获取属性
function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
const result = {} as Pick<T, K>;
keys.forEach(key => {
result[key] = obj[key];
});
return result;
}
const userInfo = pick(person, "name", "email"); // { name: string; email: string }
高级约束模式:
条件约束:
// 约束 T 必须是数组类型
type ArrayElement<T extends readonly unknown[]> = T extends readonly (infer U)[] ? U : never;
type StringArray = ArrayElement<string[]>; // string
type NumberArray = ArrayElement<number[]>; // number
// type Invalid = ArrayElement<string>; // 错误:string 不是数组
// 函数参数约束
function processArray<T extends readonly unknown[]>(
arr: T,
processor: (item: ArrayElement<T>) => void
): void {
arr.forEach(processor);
}
processArray([1, 2, 3], (num) => console.log(num * 2)); // num: number
processArray(["a", "b"], (str) => console.log(str.toUpperCase())); // str: string
多重约束:
// 同时满足多个约束
interface Serializable {
serialize(): string;
}
interface Timestamped {
timestamp: Date;
}
function processData<T extends Serializable & Timestamped>(data: T): string {
const serialized = data.serialize();
const time = data.timestamp.toISOString();
return `${time}: ${serialized}`;
}
class LogEntry implements Serializable, Timestamped {
constructor(
public message: string,
public timestamp: Date = new Date()
) {}
serialize(): string {
return JSON.stringify({
message: this.message,
timestamp: this.timestamp
});
}
}
const entry = new LogEntry("用户登录");
const result = processData(entry); // 正确:LogEntry 满足所有约束
实际应用场景:
数据库操作约束:
// 约束实体必须有 id 属性
interface Entity {
id: string | number;
}
interface Repository<T extends Entity> {
save(entity: T): Promise<T>;
findById(id: T['id']): Promise<T | null>;
delete(id: T['id']): Promise<boolean>;
update(id: T['id'], updates: Partial<T>): Promise<T>;
}
interface User extends Entity {
id: number;
name: string;
email: string;
}
interface Product extends Entity {
id: string;
name: string;
price: number;
}
class DatabaseRepository<T extends Entity> implements Repository<T> {
async save(entity: T): Promise<T> {
// 数据库保存逻辑
console.log(`Saving entity with id: ${entity.id}`);
return entity;
}
async findById(id: T['id']): Promise<T | null> {
// 数据库查询逻辑
console.log(`Finding entity with id: ${id}`);
return null;
}
async delete(id: T['id']): Promise<boolean> {
console.log(`Deleting entity with id: ${id}`);
return true;
}
async update(id: T['id'], updates: Partial<T>): Promise<T> {
console.log(`Updating entity ${id} with:`, updates);
return {} as T;
}
}
const userRepo = new DatabaseRepository<User>();
const productRepo = new DatabaseRepository<Product>();
事件系统约束:
// 约束事件类型
interface BaseEvent {
type: string;
timestamp: Date;
}
interface UserEvent extends BaseEvent {
userId: string;
}
interface SystemEvent extends BaseEvent {
level: 'info' | 'warning' | 'error';
}
// 约束事件处理器
type EventHandler<T extends BaseEvent> = (event: T) => void;
class EventManager<T extends BaseEvent> {
private handlers: Map<string, EventHandler<T>[]> = new Map();
on(eventType: T['type'], handler: EventHandler<T>): void {
if (!this.handlers.has(eventType)) {
this.handlers.set(eventType, []);
}
this.handlers.get(eventType)!.push(handler);
}
emit(event: T): void {
const handlers = this.handlers.get(event.type) || [];
handlers.forEach(handler => handler(event));
}
}
// 使用
const userEventManager = new EventManager<UserEvent>();
userEventManager.on('user.login', (event) => {
console.log(`User ${event.userId} logged in at ${event.timestamp}`);
});
API 客户端约束:
// 约束 API 请求和响应类型
interface ApiEndpoint {
path: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
}
interface GetEndpoint extends ApiEndpoint {
method: 'GET';
}
interface PostEndpoint extends ApiEndpoint {
method: 'POST';
}
// 根据请求方法约束参数
type RequestConfig<T extends ApiEndpoint> = T extends GetEndpoint
? { params?: Record<string, any> }
: T extends PostEndpoint
? { body: any; params?: Record<string, any> }
: { params?: Record<string, any>; body?: any };
async function apiRequest<
TEndpoint extends ApiEndpoint,
TResponse
>(
endpoint: TEndpoint,
config: RequestConfig<TEndpoint>
): Promise<TResponse> {
// API 请求逻辑
console.log(`${endpoint.method} ${endpoint.path}`, config);
return {} as TResponse;
}
// 类型安全的 API 调用
const getUsers = await apiRequest<GetEndpoint, User[]>(
{ path: '/users', method: 'GET' },
{ params: { page: 1 } } // 只能传 params
);
const createUser = await apiRequest<PostEndpoint, User>(
{ path: '/users', method: 'POST' },
{ body: { name: '张三', email: '[email protected]' } } // 必须传 body
);
约束的嵌套和组合:
// 复杂约束组合
interface Comparable<T> {
compareTo(other: T): number;
}
interface Cloneable<T> {
clone(): T;
}
// 多重约束
function sortAndClone<T extends Comparable<T> & Cloneable<T>>(
items: T[]
): T[] {
return items
.map(item => item.clone()) // 必须实现 Cloneable
.sort((a, b) => a.compareTo(b)); // 必须实现 Comparable
}
// 条件约束
type NonNullable<T> = T extends null | undefined ? never : T;
type RequiredKeys<T> = {
[K in keyof T]-?: T[K] extends undefined ? never : K;
}[keyof T];
// 实用工具类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
最佳实践:
How does TypeScript implement class inheritance and interface implementation?
How does TypeScript implement class inheritance and interface implementation?
答案:
TypeScript 支持完整的面向对象编程,提供了类继承(extends)和接口实现(implements)机制,实现代码复用和类型约束。
类继承(extends):
// 基础类
class Animal {
protected name: string;
protected age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public speak(): void {
console.log(`${this.name} makes a sound`);
}
protected getInfo(): string {
return `${this.name} is ${this.age} years old`;
}
}
// 继承类
class Dog extends Animal {
private breed: string;
constructor(name: string, age: number, breed: string) {
super(name, age); // 调用父类构造函数
this.breed = breed;
}
// 重写父类方法
public speak(): void {
console.log(`${this.name} barks loudly!`);
}
// 新增方法
public wagTail(): void {
console.log(`${this.name} wags tail happily`);
}
// 访问受保护的父类方法
public showInfo(): void {
console.log(this.getInfo() + `, breed: ${this.breed}`);
}
}
const dog = new Dog("旺财", 3, "金毛");
dog.speak(); // "旺财 barks loudly!"
dog.wagTail(); // "旺财 wags tail happily"
dog.showInfo(); // "旺财 is 3 years old, breed: 金毛"
接口实现(implements):
// 定义接口
interface Flyable {
fly(): void;
altitude: number;
}
interface Swimmable {
swim(): void;
depth: number;
}
// 实现接口
class Bird implements Flyable {
altitude: number = 0;
fly(): void {
this.altitude = 100;
console.log(`Flying at ${this.altitude} meters`);
}
}
// 多接口实现
class Duck extends Animal implements Flyable, Swimmable {
altitude: number = 0;
depth: number = 0;
fly(): void {
this.altitude = 50;
console.log(`${this.name} flies at ${this.altitude}m`);
}
swim(): void {
this.depth = 2;
console.log(`${this.name} swims at ${this.depth}m depth`);
}
speak(): void {
console.log(`${this.name} quacks`);
}
}
const duck = new Duck("小黄", 2);
duck.speak(); // "小黄 quacks"
duck.fly(); // "小黄 flies at 50m"
duck.swim(); // "小黄 swims at 2m depth"
复杂继承场景:
// 抽象基类
abstract class Shape {
protected x: number;
protected y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
// 抽象方法 - 子类必须实现
abstract calculateArea(): number;
abstract draw(): void;
// 具体方法 - 子类可以使用
public move(dx: number, dy: number): void {
this.x += dx;
this.y += dy;
console.log(`Moved to (${this.x}, ${this.y})`);
}
public getPosition(): [number, number] {
return [this.x, this.y];
}
}
// 继承抽象类
class Circle extends Shape {
constructor(x: number, y: number, private radius: number) {
super(x, y);
}
calculateArea(): number {
return Math.PI * this.radius ** 2;
}
draw(): void {
console.log(`Drawing circle at (${this.x}, ${this.y}) with radius ${this.radius}`);
}
}
class Rectangle extends Shape {
constructor(
x: number,
y: number,
private width: number,
private height: number
) {
super(x, y);
}
calculateArea(): number {
return this.width * this.height;
}
draw(): void {
console.log(`Drawing rectangle at (${this.x}, ${this.y}) ${this.width}x${this.height}`);
}
}
接口与类的组合应用:
// Mixin 接口
interface Timestamped {
timestamp: Date;
updateTimestamp(): void;
}
interface Loggable {
log(message: string): void;
}
// Mixin 实现函数
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
if (name !== 'constructor') {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) || Object.create(null)
);
}
});
});
}
// Mixin 类
class TimestampedMixin implements Timestamped {
timestamp: Date = new Date();
updateTimestamp(): void {
this.timestamp = new Date();
}
}
class LoggableMixin implements Loggable {
log(message: string): void {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
// 使用 Mixin 的类
class User implements Timestamped, Loggable {
constructor(public name: string, public email: string) {}
// 这些方法会通过 applyMixins 自动添加
timestamp!: Date;
updateTimestamp!: () => void;
log!: (message: string) => void;
}
// 应用 Mixins
applyMixins(User, [TimestampedMixin, LoggableMixin]);
实际应用场景:
// 基础组件接口
interface Component {
render(): string;
mount(element: HTMLElement): void;
unmount(): void;
}
interface EventEmitter {
on(event: string, callback: Function): void;
emit(event: string, ...args: any[]): void;
}
// 基础组件类
abstract class BaseComponent implements Component, EventEmitter {
protected element?: HTMLElement;
private events: Map<string, Function[]> = new Map();
abstract render(): string;
mount(element: HTMLElement): void {
this.element = element;
element.innerHTML = this.render();
this.onMounted();
}
unmount(): void {
if (this.element) {
this.element.innerHTML = '';
this.element = undefined;
}
this.onUnmounted();
}
on(event: string, callback: Function): void {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event)!.push(callback);
}
emit(event: string, ...args: any[]): void {
const callbacks = this.events.get(event) || [];
callbacks.forEach(callback => callback(...args));
}
protected onMounted(): void {}
protected onUnmounted(): void {}
}
// 具体组件
class Button extends BaseComponent {
constructor(private text: string, private onClick?: () => void) {
super();
}
render(): string {
return `<button id="btn">${this.text}</button>`;
}
protected onMounted(): void {
const button = this.element?.querySelector('#btn');
if (button && this.onClick) {
button.addEventListener('click', this.onClick);
}
}
}
最佳实践:
What are namespaces and modules?
What are namespaces and modules?
答案:
命名空间和模块是 TypeScript 中两种不同的代码组织方式。模块是现代 JavaScript 的标准,而命名空间是 TypeScript 早期的内部模块系统。
命名空间(namespace):
基础命名空间:
// 命名空间定义
namespace Geometry {
export interface Point {
x: number;
y: number;
}
export class Circle {
constructor(public center: Point, public radius: number) {}
area(): number {
return Math.PI * this.radius ** 2;
}
}
export function distance(p1: Point, p2: Point): number {
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
}
}
// 使用命名空间
const point1: Geometry.Point = { x: 0, y: 0 };
const circle = new Geometry.Circle(point1, 5);
命名空间合并:
// 跨文件命名空间合并
namespace App {
export interface User {
id: number;
name: string;
}
}
namespace App {
export class UserService {
getUser(id: number): User {
return { id, name: "用户" + id };
}
}
}
// 合并后可以一起使用
const service = new App.UserService();
const user: App.User = service.getUser(1);
模块(ES6 Modules):
模块导出:
// geometry.ts
export interface Point {
x: number;
y: number;
}
export class Circle {
constructor(public center: Point, public radius: number) {}
area(): number {
return Math.PI * this.radius ** 2;
}
}
export function distance(p1: Point, p2: Point): number {
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
}
// 默认导出
export default class Rectangle {
constructor(public width: number, public height: number) {}
}
模块导入:
// main.ts
import Rectangle, { Point, Circle, distance } from './geometry';
import * as Geo from './geometry'; // 命名空间导入
const point1: Point = { x: 0, y: 0 };
const circle = new Circle(point1, 5);
const rect = new Rectangle(10, 20);
主要区别:
What are decorators in TypeScript? What are the common types of decorators?
What are decorators in TypeScript? What are the common types of decorators?
答案:
装饰器(Decorator)是 TypeScript 中用于修改类、方法、属性或参数行为的特殊声明,提供了声明式编程和元编程能力,需要在 tsconfig.json 中启用 experimentalDecorators。
装饰器配置:
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
装饰器类型:
类装饰器(Class Decorator):
// 基础类装饰器
function Sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@Sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return `Hello, ${this.greeting}`;
}
}
// 带参数的类装饰器工厂
function Entity(tableName: string) {
return function<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
tableName = tableName;
save() {
console.log(`Saving to ${tableName} table`);
}
};
};
}
@Entity('users')
class User {
constructor(public name: string) {}
}
const user = new User("张三");
(user as any).save(); // "Saving to users table"
方法装饰器(Method Decorator):
// 方法装饰器
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned:`, result);
return result;
};
}
// 性能监控装饰器
function Performance(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const start = Date.now();
const result = originalMethod.apply(this, args);
const end = Date.now();
console.log(`${propertyKey} executed in ${end - start}ms`);
return result;
};
}
class Calculator {
@Log
@Performance
add(a: number, b: number): number {
return a + b;
}
@Log
multiply(a: number, b: number): number {
return a * b;
}
}
const calc = new Calculator();
calc.add(5, 3); // 会输出日志和性能信息
属性装饰器(Property Decorator):
// 属性验证装饰器
function Required(target: any, propertyKey: string) {
let value: any;
const getter = () => value;
const setter = (newVal: any) => {
if (newVal === null || newVal === undefined || newVal === '') {
throw new Error(`${propertyKey} is required`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
// 格式化装饰器
function Format(formatter: (value: any) => any) {
return function(target: any, propertyKey: string) {
let value: any;
const getter = () => value;
const setter = (newVal: any) => {
value = formatter(newVal);
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class UserProfile {
@Required
public name: string = '';
@Format(email => email.toLowerCase().trim())
public email: string = '';
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
const profile = new UserProfile("张三", " [email protected] ");
console.log(profile.email); // "[email protected]"
参数装饰器(Parameter Decorator):
// 参数验证装饰器
function Validate(target: any, methodName: string, parameterIndex: number) {
const existingRequiredParameters: number[] =
Reflect.getOwnMetadata('required_parameters', target, methodName) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata('required_parameters', existingRequiredParameters, target, methodName);
}
// 方法装饰器配合参数装饰器
function ValidateMethod(target: any, methodName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function(...args: any[]) {
const requiredParameters: number[] =
Reflect.getOwnMetadata('required_parameters', target, methodName) || [];
for (const parameterIndex of requiredParameters) {
if (args[parameterIndex] === null || args[parameterIndex] === undefined) {
throw new Error(`Parameter at index ${parameterIndex} is required`);
}
}
return method.apply(this, args);
};
}
class UserService {
@ValidateMethod
createUser(@Validate name: string, @Validate email: string, age?: number) {
return { name, email, age };
}
}
实际应用场景:
API 路由装饰器:
// HTTP 方法装饰器
function Get(path: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata('http:method', 'GET', target, propertyKey);
Reflect.defineMetadata('http:path', path, target, propertyKey);
};
}
function Post(path: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata('http:method', 'POST', target, propertyKey);
Reflect.defineMetadata('http:path', path, target, propertyKey);
};
}
// 控制器装饰器
function Controller(basePath: string) {
return function<T extends { new(...args: any[]): {} }>(constructor: T) {
Reflect.defineMetadata('controller:basePath', basePath, constructor);
return constructor;
};
}
@Controller('/api/users')
class UserController {
@Get('/')
getAllUsers() {
return { users: [] };
}
@Post('/')
createUser() {
return { message: 'User created' };
}
@Get('/:id')
getUser() {
return { user: {} };
}
}
依赖注入装饰器:
// 注入装饰器
const INJECTION_TOKENS = new Map<string, any>();
function Injectable<T extends { new(...args: any[]): {} }>(constructor: T) {
INJECTION_TOKENS.set(constructor.name, constructor);
return constructor;
}
function Inject(token: string) {
return function(target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
const existingTokens = Reflect.getOwnMetadata('inject:tokens', target) || [];
existingTokens[parameterIndex] = token;
Reflect.defineMetadata('inject:tokens', existingTokens, target);
};
}
@Injectable
class DatabaseService {
connect() {
console.log('Connected to database');
}
}
@Injectable
class LoggerService {
log(message: string) {
console.log(`[LOG] ${message}`);
}
}
@Injectable
class UserService {
constructor(
@Inject('DatabaseService') private db: DatabaseService,
@Inject('LoggerService') private logger: LoggerService
) {}
createUser(name: string) {
this.logger.log(`Creating user: ${name}`);
this.db.connect();
}
}
缓存装饰器:
// 缓存装饰器
function Cache(expireTime: number = 60000) {
const cache = new Map<string, { value: any; expiry: number }>();
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const cacheKey = `${propertyKey}_${JSON.stringify(args)}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() < cached.expiry) {
console.log(`Cache hit for ${propertyKey}`);
return cached.value;
}
const result = originalMethod.apply(this, args);
cache.set(cacheKey, {
value: result,
expiry: Date.now() + expireTime
});
console.log(`Cache miss for ${propertyKey}, result cached`);
return result;
};
};
}
class ApiService {
@Cache(30000) // 缓存30秒
async fetchUserData(userId: number) {
console.log(`Fetching data for user ${userId}`);
// 模拟 API 调用
return { id: userId, name: `User ${userId}` };
}
}
装饰器组合与执行顺序:
// 装饰器执行顺序示例
function First() {
console.log("First(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("First(): called");
};
}
function Second() {
console.log("Second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("Second(): called");
};
}
class ExampleClass {
@First()
@Second()
method() {}
}
// 输出顺序:
// First(): factory evaluated
// Second(): factory evaluated
// Second(): called
// First(): called
最佳实践:
experimentalDecorators 和 emitDecoratorMetadataWhat are Type Guards? What are the implementation methods?
What are Type Guards? What are the implementation methods?
答案:
类型守卫(Type Guard)是 TypeScript 中在运行时检查类型并缩窄类型范围的机制,让编译器能够在特定代码块中推断出更精确的类型。
内置类型守卫:
typeof 类型守卫:
function processValue(value: string | number) {
if (typeof value === "string") {
// 在这个块中,value 被缩窄为 string 类型
return value.toUpperCase();
}
// 这里 value 自动被缩窄为 number 类型
return value.toFixed(2);
}
// 多类型检查
function formatValue(input: string | number | boolean) {
if (typeof input === "string") {
return `"${input}"`; // input: string
} else if (typeof input === "number") {
return input.toString(); // input: number
} else {
return input ? "true" : "false"; // input: boolean
}
}
instanceof 类型守卫:
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
function handlePet(pet: Dog | Cat) {
if (pet instanceof Dog) {
pet.bark(); // pet: Dog
} else {
pet.meow(); // pet: Cat
}
}
// 错误处理中的应用
function handleError(error: unknown) {
if (error instanceof Error) {
console.log(error.message); // error: Error
} else if (typeof error === "string") {
console.log(error); // error: string
} else {
console.log("Unknown error occurred");
}
}
自定义类型守卫:
用户定义的类型守卫函数:
// 使用 is 关键字
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
// 自定义类型守卫函数
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function handleAnimal(animal: Fish | Bird) {
if (isFish(animal)) {
animal.swim(); // animal: Fish
} else {
animal.fly(); // animal: Bird
}
}
// API 响应类型守卫
interface User {
id: number;
name: string;
email: string;
}
function isUser(obj: any): obj is User {
return obj &&
typeof obj.id === 'number' &&
typeof obj.name === 'string' &&
typeof obj.email === 'string';
}
async function fetchUser(): Promise<User | null> {
const response = await fetch('/api/user');
const data = await response.json();
if (isUser(data)) {
return data; // data: User
}
return null; // 数据格式不正确
}
in 操作符类型守卫:
interface Car {
drive(): void;
wheels: number;
}
interface Boat {
sail(): void;
hull: string;
}
function operateVehicle(vehicle: Car | Boat) {
if ('drive' in vehicle) {
vehicle.drive(); // vehicle: Car
console.log(`Car has ${vehicle.wheels} wheels`);
} else {
vehicle.sail(); // vehicle: Boat
console.log(`Boat has ${vehicle.hull} hull`);
}
}
// 复杂对象检查
interface ApiSuccess {
success: true;
data: any;
}
interface ApiError {
success: false;
error: string;
}
type ApiResponse = ApiSuccess | ApiError;
function handleApiResponse(response: ApiResponse) {
if ('data' in response) {
// response: ApiSuccess
console.log("Success:", response.data);
} else {
// response: ApiError
console.log("Error:", response.error);
}
}
discriminated union 类型守卫:
// 判别联合类型
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Circle | Square | Rectangle;
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
// shape: Circle
return Math.PI * shape.radius ** 2;
case "square":
// shape: Square
return shape.size ** 2;
case "rectangle":
// shape: Rectangle
return shape.width * shape.height;
default:
// 确保所有情况都被处理
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
// 事件系统应用
interface ClickEvent {
type: "click";
target: HTMLElement;
clientX: number;
clientY: number;
}
interface KeyboardEvent {
type: "keydown" | "keyup";
target: HTMLElement;
key: string;
ctrlKey: boolean;
}
type UIEvent = ClickEvent | KeyboardEvent;
function handleUIEvent(event: UIEvent) {
switch (event.type) {
case "click":
// event: ClickEvent
console.log(`Clicked at (${event.clientX}, ${event.clientY})`);
break;
case "keydown":
case "keyup":
// event: KeyboardEvent
console.log(`Key ${event.key} ${event.type}`);
if (event.ctrlKey) {
console.log("Ctrl modifier was pressed");
}
break;
}
}
高级类型守卫应用:
// 泛型类型守卫
function isArrayOf<T>(
arr: unknown,
guard: (item: unknown) => item is T
): arr is T[] {
return Array.isArray(arr) && arr.every(guard);
}
function isNumber(value: unknown): value is number {
return typeof value === 'number';
}
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// 使用
const unknownData: unknown = [1, 2, 3, 4];
if (isArrayOf(unknownData, isNumber)) {
// unknownData: number[]
const sum = unknownData.reduce((a, b) => a + b, 0);
}
// 条件类型与类型守卫结合
type NonNullable<T> = T extends null | undefined ? never : T;
function isNotNull<T>(value: T): value is NonNullable<T> {
return value !== null && value !== undefined;
}
// 使用示例
function processUsers(users: (User | null)[]) {
const validUsers = users.filter(isNotNull); // validUsers: User[]
validUsers.forEach(user => {
console.log(user.name); // 安全访问,user 不可能是 null
});
}
// 复杂类型检查
interface LoadingState {
status: 'loading';
}
interface SuccessState {
status: 'success';
data: any;
}
interface ErrorState {
status: 'error';
error: string;
}
type AsyncState = LoadingState | SuccessState | ErrorState;
function isSuccessState(state: AsyncState): state is SuccessState {
return state.status === 'success';
}
function isErrorState(state: AsyncState): state is ErrorState {
return state.status === 'error';
}
function handleAsyncState(state: AsyncState) {
if (isSuccessState(state)) {
console.log("Data:", state.data); // state: SuccessState
} else if (isErrorState(state)) {
console.log("Error:", state.error); // state: ErrorState
} else {
console.log("Loading..."); // state: LoadingState
}
}
最佳实践:
How does TypeScript integrate with third-party JavaScript libraries?
How does TypeScript integrate with third-party JavaScript libraries?
答案:
TypeScript 通过类型声明文件与第三方 JavaScript 库集成,提供类型安全和开发体验,有多种集成方式。
集成方式:
使用 @types 包:
# 安装 JavaScript 库和对应的类型定义
npm install lodash
npm install @types/lodash --save-dev
npm install express
npm install @types/express --save-dev
// 使用有类型支持的第三方库
import _ from 'lodash';
import express from 'express';
const numbers = [1, 2, 3, 4, 5];
const doubled = _.map(numbers, n => n * 2); // 类型安全
const app = express();
app.get('/users', (req, res) => {
// req 和 res 都有完整的类型信息
res.json({ users: [] });
});
库自带类型定义:
// 现代库通常自带 TypeScript 声明
import axios from 'axios'; // axios 包含类型定义
import React from 'react'; // React 包含类型定义
interface User {
id: number;
name: string;
email: string;
}
// 使用泛型获得类型安全的 API 调用
const response = await axios.get<User[]>('/api/users');
const users: User[] = response.data; // 类型安全
// React 组件也有完整类型支持
const UserComponent: React.FC<{ user: User }> = ({ user }) => (
<div>{user.name}</div>
);
手动创建声明文件:
// 为没有类型的库创建声明文件
// types/legacy-library.d.ts
declare module 'legacy-library' {
export interface Config {
apiUrl: string;
debug: boolean;
}
export class ApiClient {
constructor(config: Config);
get<T>(path: string): Promise<T>;
post<T>(path: string, data: any): Promise<T>;
}
export function initialize(config: Config): void;
export const version: string;
}
// 使用自定义声明
import { ApiClient, initialize } from 'legacy-library';
initialize({ apiUrl: 'https://api.example.com', debug: true });
const client = new ApiClient({ apiUrl: 'https://api.example.com', debug: false });
const users = await client.get<User[]>('/users');
高级集成技巧:
全局变量声明:
// types/global.d.ts
declare global {
// jQuery 全局变量
const $: {
(selector: string): {
addClass: (className: string) => void;
removeClass: (className: string) => void;
on: (event: string, handler: (e: Event) => void) => void;
};
ajax: (options: {
url: string;
method?: 'GET' | 'POST';
data?: any;
success?: (data: any) => void;
}) => void;
};
// 环境变量
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
API_URL: string;
SECRET_KEY: string;
}
}
// Window 对象扩展
interface Window {
gtag: (command: string, ...args: any[]) => void;
dataLayer: any[];
}
}
// 使用全局声明
$('.button').addClass('active');
$.ajax({
url: '/api/data',
method: 'GET',
success: (data) => console.log(data)
});
const apiUrl = process.env.API_URL; // 有类型提示
window.gtag('event', 'click', { button: 'header' });
模块增强(Module Augmentation):
// 扩展现有模块的类型
import 'express';
declare module 'express' {
interface Request {
user?: {
id: number;
name: string;
role: 'admin' | 'user';
};
}
}
// 现在可以使用扩展的类型
app.use((req, res, next) => {
req.user = { id: 1, name: '张三', role: 'admin' }; // 类型安全
next();
});
app.get('/profile', (req, res) => {
if (req.user) {
res.json({ name: req.user.name }); // 类型安全访问
}
});
条件类型集成:
// 为动态库创建类型适配器
type LibraryMethod<T> = T extends 'get'
? <R>(path: string) => Promise<R>
: T extends 'post'
? <R>(path: string, data: any) => Promise<R>
: T extends 'delete'
? (path: string) => Promise<void>
: never;
interface DynamicApiClient {
request<T extends 'get' | 'post' | 'delete'>(
method: T,
path: string,
data?: any
): ReturnType<LibraryMethod<T>>;
}
declare const apiClient: DynamicApiClient;
// 类型安全的动态调用
const users = await apiClient.request('get', '/users'); // 返回 Promise<unknown>
const newUser = await apiClient.request('post', '/users', userData); // 返回 Promise<unknown>
await apiClient.request('delete', '/users/1'); // 返回 Promise<void>
实际项目集成示例:
React + UI 库集成:
// 集成 Ant Design
import { Button, Form, Input } from 'antd';
import type { FormInstance } from 'antd/es/form';
interface LoginFormData {
username: string;
password: string;
}
const LoginForm: React.FC = () => {
const [form] = Form.useForm<LoginFormData>();
const onFinish = (values: LoginFormData) => {
console.log('登录数据:', values); // 完整类型支持
};
return (
<Form form={form} onFinish={onFinish}>
<Form.Item name="username" rules={[{ required: true }]}>
<Input placeholder="用户名" />
</Form.Item>
<Form.Item name="password" rules={[{ required: true }]}>
<Input.Password placeholder="密码" />
</Form.Item>
<Button type="primary" htmlType="submit">
登录
</Button>
</Form>
);
};
数据库 ORM 集成:
// 集成 Prisma
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Prisma 自动生成类型
async function getUserWithPosts(userId: number) {
const user = await prisma.user.findUnique({
where: { id: userId },
include: { posts: true } // 自动类型推断
});
return user; // 返回类型自动推断包含 posts 关联
}
// 使用生成的类型
type UserWithPosts = Awaited<ReturnType<typeof getUserWithPosts>>;
最佳实践:
What are declaration files (.d.ts)? How to write them?
What are declaration files (.d.ts)? How to write them?
- 考察点:类型声明文件理解与编写能力。
答案:
声明文件(.d.ts)是 TypeScript 中专门用于类型声明的文件,只包含类型信息而不包含具体实现,为 JavaScript 代码提供类型定义。
声明文件的作用:
为 JavaScript 代码提供类型信息:
// utils.js (JavaScript 文件)
function formatDate(date) {
return date.toISOString().split('T')[0];
}
function calculateAge(birthYear) {
return new Date().getFullYear() - birthYear;
}
const API_BASE_URL = 'https://api.example.com';
module.exports = {
formatDate,
calculateAge,
API_BASE_URL
};
// utils.d.ts (对应的声明文件)
declare function formatDate(date: Date): string;
declare function calculateAge(birthYear: number): number;
declare const API_BASE_URL: string;
export { formatDate, calculateAge, API_BASE_URL };
描述全局变量和环境:
// global.d.ts
declare global {
// 全局变量
const __VERSION__: string;
const __DEV__: boolean;
// 全局函数
function gtag(command: string, targetId: string, config?: any): void;
// 扩展 Window 对象
interface Window {
dataLayer: any[];
customAnalytics: {
track: (event: string, properties?: Record<string, any>) => void;
identify: (userId: string, traits?: Record<string, any>) => void;
};
}
// 扩展 NodeJS 环境
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
REDIS_URL?: string;
}
}
}
export {}; // 确保文件被当作模块处理
编写声明文件的方法:
基本类型声明:
// types.d.ts
// 函数声明
declare function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number,
immediate?: boolean
): T;
// 变量声明
declare const VERSION: string;
declare let isProduction: boolean;
// 类声明
declare class EventEmitter {
constructor();
on(event: string, listener: (...args: any[]) => void): this;
off(event: string, listener: (...args: any[]) => void): this;
emit(event: string, ...args: any[]): boolean;
}
// 接口声明
interface ApiResponse<T = any> {
success: boolean;
data: T;
message?: string;
timestamp: number;
}
// 类型别名声明
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
type ApiEndpoint = `/api/${string}`;
模块声明:
// modules.d.ts
// 为文件扩展名声明模块
declare module '*.css' {
const content: { [className: string]: string };
export default content;
}
declare module '*.scss' {
const content: { [className: string]: string };
export default content;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.svg' {
import React from 'react';
const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
export { ReactComponent };
const src: string;
export default src;
}
// 为第三方模块声明类型
declare module 'custom-library' {
interface Config {
apiUrl: string;
timeout?: number;
retries?: number;
}
interface User {
id: string;
name: string;
email: string;
}
class ApiClient {
constructor(config: Config);
getUser(id: string): Promise<User>;
updateUser(id: string, data: Partial<User>): Promise<User>;
}
export { Config, User, ApiClient };
export default ApiClient;
}
命名空间声明:
// vendor.d.ts
// jQuery 声明
declare namespace $ {
interface JQueryStatic {
(selector: string): JQuery;
(element: Element): JQuery;
(callback: () => void): void;
ajax(settings: AjaxSettings): JQueryXHR;
get(url: string, data?: any, success?: (data: any) => void): JQueryXHR;
post(url: string, data?: any, success?: (data: any) => void): JQueryXHR;
extend<T>(target: T, ...sources: any[]): T;
each<T>(array: T[], callback: (index: number, value: T) => void): void;
}
interface JQuery {
addClass(className: string): JQuery;
removeClass(className: string): JQuery;
toggleClass(className: string, state?: boolean): JQuery;
on(events: string, handler: (eventObject: JQueryEventObject) => any): JQuery;
off(events?: string, handler?: (eventObject: JQueryEventObject) => any): JQuery;
text(): string;
text(text: string): JQuery;
html(): string;
html(htmlString: string): JQuery;
find(selector: string): JQuery;
parent(selector?: string): JQuery;
children(selector?: string): JQuery;
each(callback: (index: number, element: Element) => void): JQuery;
map<T>(callback: (index: number, element: Element) => T): JQuery;
}
interface AjaxSettings {
url: string;
type?: string;
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: any;
success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => void;
error?: (jqXHR: JQueryXHR, textStatus: string, errorThrown: string) => void;
contentType?: string;
dataType?: 'json' | 'xml' | 'html' | 'text' | 'script';
timeout?: number;
}
interface JQueryEventObject extends Event {
data?: any;
namespace?: string;
result?: any;
timeStamp: number;
currentTarget: Element;
delegateTarget: Element;
}
interface JQueryXHR {
readyState: number;
status: number;
statusText: string;
responseText: string;
responseJSON?: any;
done(callback: (data: any, textStatus: string, jqXHR: JQueryXHR) => void): JQueryXHR;
fail(callback: (jqXHR: JQueryXHR, textStatus: string, errorThrown: string) => void): JQueryXHR;
always(callback: (data?: any, textStatus?: string, jqXHR?: JQueryXHR) => void): JQueryXHR;
}
}
declare const $: $.JQueryStatic;
高级声明技巧:
// advanced.d.ts
// 条件类型声明
type ApiResult<T extends 'success' | 'error'> = T extends 'success'
? { status: 'success'; data: any; timestamp: number }
: { status: 'error'; message: string; code: number };
// 泛型约束声明
interface Repository<T extends { id: string | number }> {
findById(id: T['id']): Promise<T | null>;
create(data: Omit<T, 'id'>): Promise<T>;
update(id: T['id'], data: Partial<Omit<T, 'id'>>): Promise<T>;
delete(id: T['id']): Promise<void>;
findAll(query?: QueryOptions<T>): Promise<T[]>;
}
interface QueryOptions<T> {
where?: Partial<T>;
orderBy?: keyof T;
limit?: number;
offset?: number;
}
// 映射类型声明
type ValidationRules<T> = {
[K in keyof T]?: {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
custom?: (value: T[K]) => boolean | string;
};
};
// 函数重载声明
declare function createElement(tag: 'div'): HTMLDivElement;
declare function createElement(tag: 'span'): HTMLSpanElement;
declare function createElement(tag: 'input'): HTMLInputElement;
declare function createElement(tag: 'button'): HTMLButtonElement;
declare function createElement(tag: string): HTMLElement;
// 模板字面量类型声明
type HttpStatusCode = `${2 | 3 | 4 | 5}${number}${number}`;
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
type ApiEndpoint<T extends string> = `/api/${T}`;
declare function fetchApi<T extends string>(
method: HttpMethod,
endpoint: ApiEndpoint<T>
): Promise<ApiResponse>;
实际项目中的声明文件组织:
// 项目结构
src/
├── types/
│ ├── global.d.ts // 全局类型声明
│ ├── modules.d.ts // 模块声明
│ ├── api.d.ts // API 相关类型
│ ├── components.d.ts // 组件类型
│ └── vendor/ // 第三方库声明
│ ├── jquery.d.ts
│ └── lodash.d.ts
├── components/
└── utils/
// tsconfig.json 配置
{
"compilerOptions": {
"typeRoots": ["./src/types", "./node_modules/@types"],
"types": ["node", "jest"]
},
"include": [
"src/**/*"
]
}
具体编写示例:
// src/types/api.d.ts
declare namespace API {
// 基础响应类型
interface BaseResponse {
success: boolean;
message?: string;
timestamp: number;
}
interface SuccessResponse<T = any> extends BaseResponse {
success: true;
data: T;
}
interface ErrorResponse extends BaseResponse {
success: false;
error: {
code: number;
message: string;
details?: any;
};
}
type Response<T = any> = SuccessResponse<T> | ErrorResponse;
// 用户相关类型
namespace User {
interface Profile {
id: number;
username: string;
email: string;
avatar?: string;
role: 'admin' | 'user' | 'moderator';
createdAt: string;
updatedAt: string;
}
interface CreateRequest {
username: string;
email: string;
password: string;
}
interface UpdateRequest {
username?: string;
email?: string;
avatar?: string;
}
interface LoginRequest {
email: string;
password: string;
}
interface LoginResponse {
user: Profile;
token: string;
refreshToken: string;
expiresIn: number;
}
}
// 文章相关类型
namespace Article {
interface Item {
id: number;
title: string;
content: string;
summary: string;
author: User.Profile;
tags: string[];
publishedAt: string;
updatedAt: string;
viewCount: number;
likeCount: number;
}
interface CreateRequest {
title: string;
content: string;
summary?: string;
tags?: string[];
}
interface UpdateRequest extends Partial<CreateRequest> {}
interface ListQuery {
page?: number;
limit?: number;
author?: number;
tag?: string;
keyword?: string;
sortBy?: 'createdAt' | 'updatedAt' | 'viewCount' | 'likeCount';
order?: 'asc' | 'desc';
}
interface ListResponse {
items: Item[];
total: number;
page: number;
limit: number;
totalPages: number;
}
}
}
// 使用声明的类型
declare function apiRequest<T = any>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
url: string,
data?: any
): Promise<API.Response<T>>;
// 导出供其他文件使用
export = API;
export as namespace API;
最佳实践:
declare 关键字进行类型声明What are the common configurations in TypeScript compilation options (tsconfig.json)?
What are the common configurations in TypeScript compilation options (tsconfig.json)?
答案:
tsconfig.json 是 TypeScript 项目的配置文件,用于指定编译选项、文件包含规则和项目设置,是 TypeScript 项目的核心配置。
基础编译选项:
{
"compilerOptions": {
// 目标 JavaScript 版本
"target": "ES2020", // 输出 JavaScript 版本
"module": "ESNext", // 模块系统
"lib": ["ES2020", "DOM", "DOM.Iterable"], // 包含的库文件
// 输出配置
"outDir": "./dist", // 输出目录
"rootDir": "./src", // 输入根目录
"removeComments": true, // 移除注释
"downlevelIteration": true, // 为迭代器提供完整支持
// 模块解析
"moduleResolution": "node", // 模块解析策略
"esModuleInterop": true, // ES 模块互操作性
"allowSyntheticDefaultImports": true, // 允许合成默认导入
"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
// 类型检查
"strict": true, // 启用所有严格类型检查
"noImplicitAny": true, // 禁止隐式 any 类型
"strictNullChecks": true, // 严格 null 检查
"strictFunctionTypes": true, // 严格函数类型检查
"noImplicitReturns": true, // 禁止函数没有返回值
"noUnusedLocals": true, // 检查未使用的局部变量
"noUnusedParameters": true, // 检查未使用的参数
// 实验性功能
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true, // 为装饰器生成元数据
// 源码映射
"sourceMap": true, // 生成源码映射
"inlineSourceMap": false, // 内联源码映射
// 其他
"skipLibCheck": true, // 跳过库文件检查
"allowJs": true, // 允许 JavaScript 文件
"checkJs": false, // 检查 JavaScript 文件
"declaration": true, // 生成声明文件
"declarationMap": true, // 为声明文件生成映射
"incremental": true, // 启用增量编译
"tsBuildInfoFile": "./dist/.tsbuildinfo" // 构建信息文件位置
}
}
文件包含和排除:
{
"compilerOptions": {
// ... 编译选项
},
// 包含的文件
"include": [
"src/**/*", // 包含 src 目录下所有文件
"types/**/*", // 包含类型声明文件
"tests/**/*.test.ts" // 包含测试文件
],
// 排除的文件
"exclude": [
"node_modules", // 排除 node_modules
"dist", // 排除输出目录
"coverage", // 排除覆盖率报告
"**/*.spec.ts", // 排除特定的测试文件
"build", // 排除构建目录
"*.config.js" // 排除配置文件
],
// 直接指定文件(很少使用)
"files": [
"src/index.ts",
"src/types/global.d.ts"
]
}
不同环境的配置示例:
开发环境配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
// 开发友好配置
"sourceMap": true,
"inlineSources": true,
"removeComments": false,
"preserveConstEnums": true,
// 严格类型检查
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// 模块解析
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
// 路径映射
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/utils/*": ["src/utils/*"],
"@/types/*": ["src/types/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
生产环境配置:
{
"compilerOptions": {
"target": "ES2018",
"module": "CommonJS",
"lib": ["ES2018", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
// 生产优化
"removeComments": true,
"noEmitOnError": true,
"declaration": true,
"declarationMap": false,
"sourceMap": false,
// 严格检查
"strict": true,
"noUnusedLocals": false, // 生产环境可能需要保留未使用代码
"noUnusedParameters": false,
// 兼容性
"downlevelIteration": true,
"importHelpers": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts", "node_modules"]
}
Node.js 项目配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
// Node.js 特定
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
// 类型
"types": ["node"],
// 严格模式
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
// 输出
"sourceMap": true,
"declaration": true,
"removeComments": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests"]
}
React 项目配置:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
// React 特定
"jsx": "react-jsx",
"jsxImportSource": "react",
// 路径映射
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/components/*": ["src/components/*"],
"@/hooks/*": ["src/hooks/*"],
"@/utils/*": ["src/utils/*"]
}
},
"include": [
"src"
],
"exclude": [
"node_modules"
]
}
高级配置选项:
{
"compilerOptions": {
// 项目引用
"composite": true, // 启用项目引用
"incremental": true, // 增量编译
"tsBuildInfoFile": "./.tsbuildinfo", // 构建信息文件
// 模块解析高级选项
"baseUrl": "./", // 基础URL
"paths": { // 路径映射
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"#types/*": ["types/*"]
},
"rootDirs": ["src", "generated"], // 虚拟根目录
// 类型相关
"typeRoots": ["./types", "./node_modules/@types"], // 类型根目录
"types": ["node", "jest", "webpack-env"], // 包含的类型包
// 输出控制
"declarationDir": "./types", // 声明文件输出目录
"outFile": "./dist/bundle.js", // 单文件输出(仅限AMD和System)
// JSX 选项
"jsx": "react-jsx", // JSX 编译方式
"jsxFactory": "React.createElement", // JSX 工厂函数
"jsxFragmentFactory": "React.Fragment", // JSX Fragment 工厂
"jsxImportSource": "react", // JSX 导入源
// 高级类型检查
"exactOptionalPropertyTypes": true, // 精确可选属性类型
"noImplicitOverride": true, // 需要显式 override
"noPropertyAccessFromIndexSignature": true, // 索引签名属性访问限制
"noUncheckedIndexedAccess": true, // 未检查的索引访问
// 实验性功能
"useDefineForClassFields": true, // 使用 define 语义定义类字段
"importsNotUsedAsValues": "remove", // 未使用导入的处理方式
"preserveValueImports": false, // 保留值导入
// 性能
"assumeChangesOnlyAffectDirectDependencies": true, // 假设更改只影响直接依赖
"skipDefaultLibCheck": true, // 跳过默认库检查
// 调试
"generateCpuProfile": "profile.cpuprofile", // 生成 CPU 性能分析
"explainFiles": true, // 解释文件包含原因
"extendedDiagnostics": true // 扩展诊断信息
},
// 项目引用
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" }
],
// 监听选项
"watchOptions": {
"watchFile": "useFsEvents",
"watchDirectory": "useFsEvents",
"fallbackPolling": "dynamicPriority",
"synchronousWatchDirectory": true,
"excludeDirectories": ["**/node_modules", "_build"]
},
// 类型获取选项
"typeAcquisition": {
"enable": false,
"include": ["jquery"],
"exclude": ["jest", "mocha"]
}
}
分环境配置管理:
// tsconfig.json (基础配置)
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
// tsconfig.dev.json (开发环境)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"removeComments": false,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src/**/*", "tests/**/*"]
}
// tsconfig.prod.json (生产环境)
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"removeComments": true,
"declaration": true,
"noUnusedLocals": false,
"noUnusedParameters": false
},
"exclude": ["src/**/*.test.ts", "src/**/*.spec.ts", "tests", "node_modules"]
}
// tsconfig.build.json (构建配置)
{
"extends": "./tsconfig.prod.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"noEmit": false
}
}
命令行使用:
# 使用不同配置文件
tsc --project tsconfig.dev.json
tsc --project tsconfig.prod.json
tsc -p tsconfig.build.json
# 覆盖配置选项
tsc --target ES2018 --outDir ./build
tsc --strict --noImplicitAny
# 监听模式
tsc --watch --project tsconfig.dev.json
最佳实践:
extends 继承基础配置避免重复include 和 exclude 控制编译范围What are TypeScript's module resolution strategies? How to configure path mapping?
What are TypeScript’s module resolution strategies? How to configure path mapping?
- 考察点:模块系统与路径映射配置。
答案:
TypeScript 提供了两种主要的模块解析策略,并支持路径映射功能来简化模块导入,提高代码可维护性。
模块解析策略:
Node.js 解析策略(推荐):
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
解析顺序:
// 导入 './utils/helper'
// 查找顺序:
// 1. ./utils/helper.ts
// 2. ./utils/helper.tsx
// 3. ./utils/helper.d.ts
// 4. ./utils/helper/package.json (查看 "types" 字段)
// 5. ./utils/helper/index.ts
// 6. ./utils/helper/index.tsx
// 7. ./utils/helper/index.d.ts
// 导入 'lodash'
// 查找顺序:
// 1. node_modules/lodash.ts
// 2. node_modules/lodash.tsx
// 3. node_modules/lodash.d.ts
// 4. node_modules/lodash/package.json ("types" 或 "typings" 字段)
// 5. node_modules/lodash/index.ts
// 6. node_modules/@types/lodash/package.json
// 7. node_modules/@types/lodash/index.d.ts
Classic 解析策略(已弃用):
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "classic"
}
}
解析顺序:
// 相对导入 './helper'
// 查找:
// 1. ./helper.ts
// 2. ./helper.d.ts
// 非相对导入 'moduleA'
// 从当前目录开始向上查找:
// 1. /current/directory/moduleA.ts
// 2. /current/directory/moduleA.d.ts
// 3. /current/moduleA.ts
// 4. /current/moduleA.d.ts
// 5. /moduleA.ts
// 6. /moduleA.d.ts
路径映射配置:
基础路径映射:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
// 简化导入路径
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@types/*": ["src/types/*"],
"@assets/*": ["src/assets/*"],
"@api/*": ["src/api/*"],
// 多个可能的位置
"@lib/*": ["src/lib/*", "lib/*"],
// 精确匹配
"config": ["src/config/index.ts"],
"constants": ["src/constants/index.ts"]
}
}
}
// 使用路径映射前
import { Button } from '../../../components/ui/Button';
import { formatDate } from '../../../utils/date';
import { ApiClient } from '../../../api/client';
// 使用路径映射后
import { Button } from '@components/ui/Button';
import { formatDate } from '@utils/date';
import { ApiClient } from '@api/client';
复杂路径映射示例:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
// 根据环境映射不同实现
"config/*": ["src/config/production/*", "src/config/development/*"],
// 版本化的库
"ui-lib/*": ["src/ui/v2/*", "src/ui/v1/*"],
// 平台特定的实现
"platform/*": [
"src/platforms/web/*",
"src/platforms/mobile/*",
"src/platforms/common/*"
],
// 外部库的本地版本
"react": ["node_modules/react"],
"react-dom": ["node_modules/react-dom"],
// 工具库的别名
"utils": ["src/utils/index.ts"],
"helpers": ["src/utils/helpers/index.ts"],
// 样式文件
"@styles/*": ["src/styles/*"],
"theme": ["src/styles/theme/index.ts"],
// 测试工具
"@test-utils": ["tests/utils/index.ts"],
"@mocks/*": ["tests/mocks/*"]
}
}
}
实际项目配置示例:
React 项目路径映射:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@pages/*": ["src/pages/*"],
"@hooks/*": ["src/hooks/*"],
"@context/*": ["src/context/*"],
"@utils/*": ["src/utils/*"],
"@api/*": ["src/api/*"],
"@types/*": ["src/types/*"],
"@assets/*": ["src/assets/*"],
"@styles/*": ["src/styles/*"],
"@constants/*": ["src/constants/*"]
}
}
}
// 组件中使用
import React from 'react';
import { Button } from '@components/ui/Button';
import { useAuth } from '@hooks/useAuth';
import { AuthContext } from '@context/AuthContext';
import { formatCurrency } from '@utils/format';
import { API_ENDPOINTS } from '@constants/api';
import type { User } from '@types/user';
const UserProfile: React.FC = () => {
const { user } = useAuth();
return (
<div>
<h1>{user?.name}</h1>
<Button onClick={() => console.log('Edit profile')}>
Edit Profile
</Button>
</div>
);
};
Node.js 项目路径映射:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@controllers/*": ["controllers/*"],
"@services/*": ["services/*"],
"@models/*": ["models/*"],
"@middleware/*": ["middleware/*"],
"@utils/*": ["utils/*"],
"@config/*": ["config/*"],
"@types/*": ["types/*"],
"@db/*": ["database/*"],
"@routes/*": ["routes/*"]
}
}
}
// Express 应用中使用
import express from 'express';
import { UserController } from '@controllers/UserController';
import { AuthMiddleware } from '@middleware/AuthMiddleware';
import { UserService } from '@services/UserService';
import { DatabaseConfig } from '@config/database';
import type { AuthRequest } from '@types/request';
const router = express.Router();
router.get('/users',
AuthMiddleware.verify,
UserController.getUsers
);
高级配置技巧:
条件路径映射:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
// 根据构建目标选择不同实现
"logger": [
"src/utils/logger.development.ts",
"src/utils/logger.production.ts"
],
// 功能特性开关
"feature/*": [
"src/features/enabled/*",
"src/features/disabled/*"
]
}
}
}
Webpack 集成配置:
// webpack.config.js
const path = require('path');
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@types': path.resolve(__dirname, 'src/types'),
'@assets': path.resolve(__dirname, 'src/assets')
},
extensions: ['.ts', '.tsx', '.js', '.jsx']
}
};
Vite 集成配置:
// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@types': path.resolve(__dirname, 'src/types')
}
}
});
调试模块解析:
使用 TypeScript 编译器调试:
# 显示模块解析过程
tsc --traceResolution
# 显示包含的文件
tsc --listFiles
# 显示详细信息
tsc --explainFiles
在 tsconfig.json 中启用调试:
{
"compilerOptions": {
"traceResolution": true,
"listFiles": true,
"explainFiles": true
}
}
常见问题和解决方案:
路径映射不工作:
// 问题:路径映射不生效
// 解决方案:检查 baseUrl 设置
{
"compilerOptions": {
"baseUrl": "./", // 必须设置 baseUrl
"paths": {
"@/*": ["src/*"]
}
}
}
编译器和构建工具不一致:
// 确保 webpack/vite 的别名与 TypeScript 路径映射一致
// webpack.config.js
module.exports = {
resolve: {
alias: {
// 与 tsconfig.json 中的 paths 保持一致
'@': path.resolve(__dirname, 'src')
}
}
};
测试环境配置:
// jest.config.js
{
"moduleNameMapping": {
"^@/(.*)$": "<rootDir>/src/$1",
"^@components/(.*)$": "<rootDir>/src/components/$1"
}
}
最佳实践:
What are Conditional Types? Please give examples.
What are Conditional Types? Please give examples.
答案:
条件类型(Conditional Types)是 TypeScript 中基于条件判断来选择类型的高级类型特性,语法为 T extends U ? X : Y,类似于三元运算符但作用于类型层面。
基础语法:
简单条件类型:
// 基础条件类型语法
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
type Test3 = IsString<"hello">; // true
// 实用的条件类型
type NonNull<T> = T extends null | undefined ? never : T;
type ValidString = NonNull<string>; // string
type ValidNumber = NonNull<number | null>; // number
type NeverType = NonNull<null>; // never
内置条件类型:
// TypeScript 内置的条件类型
type MyNonNullable<T> = T extends null | undefined ? never : T;
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
// 使用示例
function add(a: number, b: number): number {
return a + b;
}
type AddParams = MyParameters<typeof add>; // [number, number]
type AddReturn = MyReturnType<typeof add>; // number
infer 关键字:
类型推断:
// 推断返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 推断参数类型
type FirstParameter<T> = T extends (first: infer P, ...args: any[]) => any ? P : never;
// 推断数组元素类型
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type StringArray = ArrayElement<string[]>; // string
type NumberElement = ArrayElement<number[]>; // number
// 推断 Promise 内容类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
type PromiseString = Awaited<Promise<string>>; // string
type DirectString = Awaited<string>; // string
复杂类型推断:
// 深层嵌套推断
type DeepArray<T> = T extends (infer U)[]
? U extends (infer V)[]
? DeepArray<V>
: U
: T;
type Deep1 = DeepArray<string[][][]>; // string
type Deep2 = DeepArray<number[]>; // number
// 对象属性推断
type GetProperty<T, K> = T extends { [P in keyof T]: T[P] }
? K extends keyof T
? T[K]
: never
: never;
interface User {
id: number;
name: string;
email: string;
}
type UserName = GetProperty<User, 'name'>; // string
type UserId = GetProperty<User, 'id'>; // number
实际应用场景:
API 响应类型处理:
// 根据请求方法确定响应类型
type ApiMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiResponse<M extends ApiMethod, T> = M extends 'GET'
? { data: T }
: M extends 'POST'
? { data: T; id: string }
: M extends 'PUT'
? { data: T; updated: Date }
: M extends 'DELETE'
? { success: boolean }
: never;
// 使用
type GetUserResponse = ApiResponse<'GET', User>; // { data: User }
type CreateUserResponse = ApiResponse<'POST', User>; // { data: User; id: string }
type DeleteUserResponse = ApiResponse<'DELETE', User>; // { success: boolean }
// 函数重载辅助
function apiCall<M extends ApiMethod, T>(
method: M,
data?: T
): Promise<ApiResponse<M, T>>;
function apiCall(method: ApiMethod, data?: any): Promise<any> {
// 实现逻辑
return Promise.resolve({});
}
表单验证类型:
// 条件类型实现表单验证
type ValidationRule<T> = T extends string
? { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp }
: T extends number
? { required?: boolean; min?: number; max?: number }
: T extends boolean
? { required?: boolean }
: { required?: boolean };
type FormValidation<T> = {
[K in keyof T]: ValidationRule<T[K]>;
};
interface LoginForm {
username: string;
password: string;
age: number;
rememberMe: boolean;
}
type LoginValidation = FormValidation<LoginForm>;
/*
{
username: { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp };
password: { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp };
age: { required?: boolean; min?: number; max?: number };
rememberMe: { required?: boolean };
}
*/
高级类型工具:
// 递归条件类型
type DeepReadonly<T> = T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
type DeepPartial<T> = T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T;
// 使用示例
interface NestedObject {
user: {
profile: {
name: string;
settings: {
theme: string;
notifications: boolean;
};
};
};
}
type ReadonlyNested = DeepReadonly<NestedObject>;
type PartialNested = DeepPartial<NestedObject>;
// 联合类型分发
type ToArray<T> = T extends any ? T[] : never;
type StringOrNumber = string | number;
type ArrayUnion = ToArray<StringOrNumber>; // string[] | number[]
// 排除特定类型
type NonFunction<T> = T extends Function ? never : T;
interface MixedObject {
name: string;
age: number;
getName: () => string;
setAge: (age: number) => void;
}
type OnlyData = {
[K in keyof MixedObject]: NonFunction<MixedObject[K]>;
};
// { name: string; age: number; getName: never; setAge: never; }
状态机类型:
// 状态机条件类型
type StateTransition<
CurrentState extends string,
Action extends string
> = CurrentState extends 'idle'
? Action extends 'start' ? 'loading' : 'idle'
: CurrentState extends 'loading'
? Action extends 'success' ? 'success'
: Action extends 'error' ? 'error'
: Action extends 'cancel' ? 'idle'
: 'loading'
: CurrentState extends 'success' | 'error'
? Action extends 'reset' ? 'idle' : CurrentState
: never;
type IdleToLoading = StateTransition<'idle', 'start'>; // 'loading'
type LoadingToSuccess = StateTransition<'loading', 'success'>; // 'success'
type SuccessToIdle = StateTransition<'success', 'reset'>; // 'idle'
分布式条件类型:
// 联合类型的分布式特性
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type Example = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type Example2 = Extract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'
// 防止分布式行为
type NoDistribute<T, U> = [T] extends [U] ? true : false;
type Test1 = NoDistribute<'a' | 'b', string>; // true
type Test2 = ('a' | 'b') extends string ? true : false; // true(分布式)
最佳实践:
What are Mapped Types? What are the common built-in mapped types?
What are Mapped Types? What are the common built-in mapped types?
答案:
映射类型(Mapped Types)是 TypeScript 中基于现有类型创建新类型的方式,通过遍历类型的属性并应用转换规则来生成新的类型结构。
基础映射类型语法:
// 基础映射类型语法
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P]; // 移除可选性
};
// 使用示例
interface User {
id: number;
name: string;
email?: string;
}
type ReadonlyUser = Readonly<User>;
/*
{
readonly id: number;
readonly name: string;
readonly email?: string;
}
*/
type PartialUser = Partial<User>;
/*
{
id?: number;
name?: string;
email?: string;
}
*/
type RequiredUser = Required<User>;
/*
{
id: number;
name: string;
email: string; // 不再可选
}
*/
常见内置映射类型:
基础工具类型:
interface Product {
id: number;
name: string;
price: number;
description?: string;
category: string;
}
// Pick - 选择特定属性
type ProductSummary = Pick<Product, 'id' | 'name' | 'price'>;
/*
{
id: number;
name: string;
price: number;
}
*/
// Omit - 排除特定属性
type ProductWithoutId = Omit<Product, 'id'>;
/*
{
name: string;
price: number;
description?: string;
category: string;
}
*/
// Record - 创建键值对类型
type StatusMap = Record<'loading' | 'success' | 'error', string>;
/*
{
loading: string;
success: string;
error: string;
}
*/
// Exclude 和 Extract - 联合类型过滤
type StringOrNumber = string | number | boolean;
type OnlyStringOrNumber = Exclude<StringOrNumber, boolean>; // string | number
type OnlyString = Extract<StringOrNumber, string>; // string
高级工具类型:
// ReturnType - 获取函数返回类型
function getUser(id: number): Promise<User> {
return Promise.resolve({} as User);
}
type GetUserReturn = ReturnType<typeof getUser>; // Promise<User>
// Parameters - 获取函数参数类型
type GetUserParams = Parameters<typeof getUser>; // [number]
// ConstructorParameters - 获取构造函数参数
class UserService {
constructor(apiUrl: string, timeout: number) {}
}
type UserServiceParams = ConstructorParameters<typeof UserService>; // [string, number]
// InstanceType - 获取构造函数实例类型
type UserServiceInstance = InstanceType<typeof UserService>; // UserService
自定义映射类型:
属性修饰符映射:
// 深度只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 深度可选
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// 可空类型
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// 使用示例
interface Config {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
};
};
database: {
url: string;
timeout: number;
};
}
type ReadonlyConfig = DeepReadonly<Config>;
type PartialConfig = DeepPartial<Config>;
type NullableConfig = Nullable<Config>;
键名转换映射:
// 添加前缀
type AddPrefix<T, Prefix extends string> = {
[P in keyof T as `${Prefix}${string & P}`]: T[P];
};
// 添加后缀
type AddSuffix<T, Suffix extends string> = {
[P in keyof T as `${string & P}${Suffix}`]: T[P];
};
// 转换为 getter 类型
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
// 转换为 setter 类型
type Setters<T> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (value: T[P]) => void;
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
/*
{
getName: () => string;
getAge: () => number;
}
*/
type PersonSetters = Setters<Person>;
/*
{
setName: (value: string) => void;
setAge: (value: number) => void;
}
*/
type PrefixedPerson = AddPrefix<Person, 'user'>;
/*
{
username: string;
userage: number;
}
*/
实际应用场景:
表单状态管理:
// 表单字段状态
type FieldState<T> = {
value: T;
error?: string;
touched: boolean;
dirty: boolean;
};
// 表单状态映射
type FormState<T> = {
[P in keyof T]: FieldState<T[P]>;
};
// 表单验证规则
type ValidationRules<T> = {
[P in keyof T]?: (value: T[P]) => string | undefined;
};
// 使用示例
interface LoginForm {
email: string;
password: string;
}
type LoginFormState = FormState<LoginForm>;
/*
{
email: FieldState<string>;
password: FieldState<string>;
}
*/
const loginState: LoginFormState = {
email: { value: '', touched: false, dirty: false },
password: { value: '', touched: false, dirty: false }
};
const validationRules: ValidationRules<LoginForm> = {
email: (value) => value.includes('@') ? undefined : 'Invalid email',
password: (value) => value.length >= 6 ? undefined : 'Too short'
};
API 客户端生成:
// API 端点定义
interface ApiEndpoints {
getUser: { params: { id: number }; response: User };
createUser: { body: Omit<User, 'id'>; response: User };
updateUser: { params: { id: number }; body: Partial<User>; response: User };
deleteUser: { params: { id: number }; response: { success: boolean } };
}
// 生成客户端方法类型
type ApiClient<T> = {
[K in keyof T]: T[K] extends { params: infer P; response: infer R }
? (params: P) => Promise<R>
: T[K] extends { body: infer B; response: infer R }
? (body: B) => Promise<R>
: T[K] extends { params: infer P; body: infer B; response: infer R }
? (params: P, body: B) => Promise<R>
: never;
};
type UserApiClient = ApiClient<ApiEndpoints>;
/*
{
getUser: (params: { id: number }) => Promise<User>;
createUser: (body: Omit<User, 'id'>) => Promise<User>;
updateUser: (params: { id: number }, body: Partial<User>) => Promise<User>;
deleteUser: (params: { id: number }) => Promise<{ success: boolean }>;
}
*/
状态管理类型:
// 状态 action 生成
type StateActions<T> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (value: T[P]) => void;
} & {
[P in keyof T as `reset${Capitalize<string & P>}`]: () => void;
};
// 状态选择器生成
type StateSelectors<T> = {
[P in keyof T as `select${Capitalize<string & P>}`]: () => T[P];
};
interface AppState {
user: User | null;
loading: boolean;
error: string | null;
}
type AppStateActions = StateActions<AppState>;
/*
{
setUser: (value: User | null) => void;
setLoading: (value: boolean) => void;
setError: (value: string | null) => void;
resetUser: () => void;
resetLoading: () => void;
resetError: () => void;
}
*/
type AppStateSelectors = StateSelectors<AppState>;
/*
{
selectUser: () => User | null;
selectLoading: () => boolean;
selectError: () => string | null;
}
*/
条件映射类型:
// 过滤特定类型的属性
type FilterByType<T, U> = {
[P in keyof T]: T[P] extends U ? T[P] : never;
};
// 只保留函数类型的属性
type FunctionProperties<T> = {
[P in keyof T]: T[P] extends Function ? P : never;
}[keyof T];
// 只保留非函数类型的属性
type NonFunctionProperties<T> = {
[P in keyof T]: T[P] extends Function ? never : P;
}[keyof T];
interface MixedInterface {
name: string;
age: number;
getName: () => string;
setAge: (age: number) => void;
isActive: boolean;
}
type FunctionProps = FunctionProperties<MixedInterface>; // "getName" | "setAge"
type DataProps = NonFunctionProperties<MixedInterface>; // "name" | "age" | "isActive"
// 分离数据和方法
type DataOnly<T> = Pick<T, NonFunctionProperties<T>>;
type MethodsOnly<T> = Pick<T, FunctionProperties<T>>;
type MixedData = DataOnly<MixedInterface>;
/*
{
name: string;
age: number;
isActive: boolean;
}
*/
type MixedMethods = MethodsOnly<MixedInterface>;
/*
{
getName: () => string;
setAge: (age: number) => void;
}
*/
最佳实践:
What are index type queries (keyof) and indexed access types (T[K])?
What are index type queries (keyof) and indexed access types (T[K])?
What is the role of the infer keyword and its usage scenarios?
What is the role of the infer keyword and its usage scenarios?
答案:
infer 关键字用于条件类型中进行类型推断,允许在类型匹配过程中提取和使用某个类型的一部分。
基础用法:
函数类型推断:
// 推断返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser(): Promise<{ id: number; name: string }> {
return Promise.resolve({ id: 1, name: "张三" });
}
type UserReturn = ReturnType<typeof getUser>; // Promise<{ id: number; name: string }>
// 推断参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function createUser(name: string, age: number, email: string): void {}
type CreateUserParams = Parameters<typeof createUser>; // [string, number, string]
// 推断第一个参数
type FirstParameter<T> = T extends (first: infer F, ...args: any[]) => any ? F : never;
type FirstParam = FirstParameter<typeof createUser>; // string
Promise 类型推断:
// 提取 Promise 内部类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
type StringPromise = Awaited<Promise<string>>; // string
type NumberPromise = Awaited<Promise<number>>; // number
type DirectType = Awaited<string>; // string (非 Promise)
// 深层 Promise 推断
type DeepAwaited<T> = T extends Promise<infer U>
? U extends Promise<any>
? DeepAwaited<U>
: U
: T;
type NestedPromise = DeepAwaited<Promise<Promise<string>>>; // string
高级应用:
数组类型推断:
// 数组元素类型推断
type ArrayElement<T> = T extends (infer U)[] ? U : never;
type StringArray = ArrayElement<string[]>; // string
type NumberArray = ArrayElement<number[]>; // number
type MixedArray = ArrayElement<(string | number)[]>; // string | number
// 元组首尾类型推断
type Head<T extends readonly any[]> = T extends readonly [infer H, ...any[]] ? H : never;
type Tail<T extends readonly any[]> = T extends readonly [any, ...infer Rest] ? Rest : never;
type First = Head<[1, 2, 3, 4]>; // 1
type Rest = Tail<[1, 2, 3, 4]>; // [2, 3, 4]
// 反转数组类型
type Reverse<T extends readonly any[]> = T extends readonly [...infer Rest, infer Last]
? [Last, ...Reverse<Rest>]
: [];
type Reversed = Reverse<[1, 2, 3, 4]>; // [4, 3, 2, 1]
对象类型推断:
// 推断对象属性值类型
type ValueOf<T> = T extends { [key: string]: infer V } ? V : never;
interface User {
id: number;
name: string;
active: boolean;
}
type UserValues = ValueOf<User>; // number | string | boolean
// 推断函数对象的方法类型
type MethodNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T];
type Methods<T> = Pick<T, MethodNames<T>>;
interface Calculator {
value: number;
add(n: number): void;
subtract(n: number): void;
getValue(): number;
}
type CalcMethods = Methods<Calculator>;
// { add: (n: number) => void; subtract: (n: number) => void; getValue: () => number; }
实际应用场景:
API 响应处理:
// 从 fetch 响应推断数据类型
type ExtractData<T> = T extends { data: infer D } ? D : never;
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}
type UserData = ExtractData<ApiResponse<User>>; // User
// 推断嵌套API响应
type NestedResponse<T> = T extends {
result: { items: infer Items }
} ? Items : never;
interface PagedResponse<T> {
result: {
items: T[];
total: number;
page: number;
};
}
type ItemsType = NestedResponse<PagedResponse<User>>; // User[]
事件系统类型推断:
// 从事件处理器推断事件类型
type EventType<T> = T extends (event: infer E) => any ? E : never;
type ClickHandler = (event: MouseEvent) => void;
type KeyHandler = (event: KeyboardEvent) => void;
type ClickEvent = EventType<ClickHandler>; // MouseEvent
type KeyEvent = EventType<KeyHandler>; // KeyboardEvent
// 通用事件系统
type EventMap = {
click: MouseEvent;
keydown: KeyboardEvent;
scroll: UIEvent;
};
type ExtractEventType<T extends keyof EventMap> = EventMap[T];
function addEventListener<T extends keyof EventMap>(
type: T,
handler: (event: ExtractEventType<T>) => void
): void {
// 实现
}
addEventListener('click', (e) => {
// e 的类型自动推断为 MouseEvent
console.log(e.clientX, e.clientY);
});
状态管理类型推断:
// 从 reducer 推断 state 类型
type StateFromReducer<T> = T extends (state: infer S, action: any) => any ? S : never;
type ActionFromReducer<T> = T extends (state: any, action: infer A) => any ? A : never;
function userReducer(
state: { users: User[]; loading: boolean },
action: { type: 'ADD_USER'; user: User } | { type: 'SET_LOADING'; loading: boolean }
) {
return state;
}
type UserState = StateFromReducer<typeof userReducer>;
// { users: User[]; loading: boolean }
type UserAction = ActionFromReducer<typeof userReducer>;
// { type: 'ADD_USER'; user: User } | { type: 'SET_LOADING'; loading: boolean }
工具类型构建:
// 构建复杂的工具类型
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
// 从类构造函数推断实例类型
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
class UserService {
getUsers(): User[] { return []; }
}
type ServiceInstance = InstanceType<typeof UserService>; // UserService
// 递归类型推断
type Flatten<T> = T extends (infer U)[]
? U extends (infer V)[]
? Flatten<V>
: U
: T;
type Deep = Flatten<string[][][]>; // string
最佳实践:
How to implement Type Challenges? Please give examples.
How to implement Type Challenges? Please give examples.
How is type compatibility determined in TypeScript?
How is type compatibility determined in TypeScript?
What is the never type? What are its application scenarios?
What is the never type? What are its application scenarios?
答案:
never 类型表示永不存在的值的类型,是 TypeScript 类型系统的底部类型,通常用于表示不可达的代码路径和详尽性检查。
never 类型特征:
函数永不返回:
// 抛出异常的函数
function throwError(message: string): never {
throw new Error(message);
}
// 无限循环的函数
function infiniteLoop(): never {
while (true) {
console.log("Running forever...");
}
}
// 使用 never 类型
function processValue(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
} else if (typeof value === "number") {
return value.toString();
}
// 这里的 value 类型是 never,因为所有可能的情况都已处理
return throwError("Unexpected type");
}
详尽性检查:
type Action = 'CREATE' | 'UPDATE' | 'DELETE';
function handleAction(action: Action): string {
switch (action) {
case 'CREATE':
return 'Creating...';
case 'UPDATE':
return 'Updating...';
case 'DELETE':
return 'Deleting...';
default:
// 确保所有情况都被处理
const _exhaustiveCheck: never = action;
throw new Error(`Unhandled action: ${_exhaustiveCheck}`);
}
}
// 如果添加新的 Action 类型,编译器会报错
type ExtendedAction = Action | 'ARCHIVE';
function handleExtendedAction(action: ExtendedAction): string {
switch (action) {
case 'CREATE':
return 'Creating...';
case 'UPDATE':
return 'Updating...';
case 'DELETE':
return 'Deleting...';
// 缺少 'ARCHIVE' 处理,编译器会在 default 中报错
default:
const _exhaustiveCheck: never = action; // 编译错误!
throw new Error(`Unhandled action`);
}
}
实际应用场景:
状态机验证:
type State = 'idle' | 'loading' | 'success' | 'error';
interface AppState {
status: State;
data?: any;
error?: string;
}
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function renderStatus(state: AppState): string {
switch (state.status) {
case 'idle':
return 'Ready to start';
case 'loading':
return 'Loading...';
case 'success':
return `Data: ${state.data}`;
case 'error':
return `Error: ${state.error}`;
default:
return assertNever(state.status); // 确保所有状态都被处理
}
}
条件类型中的过滤:
// 过滤掉函数类型
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
// 提取非函数属性
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Example {
name: string;
age: number;
getName: () => string;
setAge: (age: number) => void;
}
type DataOnly = NonFunctionProperties<Example>;
// { name: string; age: number; }
联合类型缩减:
type Exclude<T, U> = T extends U ? never : T;
type WithoutString = Exclude<string | number | boolean, string>; // number | boolean
// 自定义过滤工具
type FilterFlags<Base, Condition> = {
[Key in keyof Base]: Base[Key] extends Condition ? never : Key;
}[keyof Base];
type AllowedNames = FilterFlags<{
name: string;
count: number;
isValid: boolean;
callback: () => void;
}, Function>; // "name" | "count" | "isValid"
never 类型的特性:
How does TypeScript implement type-safe method chaining?
How does TypeScript implement type-safe method chaining?
答案:
TypeScript 通过返回类型为 this 或自定义类型的方法实现类型安全的链式调用,确保每个调用步骤都有正确的类型推断和代码提示。
基础链式调用实现:
使用 this 类型:
class QueryBuilder<T> {
private conditions: string[] = [];
private selectFields: string[] = [];
private orderByField?: string;
private limitCount?: number;
constructor(private tableName: string) {}
// 返回 this 支持链式调用
select(...fields: (keyof T)[]): this {
this.selectFields.push(...fields as string[]);
return this;
}
where(condition: string): this {
this.conditions.push(condition);
return this;
}
orderBy(field: keyof T): this {
this.orderByField = field as string;
return this;
}
limit(count: number): this {
this.limitCount = count;
return this;
}
// 终结方法,返回查询字符串
build(): string {
let query = `SELECT ${this.selectFields.join(', ')} FROM ${this.tableName}`;
if (this.conditions.length > 0) {
query += ` WHERE ${this.conditions.join(' AND ')}`;
}
if (this.orderByField) {
query += ` ORDER BY ${this.orderByField}`;
}
if (this.limitCount) {
query += ` LIMIT ${this.limitCount}`;
}
return query;
}
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
age: number;
}
const query = new QueryBuilder<User>('users')
.select('name', 'email') // 类型安全的字段选择
.where('age > 18')
.orderBy('name') // 类型检查字段名
.limit(10)
.build();
console.log(query);
// SELECT name, email FROM users WHERE age > 18 ORDER BY name LIMIT 10
状态管理的链式调用:
// 状态转换的类型安全链式调用
class StateMachine<TState extends string, TEvent extends string> {
private currentState: TState;
private transitions: Record<TState, Partial<Record<TEvent, TState>>> = {} as any;
constructor(initialState: TState) {
this.currentState = initialState;
}
// 定义状态转换规则
from(state: TState): StateTransitionBuilder<TState, TEvent> {
return new StateTransitionBuilder(state, this.transitions);
}
// 触发事件
trigger(event: TEvent): this {
const nextState = this.transitions[this.currentState]?.[event];
if (nextState) {
this.currentState = nextState;
}
return this;
}
getState(): TState {
return this.currentState;
}
}
class StateTransitionBuilder<TState extends string, TEvent extends string> {
constructor(
private state: TState,
private transitions: Record<TState, Partial<Record<TEvent, TState>>>
) {}
to(targetState: TState): EventBuilder<TState, TEvent> {
return new EventBuilder(this.state, targetState, this.transitions);
}
}
class EventBuilder<TState extends string, TEvent extends string> {
constructor(
private fromState: TState,
private toState: TState,
private transitions: Record<TState, Partial<Record<TEvent, TState>>>
) {}
on(event: TEvent): StateTransitionBuilder<TState, TEvent> {
if (!this.transitions[this.fromState]) {
this.transitions[this.fromState] = {};
}
this.transitions[this.fromState]![event] = this.toState;
return new StateTransitionBuilder(this.fromState, this.transitions);
}
}
// 使用示例
type OrderState = 'pending' | 'confirmed' | 'shipped' | 'delivered' | 'cancelled';
type OrderEvent = 'confirm' | 'ship' | 'deliver' | 'cancel';
const orderStateMachine = new StateMachine<OrderState, OrderEvent>('pending')
.from('pending').to('confirmed').on('confirm')
.from('pending').to('cancelled').on('cancel')
.from('confirmed').to('shipped').on('ship')
.from('confirmed').to('cancelled').on('cancel')
.from('shipped').to('delivered').on('deliver');
// 类型安全的状态转换
orderStateMachine
.trigger('confirm') // pending -> confirmed
.trigger('ship') // confirmed -> shipped
.trigger('deliver'); // shipped -> delivered
高级链式调用模式:
条件链式调用:
class ConditionalChain<T> {
constructor(private value: T) {}
// 条件执行
when<R>(
condition: (value: T) => boolean,
action: (value: T) => R
): ConditionalChain<R | T> {
if (condition(this.value)) {
return new ConditionalChain(action(this.value));
}
return new ConditionalChain(this.value);
}
// 映射转换
map<R>(fn: (value: T) => R): ConditionalChain<R> {
return new ConditionalChain(fn(this.value));
}
// 过滤
filter(predicate: (value: T) => boolean): ConditionalChain<T | null> {
return new ConditionalChain(predicate(this.value) ? this.value : null);
}
// 获取最终值
get(): T {
return this.value;
}
}
// 使用示例
const result = new ConditionalChain(10)
.when(x => x > 5, x => x * 2) // 10 > 5, so 10 * 2 = 20
.map(x => x + 1) // 20 + 1 = 21
.when(x => x % 2 === 0, x => x / 2) // 21 is odd, no change
.filter(x => x > 15) // 21 > 15, so keep it
.get(); // 21
异步链式调用:
class AsyncChain<T> {
constructor(private promise: Promise<T>) {}
// 异步映射
map<R>(fn: (value: T) => R | Promise<R>): AsyncChain<R> {
return new AsyncChain(this.promise.then(fn));
}
// 异步过滤
filter(predicate: (value: T) => boolean | Promise<boolean>): AsyncChain<T | null> {
return new AsyncChain(
this.promise.then(async value => {
const shouldKeep = await predicate(value);
return shouldKeep ? value : null;
})
);
}
// 错误处理
catch<R>(handler: (error: any) => R): AsyncChain<T | R> {
return new AsyncChain(this.promise.catch(handler));
}
// 副作用操作
tap(fn: (value: T) => void | Promise<void>): AsyncChain<T> {
return new AsyncChain(
this.promise.then(async value => {
await fn(value);
return value;
})
);
}
// 获取 Promise
toPromise(): Promise<T> {
return this.promise;
}
// 等待结果
async get(): Promise<T> {
return this.promise;
}
}
// 使用示例
async function fetchUserData(id: number): Promise<User> {
return fetch(`/api/users/${id}`).then(res => res.json());
}
const userData = await new AsyncChain(fetchUserData(1))
.map(user => ({ ...user, displayName: `${user.name} (${user.email})` }))
.filter(user => user.age >= 18)
.tap(user => console.log('Processing user:', user.displayName))
.catch(error => ({ id: -1, name: 'Unknown', email: '', age: 0 }))
.get();
实际应用场景:
HTTP 客户端:
class HttpClient {
private url: string = '';
private requestHeaders: Record<string, string> = {};
private requestBody?: any;
private method: string = 'GET';
baseURL(url: string): this {
this.url = url;
return this;
}
path(segment: string): this {
this.url += `/${segment}`;
return this;
}
header(key: string, value: string): this {
this.requestHeaders[key] = value;
return this;
}
headers(headers: Record<string, string>): this {
Object.assign(this.requestHeaders, headers);
return this;
}
auth(token: string): this {
this.requestHeaders['Authorization'] = `Bearer ${token}`;
return this;
}
json(data: any): this {
this.requestBody = JSON.stringify(data);
this.requestHeaders['Content-Type'] = 'application/json';
return this;
}
get(): this {
this.method = 'GET';
return this;
}
post(): this {
this.method = 'POST';
return this;
}
put(): this {
this.method = 'PUT';
return this;
}
delete(): this {
this.method = 'DELETE';
return this;
}
async send<T = any>(): Promise<T> {
const response = await fetch(this.url, {
method: this.method,
headers: this.requestHeaders,
body: this.requestBody
});
return response.json();
}
}
// 使用示例
const client = new HttpClient();
// GET 请求
const users = await client
.baseURL('https://api.example.com')
.path('users')
.header('Accept', 'application/json')
.auth('your-token-here')
.get()
.send<User[]>();
// POST 请求
const newUser = await client
.baseURL('https://api.example.com')
.path('users')
.auth('your-token-here')
.json({ name: '张三', email: '[email protected]' })
.post()
.send<User>();
数据验证链:
class Validator<T> {
private errors: string[] = [];
constructor(private value: T) {}
required(message: string = 'Value is required'): this {
if (this.value == null || this.value === '') {
this.errors.push(message);
}
return this;
}
min(minValue: number, message: string = `Value must be at least ${minValue}`): this {
if (typeof this.value === 'number' && this.value < minValue) {
this.errors.push(message);
}
return this;
}
max(maxValue: number, message: string = `Value must be at most ${maxValue}`): this {
if (typeof this.value === 'number' && this.value > maxValue) {
this.errors.push(message);
}
return this;
}
email(message: string = 'Invalid email format'): this {
if (typeof this.value === 'string') {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.value)) {
this.errors.push(message);
}
}
return this;
}
custom(fn: (value: T) => boolean, message: string): this {
if (!fn(this.value)) {
this.errors.push(message);
}
return this;
}
validate(): { isValid: boolean; errors: string[]; value: T } {
return {
isValid: this.errors.length === 0,
errors: [...this.errors],
value: this.value
};
}
}
// 使用示例
function validateUser(user: Partial<User>) {
const nameResult = new Validator(user.name)
.required('Name is required')
.custom(name => typeof name === 'string' && name.length >= 2, 'Name must be at least 2 characters')
.validate();
const emailResult = new Validator(user.email)
.required('Email is required')
.email()
.validate();
const ageResult = new Validator(user.age)
.required('Age is required')
.min(0, 'Age cannot be negative')
.max(120, 'Age seems unrealistic')
.validate();
return {
name: nameResult,
email: emailResult,
age: ageResult,
isValid: nameResult.isValid && emailResult.isValid && ageResult.isValid
};
}
类型安全的关键技巧:
泛型约束确保类型安全:
interface Chainable {
[key: string]: (...args: any[]) => this;
}
class TypeSafeChain<T extends Chainable> {
constructor(private instance: T) {}
call<K extends keyof T>(
method: K,
...args: T[K] extends (...args: infer P) => any ? P : never
): this {
(this.instance[method] as any)(...args);
return this;
}
get(): T {
return this.instance;
}
}
状态类型跟踪:
type BuilderState = 'initial' | 'configured' | 'ready';
class StatefulBuilder<TState extends BuilderState = 'initial'> {
private config: any = {};
configure(options: any): StatefulBuilder<'configured'> {
this.config = options;
return this as any;
}
// 只有在 configured 状态才能调用 build
build(this: StatefulBuilder<'configured'>): any {
return { ...this.config, timestamp: Date.now() };
}
}
// 使用时会有编译时状态检查
const builder = new StatefulBuilder()
.configure({ name: 'test' }) // 现在是 configured 状态
.build(); // 可以调用 build
// const invalid = new StatefulBuilder().build(); // 编译错误!
最佳实践:
this 类型支持继承中的链式调用build(), execute(), get())How to implement a DeepReadonly type?
How to implement a DeepReadonly type?
答案:
DeepReadonly 类型通过递归映射将对象的所有属性(包括嵌套对象)都设置为只读,是高级类型编程的典型应用。
基础实现:
简单版本:
// 基础深度只读实现
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 使用示例
interface User {
id: number;
name: string;
profile: {
avatar: string;
settings: {
theme: string;
notifications: boolean;
};
};
}
type ReadonlyUser = DeepReadonly<User>;
/*
{
readonly id: number;
readonly name: string;
readonly profile: {
readonly avatar: string;
readonly settings: {
readonly theme: string;
readonly notifications: boolean;
};
};
}
*/
改进版本(处理数组和函数):
// 更完善的深度只读实现
type DeepReadonly<T> = T extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>> // 处理数组
: T extends Function
? T // 函数保持不变
: T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> } // 递归处理对象
: T; // 基本类型保持不变
// 测试复杂结构
interface ComplexData {
users: User[];
config: {
api: {
baseUrl: string;
endpoints: string[];
};
features: {
[key: string]: boolean;
};
};
methods: {
getUser: (id: number) => User;
updateUser: (user: User) => void;
};
}
type ReadonlyComplexData = DeepReadonly<ComplexData>;
/*
{
readonly users: ReadonlyArray<DeepReadonly<User>>;
readonly config: {
readonly api: {
readonly baseUrl: string;
readonly endpoints: ReadonlyArray<string>;
};
readonly features: {
readonly [key: string]: boolean;
};
};
readonly methods: {
readonly getUser: (id: number) => User;
readonly updateUser: (user: User) => void;
};
}
*/
高级实现:
处理特殊类型:
// 更精确的深度只读实现
type Primitive = string | number | boolean | bigint | symbol | undefined | null;
type DeepReadonly<T> = T extends Primitive
? T
: T extends (infer U)[]
? ReadonlyArray<DeepReadonly<U>>
: T extends Map<infer K, infer V>
? ReadonlyMap<K, DeepReadonly<V>>
: T extends Set<infer U>
? ReadonlySet<DeepReadonly<U>>
: T extends Function
? T
: T extends Date
? T
: {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
// 测试各种类型
interface TestData {
str: string;
num: number;
bool: boolean;
date: Date;
arr: number[];
map: Map<string, User>;
set: Set<string>;
fn: () => void;
nested: {
deep: {
value: string;
};
};
}
type ReadonlyTestData = DeepReadonly<TestData>;
可选的深度控制:
// 控制递归深度的深度只读
type DeepReadonlyWithDepth<T, Depth extends number = 10> = [Depth] extends [0]
? T
: T extends Primitive
? T
: T extends (infer U)[]
? ReadonlyArray<DeepReadonlyWithDepth<U, Prev<Depth>>>
: T extends Function
? T
: {
readonly [P in keyof T]: DeepReadonlyWithDepth<T[P], Prev<Depth>>;
};
// 辅助类型:递减数字
type Prev<T extends number> = T extends 0
? 0
: T extends 1
? 0
: T extends 2
? 1
: T extends 3
? 2
: T extends 4
? 3
: T extends 5
? 4
: number;
// 限制递归深度为 2 层
type ShallowReadonly = DeepReadonlyWithDepth<ComplexData, 2>;
实际应用场景:
状态管理:
// Redux 状态类型
interface AppState {
user: {
current: User | null;
preferences: {
theme: 'light' | 'dark';
language: string;
notifications: {
email: boolean;
push: boolean;
};
};
};
data: {
users: User[];
products: Product[];
cache: Map<string, any>;
};
}
// 只读状态,防止意外修改
type ReadonlyAppState = DeepReadonly<AppState>;
// 在 reducer 中使用
function rootReducer(
state: ReadonlyAppState,
action: any
): AppState {
// 只能返回新的状态,不能修改现有状态
return {
...state,
// 状态更新逻辑
};
}
配置对象:
// 应用配置
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
options: {
ssl: boolean;
timeout: number;
pool: {
min: number;
max: number;
};
};
};
api: {
baseUrl: string;
endpoints: Record<string, string>;
rateLimits: number[];
};
}
// 运行时不可修改的配置
type ReadonlyConfig = DeepReadonly<Config>;
function loadConfig(): ReadonlyConfig {
return {
database: {
host: "localhost",
port: 5432,
credentials: {
username: "admin",
password: "secret"
},
options: {
ssl: true,
timeout: 30000,
pool: { min: 2, max: 10 }
}
},
api: {
baseUrl: "https://api.example.com",
endpoints: {
users: "/users",
products: "/products"
},
rateLimits: [100, 1000, 5000]
}
};
}
const config = loadConfig();
// config.database.host = "newhost"; // 编译错误!
API 响应类型:
// API 响应应该是只读的
interface ApiResponse<T> {
data: T;
meta: {
timestamp: Date;
version: string;
pagination?: {
page: number;
total: number;
hasNext: boolean;
};
};
}
type ReadonlyApiResponse<T> = DeepReadonly<ApiResponse<T>>;
async function fetchUsers(): Promise<ReadonlyApiResponse<User[]>> {
const response = await fetch('/api/users');
return response.json();
}
// 使用时防止意外修改响应数据
const users = await fetchUsers();
// users.data.push(newUser); // 编译错误!
// users.meta.timestamp = new Date(); // 编译错误!
扩展应用:
// 可逆的深度只读(提供解除只读的工具)
type DeepMutable<T> = T extends ReadonlyArray<infer U>
? DeepMutable<U>[]
: T extends Function
? T
: T extends object
? { -readonly [P in keyof T]: DeepMutable<T[P]> }
: T;
// 选择性深度只读
type DeepReadonlyExcept<T, K extends keyof any = never> = {
readonly [P in keyof T]: P extends K
? T[P]
: T[P] extends object
? DeepReadonlyExcept<T[P], K>
: T[P];
};
// 排除某些属性的深度只读
type PartiallyReadonly = DeepReadonlyExcept<User, 'id'>;
// id 属性不会被设为只读,其他都是只读的
最佳实践:
What important features were added in TypeScript 4.x?
What important features were added in TypeScript 4.x?
- 考察点:最新特性与技术发展趋势。
答案:
TypeScript 4.x 系列引入了许多重要特性,显著提升了类型系统的表达能力、开发体验和性能,是 TypeScript 发展史上的重要里程碑。
TypeScript 4.0 主要特性:
可变元组类型(Variadic Tuple Types):
// 4.0 之前无法表达的类型
type Concat<T extends readonly unknown[], U extends readonly unknown[]> = [...T, ...U];
type Result1 = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]
type Result2 = Concat<['a'], [1, 2, 3]>; // ['a', 1, 2, 3]
// 函数参数展开
function curry<T extends readonly unknown[], U extends readonly unknown[], R>(
f: (...args: [...T, ...U]) => R,
...first: T
) {
return (...second: U): R => f(...first, ...second);
}
// 使用示例
function add(a: number, b: number, c: number): number {
return a + b + c;
}
const curriedAdd = curry(add, 1, 2); // (c: number) => number
const result = curriedAdd(3); // 6
标记联合类型(Labeled Tuple Elements):
// 为元组元素添加标签
type Range = [start: number, end: number];
type Point3D = [x: number, y: number, z: number];
// 函数参数更清晰
function createRange(...args: Range): number[] {
const [start, end] = args;
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
}
// 更好的代码提示
function distance(from: Point3D, to: Point3D): number {
const [x1, y1, z1] = from;
const [x2, y2, z2] = to;
return Math.sqrt((x2-x1)**2 + (y2-y1)**2 + (z2-z1)**2);
}
TypeScript 4.1 主要特性:
模板字面量类型(Template Literal Types):
// 动态创建字符串类型
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
// 实际应用:CSS 属性
type CSSProperties = 'margin' | 'padding' | 'border';
type CSSDirections = 'top' | 'right' | 'bottom' | 'left';
type CSSProperty = `${CSSProperties}-${CSSDirections}`;
// "margin-top" | "margin-right" | ... | "border-left"
// 事件处理器类型
type EventNames = 'click' | 'scroll' | 'mousemove';
type EventHandlers = {
[K in EventNames as `on${Capitalize<K>}`]: (event: Event) => void;
};
// { onClick: (event: Event) => void; onScroll: ...; onMousemove: ... }
键重映射(Key Remapping):
// 重新映射对象键
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
email: string;
}
type PersonGetters = Getters<Person>;
// {
// getName: () => string;
// getAge: () => number;
// getEmail: () => string;
// }
// 过滤属性
type RemoveKindField<T> = {
[K in keyof T as K extends 'kind' ? never : K]: T[K];
};
interface WithKind {
kind: string;
name: string;
value: number;
}
type WithoutKind = RemoveKindField<WithKind>; // { name: string; value: number; }
TypeScript 4.2 主要特性:
更智能的类型别名保留:
// 保留原始类型别名信息
type BasicPrimitive = number | string | boolean;
type Object = { x: number; y: string; };
type Tuple = [number, string];
// 在错误信息中会显示更清晰的类型名称
function process(value: BasicPrimitive): void {
// TypeScript 会显示 "BasicPrimitive" 而不是 "number | string | boolean"
}
前导/中间 Rest 元素:
// Rest 元素可以在中间位置
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];
let a: StringNumberBooleans = ["hello", 42, true, false, true];
let b: StringBooleansNumber = ["hello", true, false, 42];
let c: BooleansStringNumber = [true, false, true, "hello", 42];
TypeScript 4.3 主要特性:
override 关键字:
class Base {
someMethod(): void {
console.log("Base method");
}
}
class Derived extends Base {
// 明确标记重写方法
override someMethod(): void {
console.log("Derived method");
}
// 编译器会检查是否真的在重写
// override nonExistentMethod(): void {} // 错误!
}
静态索引签名:
class MyClass {
// 静态属性的索引签名
static [s: string]: boolean | ((x: number) => number);
static a = true;
static b = false;
static f = (x: number) => x * 2;
}
TypeScript 4.4 主要特性:
控制流分析改进(符号别名):
function foo(arg: unknown) {
const argIsString = typeof arg === "string";
if (argIsString) {
// TypeScript 现在知道 arg 是 string 类型
console.log(arg.toUpperCase());
}
}
精确的可选属性类型:
interface Person {
name: string;
age?: number; // 可能是 number 或 undefined,但不能是其他类型
}
// 4.4 中更严格的检查
const person: Person = {
name: "Alice",
age: undefined // 明确允许
};
// const invalid: Person = { name: "Bob", age: null }; // 错误!
TypeScript 4.5 主要特性:
Awaited 类型:
// 内置的 Awaited 工具类型
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<boolean | Promise<number>>; // boolean | number
// 自定义实现参考
type MyAwaited<T> = T extends null | undefined
? T
: T extends object & { then(onfulfilled: infer F, ...args: any): any }
? F extends (value: infer V, ...args: any) => any
? Awaited<V>
: never
: T;
类型修饰符改进:
// 更精确的 const 断言
const colors = ['red', 'green', 'blue'] as const;
type Color = typeof colors[number]; // "red" | "green" | "blue"
// 递归条件类型尾调用优化
type InfiniteLoop<T> = T extends string ? InfiniteLoop<T> : never;
TypeScript 4.6 主要特性:
代码操作和自动导入改进:
// 更好的自动导入建议
// 支持包导出的子路径
import { Component } from "some-ui-library/components";
import { utils } from "some-library/utils";
析构赋值中的 rest 类型改进:
interface Options {
width?: number;
height?: number;
color?: string;
}
function processOptions(options: Options) {
const { width, height, ...rest } = options;
// rest 的类型是 { color?: string }
}
TypeScript 4.7 主要特性:
instantiation 表达式:
function makeBox<T>(value: T) {
return { value };
}
// 实例化表达式
const makeStringBox = makeBox<string>;
const stringBox = makeStringBox("hello");
// 数组方法的特化
const numbers = [1, 2, 3, 4, 5];
const strings = numbers.map(String); // 明确的类型转换
variance 注解(实验性):
interface Producer<out T> {
produce(): T;
}
interface Consumer<in T> {
consume(value: T): void;
}
// 协变和逆变的显式标记
let p1: Producer<string>;
let p2: Producer<string | number> = p1; // OK,协变
TypeScript 4.8 主要特性:
交集和联合类型改进:
// 更好的交集类型处理
type A = { a: string } & { b: number };
type B = { a: string; b: number }; // 现在 A 和 B 更一致
// 未知类型的改进
function process<T>(value: T): T extends string ? string : T {
return value as any;
}
bind、call、apply 的改进:
function greet(name: string, age: number): string {
return `Hello ${name}, you are ${age} years old`;
}
// 更好的类型推断
const boundGreet = greet.bind(null, "Alice");
const result = boundGreet(25); // string
TypeScript 4.9 主要特性:
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>;
// palette.red 被推断为 [number, number, number]
// palette.green 被推断为 string
const redComponent = palette.red.at(0); // number | undefined
const greenNormalized = palette.green.toUpperCase(); // string
实际应用价值:
// 综合运用 4.x 特性的实际示例
type APIEndpoints = {
'GET /users': { response: User[] };
'POST /users': { body: CreateUserRequest; response: User };
'GET /users/:id': { params: { id: string }; response: User };
'PUT /users/:id': { params: { id: string }; body: UpdateUserRequest; response: User };
};
type ExtractMethod<T> = T extends `${infer M} ${string}` ? M : never;
type ExtractPath<T> = T extends `${string} ${infer P}` ? P : never;
type APIClient = {
[K in keyof APIEndpoints as `${Lowercase<ExtractMethod<K>>}${Capitalize<string & ExtractPath<K>>}`]:
APIEndpoints[K] extends { params: infer P; body: infer B; response: infer R }
? (params: P, body: B) => Promise<R>
: APIEndpoints[K] extends { params: infer P; response: infer R }
? (params: P) => Promise<R>
: APIEndpoints[K] extends { body: infer B; response: infer R }
? (body: B) => Promise<R>
: APIEndpoints[K] extends { response: infer R }
? () => Promise<R>
: never;
};
// 生成的类型:
// {
// getUsers: () => Promise<User[]>;
// postUsers: (body: CreateUserRequest) => Promise<User>;
// getUsersId: (params: { id: string }) => Promise<User>;
// putUsersId: (params: { id: string }, body: UpdateUserRequest) => Promise<User>;
// }
升级建议:
satisfies 操作符平衡类型安全和推断override 关键字提高继承安全性Awaited 简化异步代码What are Covariance and Contravariance? How are they reflected in TypeScript?
What are Covariance and Contravariance? How are they reflected in TypeScript?
- 考察点:型变概念与实际体现。
答案:
协变和逆变是类型系统中描述类型参数在子类型关系中如何表现的概念。协变保持子类型关系的方向,逆变则反转子类型关系的方向,对于构建类型安全的系统至关重要。
基本概念:
协变(Covariance):
// 如果 Dog extends Animal,那么 Array<Dog> 也可以赋值给 Array<Animal>
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
bark(): void {
console.log("Woof!");
}
}
class Cat extends Animal {
meow(): void {
console.log("Meow!");
}
}
// 协变:数组的类型参数保持子类型关系
let dogs: Dog[] = [new Dog("Buddy", "Golden Retriever")];
let animals: Animal[] = dogs; // ✅ 协变,允许赋值
// 这是安全的,因为我们只是读取
animals.forEach(animal => console.log(animal.name));
逆变(Contravariance):
// 函数参数类型是逆变的
type AnimalHandler = (animal: Animal) => void;
type DogHandler = (dog: Dog) => void;
const handleAnimal: AnimalHandler = (animal: Animal) => {
console.log(`Handling animal: ${animal.name}`);
};
const handleDog: DogHandler = (dog: Dog) => {
console.log(`Handling dog: ${dog.name}, breed: ${dog.breed}`);
};
// 逆变:可以将 AnimalHandler 赋值给 DogHandler
const dogHandler: DogHandler = handleAnimal; // ✅ 逆变,允许赋值
// 这是安全的,因为 handleAnimal 可以处理任何 Animal(包括 Dog)
dogHandler(new Dog("Max", "Labrador"));
// 但反过来不行
// const animalHandler: AnimalHandler = handleDog; // ❌ 错误!
// 因为 handleDog 只能处理 Dog,不能处理所有 Animal
TypeScript 中的协变体现:
数组和只读数组:
// 普通数组(在读取位置协变,但写入不安全)
let dogArray: Dog[] = [new Dog("Buddy", "Golden Retriever")];
let animalArray: Animal[] = dogArray; // 协变
// 这会导致运行时错误!
// animalArray.push(new Cat()); // 编译时允许,但运行时会有问题
// 只读数组(安全的协变)
let readonlyDogs: readonly Dog[] = [new Dog("Buddy", "Golden Retriever")];
let readonlyAnimals: readonly Animal[] = readonlyDogs; // ✅ 安全的协变
// 不能修改,所以是类型安全的
// readonlyAnimals.push(new Cat()); // ❌ 编译错误
Promise 类型:
// Promise 是协变的
async function getDog(): Promise<Dog> {
return new Dog("Rover", "Beagle");
}
// 可以将 Promise<Dog> 赋值给 Promise<Animal>
const animalPromise: Promise<Animal> = getDog(); // ✅ 协变
animalPromise.then(animal => {
console.log(animal.name); // 安全访问
// animal.bark(); // ❌ 不能调用 Dog 特有方法
});
函数返回值:
// 函数返回值是协变的
type AnimalFactory = () => Animal;
type DogFactory = () => Dog;
const createDog: DogFactory = () => new Dog("Fido", "Poodle");
const createAnimal: AnimalFactory = createDog; // ✅ 返回值协变
const animal = createAnimal(); // 返回 Animal 类型
console.log(animal.name);
TypeScript 中的逆变体现:
函数参数:
// 在严格模式下,函数参数是逆变的
interface EventHandler<T> {
handle(event: T): void;
}
interface MouseEvent {
x: number;
y: number;
}
interface ClickEvent extends MouseEvent {
button: number;
}
class MouseHandler implements EventHandler<MouseEvent> {
handle(event: MouseEvent): void {
console.log(`Mouse at (${event.x}, ${event.y})`);
}
}
// 逆变:MouseHandler 可以赋值给 EventHandler<ClickEvent>
const clickHandler: EventHandler<ClickEvent> = new MouseHandler(); // ✅ 逆变
clickHandler.handle({ x: 100, y: 200, button: 1 }); // 安全调用
比较函数:
// 比较函数参数是逆变的
interface Comparable<T> {
compare(a: T, b: T): number;
}
const animalComparer: Comparable<Animal> = {
compare(a: Animal, b: Animal): number {
return a.name.localeCompare(b.name);
}
};
// 可以用 Animal 比较器来比较 Dog
const dogComparer: Comparable<Dog> = animalComparer; // ✅ 逆变
const dogs = [
new Dog("Alice", "Husky"),
new Dog("Bob", "Corgi")
];
dogs.sort(dogComparer.compare); // 安全使用
双变(Bivariance)问题:
// TypeScript 默认情况下方法参数是双变的(既协变又逆变)
interface Handler<T> {
handle(value: T): void;
}
class AnimalHandler implements Handler<Animal> {
handle(animal: Animal): void {
console.log(`Handling: ${animal.name}`);
}
}
class DogHandler implements Handler<Dog> {
handle(dog: Dog): void {
console.log(`Handling dog: ${dog.name}, breed: ${dog.breed}`);
}
}
// 双变允许这两种赋值(但可能不安全)
let h1: Handler<Animal> = new DogHandler(); // 协变方向
let h2: Handler<Dog> = new AnimalHandler(); // 逆变方向
// 使用 strictFunctionTypes 禁用双变
// 在 tsconfig.json 中设置 "strictFunctionTypes": true
实际应用场景:
事件系统设计:
// 协变用于事件数据,逆变用于事件处理器
interface Event {
timestamp: Date;
}
interface UserEvent extends Event {
userId: string;
}
interface ClickEvent extends UserEvent {
x: number;
y: number;
}
// 事件发布器(协变)
interface EventPublisher<T extends Event> {
publish(event: T): void;
subscribe(handler: (event: T) => void): void;
}
// 可以将更具体的发布器赋值给更通用的
let clickPublisher: EventPublisher<ClickEvent>;
let userEventPublisher: EventPublisher<UserEvent> = clickPublisher; // ✅ 协变
// 事件处理器(逆变)
const handleUserEvent = (event: UserEvent) => {
console.log(`User ${event.userId} triggered event at ${event.timestamp}`);
};
const handleClickEvent: (event: ClickEvent) => void = handleUserEvent; // ✅ 逆变
状态管理:
// 状态读取器(协变)
interface StateReader<T> {
get(): T;
}
// 状态写入器(逆变)
interface StateWriter<T> {
set(value: T): void;
}
// 完整状态管理器
interface State<T> extends StateReader<T>, StateWriter<T> {
update(updater: (current: T) => T): void;
}
interface AppState {
user: User | null;
theme: 'light' | 'dark';
}
interface UserState {
user: User | null;
}
// 读取器协变
let appStateReader: StateReader<AppState>;
let userStateReader: StateReader<UserState> = appStateReader; // ❌ 需要更精确的类型
// 写入器逆变
let userStateWriter: StateWriter<UserState>;
let appStateWriter: StateWriter<AppState> = userStateWriter; // ❌ 需要更精确的类型
API 设计:
// 泛型约束中的协变和逆变
interface Repository<T> {
find(id: string): Promise<T | null>;
save(entity: T): Promise<void>;
query(predicate: (entity: T) => boolean): Promise<T[]>;
}
// 协变:可以返回更具体的类型
class UserRepository implements Repository<User> {
async find(id: string): Promise<User | null> {
// 实现细节
return null;
}
async save(user: User): Promise<void> {
// 实现细节
}
// 逆变:查询谓词可以接受更通用的类型
async query(predicate: (user: User) => boolean): Promise<User[]> {
// 实现细节
return [];
}
}
// 工厂函数利用协变
function createRepository<T extends BaseEntity>(): Repository<T> {
return new GenericRepository<T>();
}
const userRepo = createRepository<User>(); // Repository<User>
最佳实践和配置:
// tsconfig.json 配置
{
"compilerOptions": {
"strict": true,
"strictFunctionTypes": true, // 启用严格函数类型检查(禁用双变)
"strictNullChecks": true,
"noImplicitReturns": true
}
}
// 显式协变和逆变注解(TypeScript 4.7+,实验性)
interface Producer<out T> { // 协变
produce(): T;
}
interface Consumer<in T> { // 逆变
consume(item: T): void;
}
interface Processor<in TInput, out TOutput> { // 混合
process(input: TInput): TOutput;
}
类型安全指导原则:
How to implement the Singleton pattern in TypeScript while ensuring type safety?
How to implement the Singleton pattern in TypeScript while ensuring type safety?
- 考察点:设计模式实现与类型技巧综合应用。
答案:
TypeScript 中的单例模式实现需要考虑类型安全、线程安全(在某些环境中)、扩展性和测试友好性,有多种实现方式可以满足不同的需求。
经典单例模式实现:
基础单例类:
class Logger {
private static instance: Logger;
private logLevel: 'debug' | 'info' | 'warn' | 'error';
// 私有构造函数防止外部实例化
private constructor() {
this.logLevel = 'info';
}
// 获取单例实例的静态方法
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
public setLogLevel(level: 'debug' | 'info' | 'warn' | 'error'): void {
this.logLevel = level;
}
public log(message: string, level: 'debug' | 'info' | 'warn' | 'error' = 'info'): void {
const levels = { debug: 0, info: 1, warn: 2, error: 3 };
if (levels[level] >= levels[this.logLevel]) {
console.log(`[${level.toUpperCase()}] ${new Date().toISOString()}: ${message}`);
}
}
// 防止克隆
public clone(): never {
throw new Error("Cannot clone singleton instance");
}
}
// 使用示例
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
console.log(logger1 === logger2); // true
logger1.setLogLevel('debug');
logger1.log('Debug message', 'debug');
logger2.log('Info message'); // 同一个实例,共享状态
泛型单例基类:
abstract class Singleton<T> {
private static instances: Map<any, any> = new Map();
constructor() {
const constructor = this.constructor as new () => T;
if (Singleton.instances.has(constructor)) {
throw new Error(`Instance of ${constructor.name} already exists. Use getInstance() instead.`);
}
Singleton.instances.set(constructor, this);
}
public static getInstance<T>(this: new () => T): T {
if (!Singleton.instances.has(this)) {
new this();
}
return Singleton.instances.get(this);
}
}
// 继承单例基类
class DatabaseConnection extends Singleton<DatabaseConnection> {
private connectionString: string;
private isConnected: boolean = false;
constructor() {
super();
this.connectionString = process.env.DB_URL || 'localhost:5432';
}
public connect(): void {
if (!this.isConnected) {
console.log(`Connecting to ${this.connectionString}`);
this.isConnected = true;
}
}
public disconnect(): void {
if (this.isConnected) {
console.log('Disconnecting from database');
this.isConnected = false;
}
}
public isConnectionActive(): boolean {
return this.isConnected;
}
}
// 使用示例
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
console.log(db1 === db2); // true
现代 TypeScript 单例实现:
模块单例(推荐):
// config-manager.ts
interface AppConfig {
apiUrl: string;
timeout: number;
retries: number;
features: Record<string, boolean>;
}
class ConfigManager {
private config: AppConfig;
private readonly defaultConfig: AppConfig = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3,
features: {}
};
constructor() {
this.config = { ...this.defaultConfig };
this.loadConfig();
}
private loadConfig(): void {
// 从环境变量、配置文件等加载配置
if (process.env.API_URL) {
this.config.apiUrl = process.env.API_URL;
}
if (process.env.TIMEOUT) {
this.config.timeout = parseInt(process.env.TIMEOUT, 10);
}
}
public get<K extends keyof AppConfig>(key: K): AppConfig[K] {
return this.config[key];
}
public set<K extends keyof AppConfig>(key: K, value: AppConfig[K]): void {
this.config[key] = value;
}
public updateConfig(updates: Partial<AppConfig>): void {
this.config = { ...this.config, ...updates };
}
public getFullConfig(): Readonly<AppConfig> {
return Object.freeze({ ...this.config });
}
}
// 导出单例实例
export const configManager = new ConfigManager();
// 使用示例
// import { configManager } from './config-manager';
// const apiUrl = configManager.get('apiUrl');
// configManager.set('timeout', 10000);
工厂函数单例:
// cache-manager.ts
interface CacheOptions {
maxSize: number;
ttl: number; // Time to live in milliseconds
}
interface CacheEntry<T> {
value: T;
timestamp: number;
ttl: number;
}
class CacheManager<T = any> {
private cache = new Map<string, CacheEntry<T>>();
private readonly options: CacheOptions;
constructor(options: CacheOptions) {
this.options = options;
// 定期清理过期缓存
setInterval(() => this.cleanup(), this.options.ttl);
}
public set(key: string, value: T, ttl?: number): void {
// 检查缓存大小限制
if (this.cache.size >= this.options.maxSize && !this.cache.has(key)) {
// 移除最旧的条目
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, {
value,
timestamp: Date.now(),
ttl: ttl ?? this.options.ttl
});
}
public get(key: string): T | undefined {
const entry = this.cache.get(key);
if (!entry) return undefined;
// 检查是否过期
if (Date.now() - entry.timestamp > entry.ttl) {
this.cache.delete(key);
return undefined;
}
return entry.value;
}
public has(key: string): boolean {
return this.get(key) !== undefined;
}
public delete(key: string): boolean {
return this.cache.delete(key);
}
public clear(): void {
this.cache.clear();
}
public size(): number {
this.cleanup(); // 清理过期条目后返回大小
return this.cache.size;
}
private cleanup(): void {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now - entry.timestamp > entry.ttl) {
this.cache.delete(key);
}
}
}
}
// 单例工厂函数
let cacheInstance: CacheManager | null = null;
export function getCacheManager(options?: CacheOptions): CacheManager {
if (!cacheInstance) {
cacheInstance = new CacheManager(options || { maxSize: 100, ttl: 300000 });
}
return cacheInstance;
}
// 类型安全的特定缓存管理器
export function getTypedCacheManager<T>(options?: CacheOptions): CacheManager<T> {
return getCacheManager(options) as CacheManager<T>;
}
// 使用示例
interface User {
id: number;
name: string;
email: string;
}
const userCache = getTypedCacheManager<User>({ maxSize: 50, ttl: 600000 });
userCache.set('user:1', { id: 1, name: 'Alice', email: '[email protected]' });
const user = userCache.get('user:1'); // 类型为 User | undefined
高级单例实现:
依赖注入容器单例:
// dependency-container.ts
type Constructor<T = {}> = new (...args: any[]) => T;
type ServiceIdentifier<T = any> = string | symbol | Constructor<T>;
interface ServiceDefinition {
factory: () => any;
singleton: boolean;
instance?: any;
}
class DependencyContainer {
private static instance: DependencyContainer;
private services = new Map<ServiceIdentifier, ServiceDefinition>();
private constructor() {}
public static getInstance(): DependencyContainer {
if (!DependencyContainer.instance) {
DependencyContainer.instance = new DependencyContainer();
}
return DependencyContainer.instance;
}
// 注册单例服务
public registerSingleton<T>(
identifier: ServiceIdentifier<T>,
factory: () => T
): this {
this.services.set(identifier, {
factory,
singleton: true
});
return this;
}
// 注册瞬态服务
public registerTransient<T>(
identifier: ServiceIdentifier<T>,
factory: () => T
): this {
this.services.set(identifier, {
factory,
singleton: false
});
return this;
}
// 注册类
public registerClass<T>(
identifier: ServiceIdentifier<T>,
constructor: Constructor<T>,
singleton: boolean = true
): this {
this.services.set(identifier, {
factory: () => new constructor(),
singleton
});
return this;
}
// 解析服务
public resolve<T>(identifier: ServiceIdentifier<T>): T {
const service = this.services.get(identifier);
if (!service) {
throw new Error(`Service ${String(identifier)} not registered`);
}
if (service.singleton) {
if (!service.instance) {
service.instance = service.factory();
}
return service.instance;
}
return service.factory();
}
// 检查服务是否已注册
public isRegistered<T>(identifier: ServiceIdentifier<T>): boolean {
return this.services.has(identifier);
}
// 清除所有注册
public clear(): void {
this.services.clear();
}
}
// 便利函数
export const container = DependencyContainer.getInstance();
// 装饰器支持
export function Injectable<T extends Constructor>(constructor: T) {
return class extends constructor {
static getInstance() {
return container.resolve(constructor);
}
};
}
// 使用示例
interface IEmailService {
sendEmail(to: string, subject: string, body: string): Promise<void>;
}
@Injectable
class EmailService implements IEmailService {
async sendEmail(to: string, subject: string, body: string): Promise<void> {
console.log(`Sending email to ${to}: ${subject}`);
// 实际发送邮件的逻辑
}
}
interface IUserService {
createUser(userData: any): Promise<User>;
}
@Injectable
class UserService implements IUserService {
constructor(private emailService: IEmailService) {}
async createUser(userData: any): Promise<User> {
const user = { ...userData, id: Date.now() };
await this.emailService.sendEmail(
user.email,
'Welcome!',
'Welcome to our service'
);
return user;
}
}
// 注册服务
container
.registerClass('EmailService', EmailService)
.registerSingleton('UserService', () =>
new UserService(container.resolve('EmailService'))
);
// 使用
const userService = container.resolve<IUserService>('UserService');
状态管理单例:
// state-manager.ts
type StateListener<T> = (newState: T, oldState: T) => void;
type StateUpdater<T> = (currentState: T) => Partial<T>;
class StateManager<T extends Record<string, any>> {
private static instances = new Map<string, StateManager<any>>();
private state: T;
private listeners = new Set<StateListener<T>>();
private readonly stateKey: string;
private constructor(stateKey: string, initialState: T) {
this.stateKey = stateKey;
this.state = { ...initialState };
}
public static getInstance<T extends Record<string, any>>(
stateKey: string,
initialState: T
): StateManager<T> {
if (!StateManager.instances.has(stateKey)) {
StateManager.instances.set(stateKey, new StateManager(stateKey, initialState));
}
return StateManager.instances.get(stateKey);
}
// 获取当前状态
public getState(): Readonly<T> {
return Object.freeze({ ...this.state });
}
// 获取特定属性
public get<K extends keyof T>(key: K): T[K] {
return this.state[key];
}
// 更新状态
public setState(updates: Partial<T> | StateUpdater<T>): void {
const oldState = { ...this.state };
if (typeof updates === 'function') {
const newUpdates = updates(this.state);
this.state = { ...this.state, ...newUpdates };
} else {
this.state = { ...this.state, ...updates };
}
// 通知监听者
this.listeners.forEach(listener => listener(this.state, oldState));
}
// 订阅状态变化
public subscribe(listener: StateListener<T>): () => void {
this.listeners.add(listener);
// 返回取消订阅函数
return () => {
this.listeners.delete(listener);
};
}
// 重置状态
public reset(newState: T): void {
const oldState = { ...this.state };
this.state = { ...newState };
this.listeners.forEach(listener => listener(this.state, oldState));
}
// 清除所有监听者
public clearListeners(): void {
this.listeners.clear();
}
}
// 使用示例
interface AppState {
user: User | null;
theme: 'light' | 'dark';
language: string;
isLoading: boolean;
}
const initialAppState: AppState = {
user: null,
theme: 'light',
language: 'en',
isLoading: false
};
export const appStateManager = StateManager.getInstance('app', initialAppState);
// 使用状态管理器
appStateManager.subscribe((newState, oldState) => {
console.log('App state changed:', { oldState, newState });
});
appStateManager.setState({ theme: 'dark', isLoading: true });
const currentUser = appStateManager.get('user');
const currentTheme = appStateManager.get('theme');
测试友好的单例实现:
// testable-singleton.ts
interface SingletonOptions {
allowReset?: boolean;
testMode?: boolean;
}
class TestableLogger {
private static instance: TestableLogger | null = null;
private static options: SingletonOptions = { allowReset: false };
private logs: string[] = [];
private constructor() {}
public static getInstance(options?: SingletonOptions): TestableLogger {
if (options) {
TestableLogger.options = { ...TestableLogger.options, ...options };
}
if (!TestableLogger.instance) {
TestableLogger.instance = new TestableLogger();
}
return TestableLogger.instance;
}
public static reset(): void {
if (TestableLogger.options.allowReset) {
TestableLogger.instance = null;
} else {
throw new Error('Reset not allowed. Enable allowReset option.');
}
}
public log(message: string): void {
const logEntry = `${new Date().toISOString()}: ${message}`;
this.logs.push(logEntry);
if (!TestableLogger.options.testMode) {
console.log(logEntry);
}
}
public getLogs(): readonly string[] {
return [...this.logs];
}
public clearLogs(): void {
this.logs = [];
}
}
// 测试示例
describe('TestableLogger', () => {
beforeEach(() => {
// 在测试模式下允许重置
TestableLogger.reset();
});
it('should be a singleton', () => {
const logger1 = TestableLogger.getInstance({ testMode: true, allowReset: true });
const logger2 = TestableLogger.getInstance();
expect(logger1).toBe(logger2);
});
it('should log messages', () => {
const logger = TestableLogger.getInstance({ testMode: true, allowReset: true });
logger.log('Test message');
expect(logger.getLogs()).toHaveLength(1);
expect(logger.getLogs()[0]).toContain('Test message');
});
});
最佳实践总结: