JavaScript高手必备:10个进阶技巧
一、解构赋值
解构赋值能轻松提取数组中的值或解包对象属性到独立变量中,而别名功能则允许您在此过程中重命名变量。这在处理API响应等外部数据源时尤为实用,可以避免命名冲突并提高代码可读性
/************json解构************/
const userApiData = {
user_details: {
given_name: 'Emily',
family_name: 'Chen',
years_since_signup: 5
},
contact_info: {
mailing_address: {
municipality: 'San Francisco',
postal_number: '94105'
},
phone_digits: '5551234567'
}
};
const {
user_details: {
given_name: firstName,
family_name: lastName,
years_since_signup: memberSinceYears
},
contact_info: {
mailing_address: {
municipality: city,
postal_number: zipCode
},
phone_digits: phoneNumber
}
} = userApiData;
console.log('User Profile:');
console.log(`Name: ${firstName} ${lastName}`);
console.log(`Member for: ${memberSinceYears} years`);
console.log(`Location: ${city}, ${zipCode}`);
console.log(`Contact: ${phoneNumber}`);
/************数组解构************/
// 1. 基本解构
const rgb = [255, 128, 64];
const [red, green, blue] = rgb;
console.log(`R: ${red}, G: ${green}, B: ${blue}`);
// 输出: R: 255, G: 128, B: 64
// 2. 跳过某些元素
// 1. 基本解构
const rgb = [255, 128, 64];
const [red, green, blue] = rgb;
console.log(`R: ${red}, G: ${green}, B: ${blue}`);
// 输出: R: 255, G: 128, B: 64
// 2. 跳过某些元素
const dateParts = ['2023', '09', '15'];
const [year, , day] = dateParts;
console.log(`Year: ${year}, Day: ${day}`);
// 输出: Year: 2023, Day: 15
// 3. 默认值
const sizes = ['M'];
const [size = 'L', color = 'black'] = sizes;
console.log(`Size: ${size}, Color: ${color}`);
// 输出: Size: M, Color: black
// 4. 剩余元素
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...others] = numbers;
console.log(`First two: ${first}, ${second}, Rest: ${others}`);
// 输出: First two: 1, 2, Rest: 3,4,5
// 5. 交换变量
let a = 10, b = 20;
[a, b] = [b, a];
console.log(`a: ${a}, b: ${b}`);
// 输出: a: 20, b: 10
// 6. 嵌套数组解构
const matrix = [[1, 2], [3, 4], [5, 6]];
const [[x1, y1], [x2, y2]] = matrix;
console.log(`First point: (${x1},${y1})`);
// 输出: First point: (1,2)
// 7. 函数返回解构
function getCoordinates() {
return [12.34, 56.78];
}
const [latitude, longitude] = getCoordinates();
console.log(`Lat: ${latitude}, Lng: ${longitude}`);
// 输出: Lat: 12.34, Lng: 56.78
// 8. 迭代器解构
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
];
for (const [index, { name }] of users.entries()) {
console.log(`${index + 1}. ${name}`);
}
二、柯里化
柯里化(Currying)是一种函数转换技术,它将接收多个参数的函数拆解为一系列嵌套的单一参数函数。这种技术通过分步接收参数,使函数更具灵活性和复用性。例如,一个计算 a + b + c 的函数可以柯里化为 a => b => c => a + b + c,允许先传入部分参数(如 a 和 b),稍后再传入剩余参数(如 c)。柯里化在函数式编程中尤其重要,便于组合高阶函数、延迟计算和创建更模块化的代码。此外,它能简化参数定制,使函数逻辑更清晰,适用于事件处理、配置预设和链式调用等场景
// 柯里化日志函数
const createLogger = namespace => level => message =>
`[${namespace}] ${level.toUpperCase()}: ${message}`;
// 创建特定日志器
const appLogger = createLogger('App');
const appError = appLogger('error');
const appWarning = appLogger('warning');
console.log(appError('Connection failed'));
console.log(appWarning('Memory usage high'));
三、防抖和节流
防抖(Debounce)和节流(Throttle)是两种优化高频函数调用的技术,可有效提升性能表现。防抖确保在事件频繁触发时,只在停止触发后的指定延迟时间执行一次(如搜索框输入)。而节流则强制函数以固定间隔执行(如滚动事件处理),避免短时间内的多次调用。
/************防抖(Debounce)************/
function enhancedDebounce(func, wait, immediate = false) {
let timeout;
return function() {
const context = this;
const args = arguments;
const later = () => {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
// 首次立即执行的防抖
const btn = document.getElementById('submit');
btn.addEventListener('click', enhancedDebounce(() => {
console.log('表单提交');
}, 1000, true));
/************节流(Throttle)************/
function throttle(func, interval = 300) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
func.apply(this, args); // 执行函数
lastTime = now; // 重置最后执行时间
}
};
}
// 使用案例:滚动事件
const scrollHandler = () => console.log('处理滚动...');
// 每1秒最多执行一次
window.addEventListener('scroll', throttle(scrollHandler, 1000));
四、记忆化
记忆化(Memoization)是一种性能优化技术,通过缓存函数计算结果来避免重复运算。 当函数被相同参数重复调用时,直接返回缓存值而非重新计算,特别适用于以下场景:
- 复杂数学计算(如阶乘/斐波那契数列)
- 数据格式转换等CPU密集型操作
- 需要重复访问的API响应处理
/************斐波那契数列优化************/
function memoizedFibonacci() {
const cache = {};
return function fib(n) {
if (n in cache) return cache[n];
if (n <= 1) return n;
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
};
}
const fib = memoizedFibonacci();
console.time('With Memoization');
console.log(fib(40)); // 102334155
console.timeEnd('With Memoization'); // ~0.1ms(后续调用直接返回缓存)
/************缓存网络请求结果,避免重复请求************/
const cachedFetch = (() => {
const cache = new Map();
return async (url) => {
if (cache.has(url)) {
console.log('缓存数据');
return cache.get(url);
}
console.log('第一次请求');
const response = await fetch(url);
const data = await response.json();
cache.set(url, data);
return data;
};
})();
// 使用示例
(async () => {
const apiUrl = 'https://jsonplaceholder.typicode.com/todos/1';
await cachedFetch(apiUrl); // 第一次请求(发送真实请求)
await cachedFetch(apiUrl); // 第二次请求(返回缓存)
})();
/************高阶函数:通用记忆化封装************/
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args); // 参数序列化为缓存键
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// 使用示例:缓存阶乘计算
const factorial = memoize((n) => {
if (n === 0) return 1;
return n * factorial(n - 1); // 递归调用也会利用缓存
});
console.log(factorial(5)); // 120(计算并缓存)
console.log(factorial(5)); // 120(直接返回缓存)
/************React 应用案例:记忆化组件,父组件更新时避免子组件重复计算************/
import React, { useMemo } from 'react';
const ExpensiveComponent = ({ data }) => {
// 仅当data变化时重新计算
const processedData = useMemo(() => {
console.log('Recalculating...');
return data.map(item => item * 2); // 模拟耗时计算
}, [data]);
return <div>{processedData.join(', ')}</div>;
};
五、代理
Proxy(代理)是JavaScript提供的一种元编程能力,允许你创建一个对象的包装器,从而拦截并自定义该对象的基本操作行为。
通过Proxy,可以:
- 拦截属性访问(obj.property)
- 控制赋值操作(obj.property = value)
- 管理函数调用(obj.method())
- 自定义in、delete等运算符行为
典型应用场景包括: - 数据校验(自动验证赋值合法性)
- 观察对象变化(实现响应式系统,如Vue 3核心)
- 动态计算属性(替代Object.defineProperty)
- API访问控制(限制敏感操作)
/************数据校验代理************/
const userValidator = {
set(target, prop, value) {
if (prop === 'age' && typeof value !== 'number') {
throw new TypeError('年龄必须是数字');
}
if (prop === 'email' && !value.includes('@')) {
throw new Error('邮箱格式错误');
}
target[prop] = value;
return true; // 表示设置成功
}
};
const user = new Proxy({}, userValidator);
user.age = 25; // 正常
user.email = "test@example.com"; // 正常
/************自动类型转换代理************/
const autoConvert = {
get(target, prop) {
const value = target[prop];
if (typeof value === 'string') {
return value.toUpperCase(); // 字符串自动转大写
}
return value;
}
};
const data = new Proxy({ name: 'john', age: 30 }, autoConvert);
console.log(data.name); // "JOHN" (自动转换)
console.log(data.age); // 30 (保持不变)
/************私有属性控制************/
const privateStore = (initialData) => {
const privateData = { ...initialData };
return new Proxy({}, {
get(_, prop) {
if (prop.startsWith('_')) {
throw new Error('无权访问私有属性');
}
return privateData[prop];
},
set(_, prop, value) {
if (prop.startsWith('_')) {
throw new Error('禁止修改私有属性');
}
privateData[prop] = value;
return true;
}
});
};
const obj = privateStore({ _secret: 123, public: 'hello' });
console.log(obj.public); // "hello"
// console.log(obj._secret); // 抛出Error
// obj._secret = 456; // 抛出Error
/************函数调用拦截************/
const deprecated = (fn, message) => {
return new Proxy(fn, {
apply(target, thisArg, args) {
console.warn(`警告: ${message}`);
return target.apply(thisArg, args);
}
});
};
const oldFunc = () => console.log('旧函数逻辑');
const newFunc = deprecated(oldFunc, '该函数将在下个版本移除');
newFunc(); // 先输出警告,再执行函数
/************动态计算属性************/
const metrics = {
values: [10, 20, 30],
get average() {
return this.values.reduce((a, b) => a + b, 0) / this.values.length;
}
};
const smartMetrics = new Proxy(metrics, {
get(target, prop) {
if (prop === 'sum') {
return target.values.reduce((a, b) => a + b, 0);
}
return target[prop];
}
});
console.log(smartMetrics.average); // 20 (原属性)
console.log(smartMetrics.sum); // 60 (动态计算)
/************数组负索引支持************/
const negativeArray = (arr) => {
return new Proxy(arr, {
get(target, prop) {
const index = parseInt(prop);
if (index < 0) {
return target[target.length + index];
}
return target[prop];
}
});
};
const arr = negativeArray(['a', 'b', 'c']);
console.log(arr[-1]); // "c" (相当于arr[2])
console.log(arr[-2]); // "b" (相当于arr[1])
/************DOM操作日志记录************/
const createObservableDOM = (element) => {
return new Proxy(element, {
set(target, prop, value) {
console.log(`设置 ${target.tagName}.${prop} = ${value}`);
target[prop] = value;
return true;
}
});
};
const div = createObservableDOM(document.createElement('div'));
div.className = 'container'; // 控制台输出日志
div.textContent = 'Hello'; // 控制台输出日志
六、生成器
生成器(Generator)是一种特殊的函数,能够暂停执行并保留上下文状态,后续可从暂停处恢复运行。 通过 function* 语法和 yield 关键字实现控制流的灵活中断与继续,具有以下核心特性:
- 状态保持:暂停期间保留局部变量和执行位置
- 迭代协议:自动实现迭代器接口(next()、return()、throw())
- 双向通信:通过 yield 接收外部传入的值
/************基础迭代控制************/
function* countdown(start) {
while (start >= 0) {
yield start--;
}
}
const timer = countdown(3);
console.log(timer.next().value); // 3
console.log(timer.next().value); // 2
console.log(timer.next().value); // 1
console.log(timer.next().value); // 0
console.log(timer.next().done); // true
/************无限数据流, 可无限调用,内存友好************/
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
/************ 递归目录遍历************/
const fs = require('fs');
const path = require('path');
function* walkDir(dir) {
for (const file of fs.readdirSync(dir)) {
const fullPath = path.join(dir, file);
if (fs.statSync(fullPath).isDirectory()) {
yield* walkDir(fullPath); // 递归委托
} else {
yield fullPath;
}
}
}
// 使用示例
for (const file of walkDir('/path/to/dir')) {
console.log(file); // 逐个输出文件路径
}
/************状态机实现************/
function* trafficLight() {
while (true) {
yield '🟢 绿灯(通行)';
yield '🟡 黄灯(警告)';
yield '🔴 红灯(停止)';
}
}
const light = trafficLight();
setInterval(() => {
console.log(light.next().value);
}, 3000); // 每3秒切换状态
/************批量数据分块处理************/
function* batchProcess(data, chunkSize = 10) {
for (let i = 0; i < data.length; i += chunkSize) {
yield data.slice(i, i + chunkSize);
}
}
const bigData = Array(100).fill(0).map((_, i) => i);
for (const chunk of batchProcess(bigData, 20)) {
console.log('处理块:', chunk); // 每次处理20条数据
}
七、使用structuredClone进行结构化克隆
structuredClone() 是现代 JavaScript 提供的深度克隆方法,能够完整复制复杂对象结构,解决了传统方案的诸多限制。与浅拷贝(如扩展运算符 … 或 Object.assign())不同,它会递归复制所有嵌套属性。
const obj = {
date: new Date(),
// 移除无法克隆的函数
nested: { a: 1 },
self: null // 循环引用占位
};
obj.self = obj;
const clone = structuredClone(obj); // 成功克隆
console.log(clone.date instanceof Date); // true
console.log(clone.nested.a); // 1
console.log(clone.self === clone); // true (循环引用保留)
八、自调用函数IIFE
立即调用函数表达式(IIFE)是一种在定义时自动执行的函数模式,通过创建独立作用域封装代码,有效避免变量污染全局环境,是早期JavaScript实现模块化和保持代码纯净性的核心方案。
// 传统写法
(function() {
const privateData = '内部数据';
console.log(privateData); // "内部数据"
})();
// 现代箭头函数写法
(() => {
const secret = 123;
console.log(secret); // 123
})();
// 全局空间检查
console.log(typeof privateData); // "undefined"
console.log(typeof secret); // "undefined"
九、函数重载
函数重载是静态类型语言的关键特性,允许在同一作用域中定义多个同名函数,通过参数数量、类型或顺序的差异来区分具体实现。 这种机制让开发者能够使用统一的函数名表达语义相似但处理逻辑不同的操作,显著提升代码的可读性和模块化程度。在编译阶段,编译器会根据实际调用时传入的参数类型自动选择匹配的函数版本。
function createOverloadedFunction() {
const handlers = new Map();
// 主调用函数
const overloadedFn = (...args) => {
const signature = args.map(arg => typeof arg).join('-');
const handler = handlers.get(signature);
if (typeof handler === 'function') {
return handler(...args);
}
throw new Error(
`找不到匹配的函数签名: ${signature}. ` +
`可用的签名有: ${Array.from(handlers.keys()).join(', ')}`
);
};
// 注册方法
overloadedFn.register = (...types) => handler => {
if (typeof handler !== 'function') {
throw new TypeError('处理函数必须是一个函数');
}
const signature = types.join('-');
if (handlers.has(signature)) {
console.warn(`警告:正在覆盖已存在的函数签名:{signature}`);
}
handlers.set(signature, handler);
return overloadedFn;
};
// 移除方法(新增功能)
overloadedFn.unregister = (...types) => {
const signature = types.join('-');
handlers.delete(signature);
return overloadedFn;
};
return overloadedFn;
}
// 使用示例
const myFun = createOverloadedFunction();
myFun
.register('string')(str => console.log(`处理字符串: ${str}`))
.register('string', 'number')((str, num) => console.log(`处理字符串和数字: ${str}, ${num}`))
.register('object')(obj => console.log(`处理对象: ${JSON.stringify(obj)}`));
// 测试调用
myFun('hello'); // 处理字符串: hello
myFun('test', 42); // 处理字符串和数字: test, 42
myFun({ key: 'value' }); // 处理对象: {"key":"value"}
try {
myFun(123, true); // 抛出错误: No handler defined for signature: number-boolean
} catch (err) {
console.error(err.message);
}
十、使用对象映射(Object Literal)替代 switch
现代 JavaScript 提供了比传统 if-else 和 switch 更优雅的条件逻辑处理方案——对象字面量和 Map 数据结构。 这些替代方案通过键值映射实现条件分支,避免了冗长的条件判断,使代码更简洁直观。对象映射适合静态条件匹配,而 Map 则支持动态键和复杂数据类型作为键。它们特别适用于:
- 多条件分支场景
- 需要频繁修改或扩展的条件逻辑
- 追求更高可读性和可维护性的代码结构
// 对象字面量方案
const actions = {
login: () => console.log('登录'),
logout: () => console.log('登出')
};
actions['login']?.();
// Map 方案
const strategy = new Map([
[status === 200, handleSuccess],
[status >= 400, handleError]
]);
strategy.get(true)?.(data);