JavaScript 中有哪些基本数据类型 (Primitive Types)?
What are the primitive data types in JavaScript?
- *考察点:基础知识的掌握。*
共 53 道题目
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 框架的重要基础技术。