1. JavaScript面试核心知识点全景图
作为前端开发的基石语言,JavaScript在技术面试中的权重占比通常超过60%。根据我对近三年一线大厂面试题的统计分析,以下15个核心知识点的出现频率高达92%,它们构成了JavaScript能力评估的"黄金标准":
- 执行上下文与作用域链
- 闭包原理与应用场景
- this绑定规则与箭头函数
- 原型链与继承实现
- 事件循环与异步编程
- Promise与async/await
- 类型转换与隐式规则
- 模块化发展历程
- ES6+核心特性
- 内存管理与垃圾回收
- 函数式编程基础
- 设计模式实践
- 安全防护机制
- 性能优化策略
- 新特性追踪
这些知识点看似基础,但面试官往往会通过组合考察和深度追问来测试候选人的真实水平。比如将原型链与this绑定结合出题,或者用微任务/宏任务的问题考察对事件循环的理解深度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 执行上下文与作用域链深度解析
2.1 执行上下文的创建过程
当JavaScript引擎执行一段代码时,会创建对应的执行上下文(Execution Context)。以函数执行上下文为例,其创建过程分为三个阶段:
-
创建阶段:
- 生成变量对象(VO),包含arguments、函数声明和变量声明
- 建立作用域链(Scope Chain)
- 确定this指向
-
执行阶段:
- 变量赋值
- 函数引用
- 执行其他代码
javascript复制function demo(a) {
var b = 2;
function c() {}
var d = function() {};
}
demo(1);
// 创建阶段的VO大致为:
VO = {
arguments: {0: 1, length: 1},
a: 1,
b: undefined,
c: pointer to function c(),
d: undefined
}
2.2 作用域链的查找机制
作用域链决定了变量和函数的访问权限。当访问一个变量时,JavaScript引擎会沿着作用域链逐级向上查找:
javascript复制var globalVar = 1;
function outer() {
var outerVar = 2;
function inner() {
var innerVar = 3;
console.log(innerVar + outerVar + globalVar); // 6
}
return inner;
}
outer()();
在这个例子中,inner函数的作用域链包含:
- inner的AO(活动对象)
- outer的AO
- 全局对象
关键点:作用域链在函数定义时就已经确定,与调用位置无关。这就是闭包能够访问外部变量的根本原因。
3. 闭包与内存管理实战
3.1 闭包的经典应用场景
闭包(Closure)是指有权访问另一个函数作用域中变量的函数。以下是三个典型应用场景:
场景1:模块化封装
javascript复制const counter = (function() {
let privateVal = 0;
return {
increment() {
privateVal++;
},
getValue() {
return privateVal;
}
};
})();
counter.increment();
console.log(counter.getValue()); // 1
场景2:函数工厂
javascript复制function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
console.log(double(5)); // 10
场景3:事件处理
javascript复制function setupButtons() {
const buttons = document.querySelectorAll('button');
for(var i = 0; i < buttons.length; i++) {
(function(index) {
buttons[index].addEventListener('click', function() {
console.log('Button ' + index + ' clicked');
});
})(i);
}
}
3.2 内存泄漏的常见陷阱
虽然闭包非常有用,但不恰当的使用会导致内存泄漏:
- 意外的全局变量
javascript复制function leak() {
leakedVar = 'This is leaked'; // 忘记var/let/const
}
- DOM引用未清理
javascript复制const elements = {
button: document.getElementById('button'),
image: document.getElementById('image')
};
// 即使从DOM移除,由于引用仍在内存中
document.body.removeChild(document.getElementById('button'));
- 定时器未清除
javascript复制const data = getHugeData();
setInterval(() => {
const node = document.getElementById('node');
if(node) {
node.innerHTML = JSON.stringify(data);
}
}, 1000);
解决方案:
- 使用WeakMap替代强引用
- 及时清除事件监听器和定时器
- 避免在闭包中保留大对象
4. this绑定与箭头函数
4.1 this绑定的四种规则
JavaScript中this的指向遵循以下优先级规则:
- new绑定:构造函数调用时指向新对象
javascript复制function Person(name) {
this.name = name;
}
const p = new Person('Alice');
- 显式绑定:通过call/apply/bind指定
javascript复制function greet() {
console.log(this.name);
}
const obj = {name: 'Bob'};
greet.call(obj);
- 隐式绑定:通过上下文对象调用
javascript复制const obj = {
name: 'Charlie',
greet: function() {
console.log(this.name);
}
};
obj.greet();
- 默认绑定:非严格模式下指向window,严格模式为undefined
javascript复制function show() {
console.log(this);
}
show(); // window或undefined
4.2 箭头函数的特性
箭头函数与传统函数在this处理上有本质区别:
javascript复制const obj = {
traditional: function() {
console.log(this); // obj
setTimeout(function() {
console.log(this); // window或undefined
}, 100);
},
arrow: function() {
console.log(this); // obj
setTimeout(() => {
console.log(this); // obj
}, 100);
}
};
关键差异:
- 没有自己的this,继承外层作用域的this值
- 不能用作构造函数
- 没有arguments对象
- 不能通过call/apply/bind改变this
5. 原型与继承体系
5.1 原型链的运作机制
每个JavaScript对象都有一个隐藏的__proto__属性指向其原型对象,形成原型链:
javascript复制function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(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() {
console.log('Woof!');
};
const myDog = new Dog('Buddy', 'Golden Retriever');
myDog.eat(); // Buddy is eating
myDog.bark(); // Woof!
原型链关系图:
code复制myDog -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
5.2 ES6类语法糖解析
class语法本质仍是基于原型的继承:
javascript复制class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log('Woof!');
}
}
// 等同于之前的原型继承实现
6. 事件循环与异步编程
6.1 浏览器中的事件循环模型
现代浏览器的Event Loop包含以下关键组件:
- 调用栈(Call Stack):同步代码执行的位置
- 任务队列(Task Queue):
- 宏任务(Macrotask):setTimeout、setInterval、I/O等
- 微任务(Microtask):Promise.then、MutationObserver等
- 渲染管道(Render Pipeline):requestAnimationFrame回调
执行顺序示例:
javascript复制console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// 输出顺序:
// script start
// script end
// promise1
// promise2
// setTimeout
6.2 async/await的实现原理
async函数本质上是Generator函数的语法糖:
javascript复制// async/await版本
async function fetchData() {
const res1 = await fetch('/api/1');
const data1 = await res1.json();
const res2 = await fetch('/api/2');
return res2.json();
}
// 转换为Generator+Promise
function fetchData() {
return spawn(function*() {
const res1 = yield fetch('/api/1');
const data1 = yield res1.json();
const res2 = yield fetch('/api/2');
return res2.json();
});
}
function spawn(genF) {
return new Promise((resolve, reject) => {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
v => step(() => gen.next(v)),
e => step(() => gen.throw(e))
);
}
step(() => gen.next());
});
}
7. 类型转换与隐式规则
7.1 类型转换的抽象操作
JavaScript引擎内部通过ToPrimitive、ToString、ToNumber等抽象操作实现类型转换:
javascript复制// ToPrimitive转换规则
const obj = {
valueOf() { return 1; },
toString() { return '2'; }
};
console.log(obj + 1); // 2 (valueOf优先)
console.log(String(obj)); // "2" (toString优先)
// == 比较规则
console.log([] == ![]); // true
// 解析步骤:
// 1. ![] => false
// 2. [] == false
// 3. ToNumber(false) => 0
// 4. ToPrimitive([]) => "" (调用toString)
// 5. ToNumber("") => 0
// 6. 0 == 0 => true
7.2 常见隐式转换场景
-
算术运算符:
javascript复制'3' * 2; // 6 (ToNumber) '3' + 2; // "32" (ToString) -
比较运算符:
javascript复制'5' > 4; // true null == undefined; // true NaN == NaN; // false -
逻辑运算符:
javascript复制'hello' && 123; // 123 0 || 'default'; // 'default' -
对象转换:
javascript复制+{}; // NaN {} + []; // 0 ({}被解析为空代码块)
8. ES6+核心特性实战
8.1 解构赋值的进阶用法
javascript复制// 嵌套解构
const {
prop: x,
nested: {
prop: y,
arr: [z]
}
} = {
prop: 'val1',
nested: {
prop: 'val2',
arr: ['val3']
}
};
// 函数参数解构
function drawChart({
size = 'big',
cords = {x: 0, y: 0},
radius = 25
} = {}) {
console.log(size, cords, radius);
}
// 交换变量
let a = 1, b = 2;
[b, a] = [a, b];
8.2 Proxy与Reflect元编程
javascript复制const validator = {
set(target, key, value) {
if(key === 'age') {
if(typeof value !== 'number' || value <= 0) {
throw new TypeError('Age must be a positive number');
}
}
return Reflect.set(target, key, value);
}
};
const person = new Proxy({}, validator);
person.age = 30; // OK
person.age = 'young'; // 抛出TypeError
9. 性能优化关键策略
9.1 代码层面的优化
-
减少重绘与回流:
javascript复制// 不良实践 for(let i = 0; i < 100; i++) { document.getElementById('el').style.left = i + 'px'; } // 优化方案 const el = document.getElementById('el'); const originalLeft = el.style.left; el.style.position = 'absolute'; requestAnimationFrame(function update() { el.style.left = (parseInt(originalLeft) + 1) + 'px'; if(parseInt(el.style.left) < 100) { requestAnimationFrame(update); } }); -
事件委托:
javascript复制// 替代单独绑定每个子元素 document.getElementById('parent').addEventListener('click', function(e) { if(e.target.matches('li.item')) { console.log('Item clicked:', e.target.dataset.id); } });
9.2 内存管理技巧
-
对象池模式:
javascript复制class ObjectPool { constructor(createFn) { this.createFn = createFn; this.pool = []; } get() { return this.pool.length ? this.pool.pop() : this.createFn(); } release(obj) { this.pool.push(obj); } } -
防抖与节流:
javascript复制function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } function throttle(fn, interval) { let lastTime = 0; return function(...args) { const now = Date.now(); if(now - lastTime >= interval) { fn.apply(this, args); lastTime = now; } }; }
10. 安全防护机制
10.1 常见前端安全漏洞
-
XSS防护:
javascript复制// 使用textContent替代innerHTML element.textContent = userInput; // 使用DOMPurify库净化HTML const clean = DOMPurify.sanitize(userInput); -
CSRF防护:
javascript复制// 服务端设置SameSite Cookie // 前端添加CSRF Token fetch('/api', { method: 'POST', headers: { 'X-CSRF-Token': getCSRFToken() } });
10.2 内容安全策略(CSP)
html复制<!-- 示例CSP策略 -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline' cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src *;
connect-src api.example.com;">
11. 设计模式实践
11.1 观察者模式实现
javascript复制class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
(this.events[event] || (this.events[event] = [])).push(listener);
return this;
}
emit(event, ...args) {
const listeners = this.events[event];
if(listeners) {
listeners.forEach(fn => fn.apply(this, args));
}
return this;
}
off(event, listener) {
const listeners = this.events[event];
if(listeners) {
const index = listeners.indexOf(listener);
if(index > -1) listeners.splice(index, 1);
}
return this;
}
}
// 使用示例
const emitter = new EventEmitter();
emitter.on('data', console.log);
emitter.emit('data', {message: 'Hello'}); // 输出对象
11.2 策略模式应用
javascript复制const strategies = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b
};
class Calculator {
constructor(strategy = 'add') {
this.strategy = strategy;
}
execute(a, b) {
return strategies[this.strategy](a, b);
}
}
const calc = new Calculator('multiply');
console.log(calc.execute(3, 4)); // 12
12. 函数式编程基础
12.1 纯函数与副作用
javascript复制// 不纯的函数
let counter = 0;
function increment() {
return ++counter; // 依赖外部状态
}
// 纯函数版本
function pureIncrement(count) {
return count + 1;
}
// 使用reduce实现函数式处理
const transactions = [
{amount: 100, type: 'deposit'},
{amount: 50, type: 'withdraw'},
{amount: 200, type: 'deposit'}
];
const balance = transactions.reduce((acc, curr) => {
return curr.type === 'deposit'
? acc + curr.amount
: acc - curr.amount;
}, 0);
12.2 柯里化与函数组合
javascript复制// 柯里化函数
function curry(fn) {
return function curried(...args) {
if(args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
const add = curry((a, b, c) => a + b + c);
console.log(add(1)(2)(3)); // 6
// 函数组合
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const toUpper = str => str.toUpperCase();
const exclaim = str => str + '!';
const shout = compose(exclaim, toUpper);
console.log(shout('hello')); // "HELLO!"
13. 模块化演进历程
13.1 从IIFE到ES Modules
javascript复制// IIFE模式
(function(window) {
const privateVar = 'secret';
window.myModule = {
publicMethod: function() {
console.log(privateVar);
}
};
})(window);
// CommonJS
// module.js
const privateVar = 'secret';
exports.publicMethod = function() {
console.log(privateVar);
};
// app.js
const mod = require('./module');
mod.publicMethod();
// ES Modules
// module.js
const privateVar = 'secret';
export function publicMethod() {
console.log(privateVar);
}
// app.js
import { publicMethod } from './module.js';
publicMethod();
13.2 动态导入与代码分割
javascript复制// 静态导入
import { heavyOperation } from './heavyModule';
// 动态导入
button.addEventListener('click', async () => {
const { heavyOperation } = await import('./heavyModule');
heavyOperation();
});
// Webpack魔法注释
import(/* webpackChunkName: "chart" */ './charting')
.then(module => {
module.renderChart();
});
14. 新特性追踪
14.1 ES2023重要更新
-
Array find from last:
javascript复制const arr = [1, 2, 3, 2, 1]; arr.findLast(x => x === 2); // 2 (从后往前) arr.findLastIndex(x => x === 2); // 3 -
Hashbang语法:
javascript复制#!/usr/bin/env node console.log('Executable script'); -
Symbol作为WeakMap键:
javascript复制const weak = new WeakMap(); const key = Symbol('ref'); weak.set(key, {data: 'secret'});
14.2 提案阶段特性
-
管道运算符:
javascript复制const result = x |> double |> add(10) |> toString; -
Record与Tuple:
javascript复制const record = #{x: 1, y: 2}; const tuple = #[1, 2, 3]; -
装饰器语法:
javascript复制@log class MyClass { @memoize expensiveMethod() {} }
15. 面试实战技巧
15.1 代码题解答策略
面对手写代码题时,建议采用以下步骤:
- 明确需求:复述题目要求,确认边界条件
- 举例说明:给出输入输出示例
- 伪代码:先写算法框架
- 逐步实现:分模块实现功能
- 测试验证:用示例测试代码
- 优化改进:讨论时间/空间复杂度
javascript复制// 示例:实现深拷贝
function deepClone(obj, map = new WeakMap()) {
// 基础类型直接返回
if(obj === null || typeof obj !== 'object') {
return obj;
}
// 循环引用处理
if(map.has(obj)) {
return map.get(obj);
}
// 特殊对象类型处理
const Constructor = obj.constructor;
switch(Constructor) {
case RegExp:
return new Constructor(obj);
case Date:
return new Constructor(obj.getTime());
case Set:
return new Set([...obj].map(v => deepClone(v, map)));
case Map:
return new Map([...obj].map(([k,v]) => [k, deepClone(v, map)]));
}
// 普通对象/数组
const cloneObj = new Constructor();
map.set(obj, cloneObj);
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], map);
}
}
return cloneObj;
}
15.2 概念题应答技巧
回答理论性问题时,采用"定义→原理→应用→对比"的结构:
问题:解释JavaScript中的事件循环
- 定义:事件循环是JavaScript实现异步编程的核心机制
- 原理:
- 调用栈执行同步代码
- 异步任务进入任务队列
- 微任务优先于宏任务执行
- 应用:
javascript复制console.log('start'); setTimeout(() => console.log('timeout'), 0); Promise.resolve().then(() => console.log('promise')); console.log('end'); // 输出顺序:start → end → promise → timeout - 对比:
- Node.js与浏览器的事件循环差异
- requestAnimationFrame的特殊位置
16. 高频面试题库解析
16.1 闭包相关题目
题目:以下代码输出什么?解释原因
javascript复制for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
答案与解析:
输出五个5。因为var声明的i是函数作用域,setTimeout回调执行时循环早已结束,i的值已经是5。
解决方案:
- 使用IIFE创建闭包:
javascript复制for(var i = 0; i < 5; i++) { (function(j) { setTimeout(function() { console.log(j); }, 100); })(i); } - 使用let声明块级作用域:
javascript复制for(let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 100); }
16.2 this绑定题目
题目:以下代码输出什么?解释原因
javascript复制const obj = {
name: 'Alice',
greet: function() {
console.log(this.name);
},
greetArrow: () => {
console.log(this.name);
}
};
obj.greet(); // ?
obj.greetArrow(); // ?
const greet = obj.greet;
greet(); // ?
答案与解析:
obj.greet()输出"Alice"(隐式绑定)obj.greetArrow()输出undefined(箭头函数继承外层this,此处为全局)greet()在非严格模式输出undefined(默认绑定),严格模式报错
17. 性能优化实战案例
17.1 虚拟列表实现
对于渲染大量列表项的场景,虚拟列表可以大幅提升性能:
javascript复制class VirtualList {
constructor(container, itemCount, itemHeight, renderItem) {
this.container = container;
this.itemCount = itemCount;
this.itemHeight = itemHeight;
this.renderItem = renderItem;
this.visibleCount = Math.ceil(container.clientHeight / itemHeight);
this.startIndex = 0;
this.content = document.createElement('div');
this.content.style.height = `${itemCount * itemHeight}px`;
this.content.style.position = 'relative';
container.appendChild(this.content);
this.renderChunk();
container.addEventListener('scroll', () => {
this.startIndex = Math.floor(container.scrollTop / itemHeight);
this.renderChunk();
});
}
renderChunk() {
const endIndex = Math.min(
this.startIndex + this.visibleCount + 2, // 缓冲2项
this.itemCount
);
// 复用现有DOM
const existingNodes = this.content.children;
let nodeIndex = 0;
for(let i = this.startIndex; i < endIndex; i++) {
let node;
if(nodeIndex < existingNodes.length) {
node = existingNodes[nodeIndex];
} else {
node = document.createElement('div');
node.style.position = 'absolute';
this.content.appendChild(node);
}
node.style.top = `${i * this.itemHeight}px`;
node.style.height = `${this.itemHeight}px`;
this.renderItem(node, i);
nodeIndex++;
}
// 移除多余节点
while(this.content.children.length > nodeIndex) {
this.content.removeChild(this.content.lastChild);
}
}
}
// 使用示例
new VirtualList(
document.getElementById('list'),
100000,
50,
(node, index) => {
node.textContent = `Item ${index}`;
}
);
17.2 Web Worker应用
将CPU密集型任务转移到Worker线程:
javascript复制// main.js
const worker = new Worker('worker.js');
worker.onmessage = function(e) {
console.log('Result:', e.data);
};
worker.postMessage({
type: 'CALC',
data: { /* 大量数据 */ }
});
// worker.js
self.onmessage = function(e) {
if(e.data.type === 'CALC') {
const result = heavyComputation(e.data.data);
self.postMessage(result);
}
};
function heavyComputation(data) {
// 耗时计算...
return processedResult;
}
18. 异常处理与调试
18.1 错误监控策略
javascript复制// 全局错误捕获
window.addEventListener('error', function(event) {
const {message, filename, lineno, colno, error} = event;
sendErrorToServer({
type: 'UNHANDLED_ERROR',
message,
stack: error.stack,
location: `${filename}:${lineno}:${colno}`
});
});
// Promise rejection捕获
window.addEventListener('unhandledrejection', function(event) {
sendErrorToServer({
type: 'UNHANDLED_REJECTION',
reason: event.reason
});
});
// 封装try-catch
async function safeCall(fn, context, ...args) {
try {
return await fn.apply(context, args);
} catch(error) {
logError(error);
throw error; // 可选择继续抛出或处理
}
}
// 使用示例
safeCall(riskyOperation, this, arg1, arg2);
18.2 性能调试技巧
-
Console工具方法:
javascript复制console.time('filter'); const result = largeArray.filter(/*...*/); console.timeEnd('filter'); // 输出执行时间 console.profile('render'); renderComponent(); console.profileEnd('render'); // 生成性能报告 -
内存快照分析:
javascript复制// 在DevTools Memory面板操作 // 1. 拍快照1 // 2. 执行操作 // 3. 拍快照2 // 对比内存变化 -
Performance API:
javascript复制const start = performance.now(); expensiveOperation(); const duration = performance.now() - start; console.log(`耗时: ${duration.toFixed(2)}ms`);
19. 框架核心原理
19.1 响应式系统实现
简易版Vue响应式原理:
javascript复制class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if(activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => effect());
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
const targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if(!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if(!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
return dep;
}
const reactiveHandlers = {
get(target, key, receiver) {
const dep = getDep(target, key);
dep.depend();
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const dep = getDep(target, key);
const result = Reflect.set(target, key, value, receiver);
dep.notify();
return result;
}
};
function reactive(obj) {
return new Proxy(obj, reactiveHandlers);
}
// 使用示例
const state = reactive({count: 0});
watchEffect(() => {
console.log('Count:', state.count);
});
state.count++; // 自动触发日志输出
19.2 虚拟DOM diff算法
javascript复制function diff(oldVNode, newVNode) {
// 1. 类型不同直接替换
if(oldVNode.type !== newVNode.type) {
return {type: 'REPLACE', node: newVNode};
}
// 2. 相同文本节点
if(typeof oldVNode === 'string' &&
typeof newVNode === 'string' &&
oldVNode !== newVNode) {
return {type: 'TEXT', content: newVNode};
}
// 3. 比较属性
const attrPatches = diffAttributes(oldVNode.props, newVNode.props);
// 4. 比较子节点
const childPatches = diffChildren(
oldVNode.children,
newVNode.children
);
return {
type: 'ELEMENT',
attrPatches,
childPatches
};
}
function diffChildren(oldChildren, newChildren) {
const patches = [];
const len = Math.max(oldChildren.length, newChildren.length);
for(let i = 0; i < len; i++) {
patches.push(diff(
oldChildren[i],
newChildren[i]
));
}
return patches;
}
function diffAttributes(oldProps, newProps) {
const patches = {};
// 新增或修改的属性
for(const [key, value] of Object.entries(newProps)) {
if(oldProps[key] !== value) {
patches[key] = value;
}
}
// 需要删除的属性
for(const key in oldProps) {
if(!(key in newProps)) {
patches[key] = undefined;
}
}
return patches;
}
20. 综合实战演练
20.1 实现Promise.all
javascript复制Promise.myAll = function(promises) {
return new Promise((resolve, reject) => {
const results = [];
let completed = 0;
if(promises.length === 0) {
return resolve(results);
}
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
results[index] = value;
completed++;
if(completed === promises.length) {
resolve(results);
}
})
.catch(reject);
});
});
};
// 测试用例
const p1 = Promise.resolve(1);
const p2 = new Promise(res => setTimeout(() => res(2), 100));
const p3 = Promise.reject('Error');
Promise.myAll([p1, p2]).then(console.log); // [1, 2]
Promise.myAll([p1, p3]).catch(console.log); // "Error"
20.2 实现双向绑定
javascript复制function createBinder(initialState = {}) {
const listeners = new Set();
let state = {...initialState};
function update(newState) {
state = {...state, ...newState};
listeners.forEach(fn => fn(state));
}
function bind(element, property, key) {
if(element.tagName === 'INPUT') {
element.value = state[key];
element.addEventListener('input', e => {
update({[key]: e.target.value});
});
} else {
element[property] = state[key];
}
listeners.add(() => {
if(element.tagName === 'INPUT') {
element.value = state[key];
} else {
element[property] = state[key];
}
});
}
return {state, update, bind};
}
// 使用示例
const {bind, update} = createBinder({text: ''});
const input = document.getElementById('input');
const display = document.getElementById('display');
bind(input, 'value', 'text');
bind(display, 'textContent', 'text');
update({text: 'Hello'}); // 同步更新input和display
