JavaScript 核心要点详解
ES6+ 关键特性
解构赋值
解构赋值是一种从数组或对象中提取值并赋给变量的语法糖。
数组解构
// 基本数组解构
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1, 2, 3
// 跳过某些元素
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1, 3
// 剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]
// 默认值
const [x = 10, y = 20] = [5];
console.log(x, y); // 5, 20
// 交换变量
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2, 1
// 嵌套解构
const nested = [1, [2, 3], 4];
const [a, [b, c], d] = nested;
console.log(a, b, c, d); // 1, 2, 3, 4对象解构
// 基本对象解构
const { name, age } = { name: '张三', age: 25 };
console.log(name, age); // 张三, 25
// 重命名变量
const { name: userName, age: userAge } = { name: '李四', age: 30 };
console.log(userName, userAge); // 李四, 30
// 默认值
const { x = 100, y = 200 } = { x: 50 };
console.log(x, y); // 50, 200
// 嵌套解构
const user = {
id: 1,
profile: {
name: '王五',
contacts: {
email: 'wang@example.com',
phone: '1234567890'
}
}
};
const {
profile: {
name,
contacts: { email }
}
} = user;
console.log(name, email); // 王五, wang@example.com
// 函数参数解构
function greet({ name, greeting = '你好' }) {
console.log(`${greeting}, ${name}!`);
}
greet({ name: '赵六' }); // 你好, 赵六!
greet({ name: '孙七', greeting: '早上好' }); // 早上好, 孙七!展开运算符
展开运算符 (...) 可以将数组或对象"展开"成独立的元素。
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 添加元素
const newArr = [0, ...arr1, 4];
console.log(newArr); // [0, 1, 2, 3, 4]
// 复制数组
const copy = [...arr1];
console.log(copy); // [1, 2, 3]
// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { a: 1, b: 2, c: 3, d: 4 }
// 覆盖属性
const updated = { ...obj1, b: 10 };
console.log(updated); // { a: 1, b: 10 }
// 函数参数展开
function sum(x, y, z) {
return x + y + z;
}
const nums = [1, 2, 3];
console.log(sum(...nums)); // 6
// 注意事项:浅拷贝
const original = { nested: { value: 1 } };
const shallowCopy = { ...original };
shallowCopy.nested.value = 2;
console.log(original.nested.value); // 2 (原对象也被修改)可选链与空值合并
这两个操作符大大简化了处理可能为 null 或 undefined 的值。
// 可选链操作符 (?.)
const user = {
name: '张三',
address: {
city: '北京'
}
};
// 不使用可选链
const zip1 = user && user.address && user.address.zip;
// 使用可选链
const zip2 = user?.address?.zip;
console.log(zip2); // undefined (不会报错)
// 数组可选链
const arr = [1, 2, 3];
console.log(arr?.[0]); // 1
console.log(arr?.[5]); // undefined
// 函数可选链
const obj = {
greet: () => console.log('你好')
};
obj.greet?.(); // 你好
obj.sayHi?.(); // undefined (不会报错)
// 空值合并操作符 (??)
// 只在值为 null 或 undefined 时返回右侧值
const value1 = 0 ?? 10;
console.log(value1); // 0 (0 不是 null/undefined)
const value2 = '' ?? 'default';
console.log(value2); // '' (空字符串不是 null/undefined)
const value3 = null ?? 'default';
console.log(value3); // 'default'
const value4 = undefined ?? 'default';
console.log(value4); // 'default'
// 与 || 的区别
const val1 = 0 || 'default';
console.log(val1); // 'default' (0 是 falsy)
const val2 = 0 ?? 'default';
console.log(val2); // 0 (0 不是 null/undefined)
// 组合使用
const config = {
timeout: 3000
};
const timeout = config?.timeout ?? 5000;
console.log(timeout); // 3000Promise 与异步处理
Promise 是处理异步操作的核心机制。
// 创建 Promise
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
// 使用 Promise
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('操作完成'));
// async/await 语法糖
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('获取数据失败:', error);
throw error; // 重新抛出错误让调用者处理
}
}
// 并行执行多个异步操作
async function fetchMultipleData() {
try {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { users, posts, comments };
} catch (error) {
console.error('批量获取失败:', error);
}
}
// Promise.race - 返回最先完成的 Promise
async function fetchWithTimeout(url, timeout = 5000) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), timeout);
});
return Promise.race([
fetch(url).then(r => r.json()),
timeoutPromise
]);
}
// Promise.allSettled - 等待所有 Promise 完成(无论成功失败)
async function fetchAllIgnoreErrors() {
const promises = [
fetch('/api/users').then(r => r.json()),
fetch('/api/invalid').then(r => r.json()), // 可能失败
fetch('/api/posts').then(r => r.json())
];
const results = await Promise.allSettled(promises);
const successful = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
const failed = results
.filter(r => r.status === 'rejected')
.map(r => r.reason);
return { successful, failed };
}模块化
ES6 模块系统使得代码组织更加清晰。
// math.js - 导出多个函数
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => {
if (b === 0) throw new Error('除数不能为零');
return a / b;
};
// 默认导出
export default class Calculator {
constructor() {
this.result = 0;
}
add(n) {
this.result += n;
return this;
}
getResult() {
return this.result;
}
}
// user.js - 导出对象
export const user = {
name: '张三',
age: 25
};
export function greet(name) {
console.log(`你好, ${name}!`);
}
// main.js - 导入
import Calculator, { add, subtract } from './math.js';
import { user, greet } from './user.js';
import * as MathUtils from './math.js'; // 导入所有
// 使用
const calc = new Calculator();
calc.add(5).add(3);
console.log(calc.getResult()); // 8
console.log(add(10, 5)); // 15
greet(user.name); // 你好, 张三!
console.log(MathUtils.multiply(4, 5)); // 20
// 动态导入(代码分割)
async function loadModule() {
const { heavyFunction } = await import('./heavy-module.js');
heavyFunction();
}
// index.js - 重新导出
export { default as Calculator } from './math.js';
export * from './user.js';核心概念深入
事件循环 (Event Loop)
JavaScript 是单线程语言,通过事件循环实现异步操作。
// 执行顺序示例
console.log('1. 同步代码开始');
setTimeout(() => {
console.log('4. 宏任务:setTimeout');
}, 0);
Promise.resolve()
.then(() => {
console.log('3. 微任务:Promise.then');
})
.then(() => {
console.log('3.5 微任务:Promise.then 链');
});
console.log('2. 同步代码结束');
// 输出顺序:
// 1. 同步代码开始
// 2. 同步代码结束
// 3. 微任务:Promise.then
// 3.5 微任务:Promise.then 链
// 4. 宏任务:setTimeout事件循环流程:
- 执行同步代码:调用栈中的代码优先执行
- 检查微任务队列:执行所有微任务(Promise.then、MutationObserver、queueMicrotask)
- 执行渲染:如果需要更新 DOM,浏览器会进行渲染
- 检查宏任务队列:执行一个宏任务(setTimeout、setInterval、I/O、UI 事件)
- 重复步骤 2-4
// 微任务和宏任务的优先级
setTimeout(() => {
console.log('宏任务 1');
}, 0);
queueMicrotask(() => {
console.log('微任务 1');
});
Promise.resolve().then(() => {
console.log('微任务 2');
});
setTimeout(() => {
console.log('宏任务 2');
}, 0);
// 输出:
// 微任务 1
// 微任务 2
// 宏任务 1
// 宏任务 2实际应用:避免阻塞主线程
// 错误:长时间阻塞主线程
function heavyComputation() {
const start = Date.now();
while (Date.now() - start < 1000) {
// 阻塞 1 秒,页面会卡死
}
}
// 正确:将大任务拆分成小块
function chunkedComputation(items, callback) {
let index = 0;
const chunkSize = 100; // 每次处理 100 个
function processChunk() {
const end = Math.min(index + chunkSize, items.length);
for (; index < end; index++) {
callback(items[index]);
}
if (index < items.length) {
setTimeout(processChunk, 0); // 让出主线程
}
}
processChunk();
}闭包 (Closure)
闭包是指函数能够记住并访问其词法作用域,即使该函数在其词法作用域之外执行。
// 基本闭包
function outer() {
let count = 0; // 外部函数的变量
return function inner() {
count++; // 内部函数访问外部变量
console.log(`计数: ${count}`);
};
}
const counter = outer();
counter(); // 计数: 1
counter(); // 计数: 2
counter(); // 计数: 3
// 闭包的应用:私有变量
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count,
reset: () => {
count = initialValue;
return count;
}
};
}
const myCounter = createCounter(10);
console.log(myCounter.increment()); // 11
console.log(myCounter.increment()); // 12
console.log(myCounter.decrement()); // 11
console.log(myCounter.getCount()); // 11
console.log(myCounter.reset()); // 10
// 闭包的应用:函数工厂
function multiply(x) {
return function(y) {
return x * y;
};
}
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 闭包的应用:缓存(记忆化)
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('从缓存获取');
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
console.log('计算并缓存');
return result;
};
}
const expensiveCalculation = memoize((a, b) => {
console.log('执行复杂计算...');
return a + b;
});
console.log(expensiveCalculation(1, 2)); // 计算并缓存, 3
console.log(expensiveCalculation(1, 2)); // 从缓存获取, 3
console.log(expensiveCalculation(2, 3)); // 计算并缓存, 5
// 闭包的陷阱
function createFunctions() {
const functions = [];
// 错误示例:var 没有块级作用域
for (var i = 0; i < 3; i++) {
functions.push(() => console.log(i));
}
return functions;
}
const funcs = createFunctions();
funcs[0](); // 3 (不是 0)
funcs[1](); // 3 (不是 1)
funcs[2](); // 3 (不是 2)
// 解决方案 1:使用 let
function createFunctionsCorrect() {
const functions = [];
for (let i = 0; i < 3; i++) {
functions.push(() => console.log(i));
}
return functions;
}
const funcsCorrect = createFunctionsCorrect();
funcsCorrect[0](); // 0
funcsCorrect[1](); // 1
funcsCorrect[2](); // 2
// 解决方案 2:使用闭包
function createFunctionsWithClosure() {
const functions = [];
for (var i = 0; i < 3; i++) {
functions.push((function(j) {
return () => console.log(j);
})(i));
}
return functions;
}原型链 (Prototype Chain)
JavaScript 使用原型链实现继承和属性共享。
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 原型方法
Person.prototype.greet = function() {
console.log(`你好, 我是${this.name}, ${this.age}岁`);
};
Person.prototype.toString = function() {
return `${this.name}(${this.age})`;
};
// 创建实例
const person1 = new Person('张三', 25);
const person2 = new Person('李四', 30);
person1.greet(); // 你好, 我是张三, 25岁
person2.greet(); // 你好, 我是李四, 30岁
// 原型链关系
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
// 属性查找过程
console.log(person1.hasOwnProperty('name')); // true (自身属性)
console.log(person1.hasOwnProperty('greet')); // false (原型属性)
console.log('greet' in person1); // true (包括原型链)
// ES6 class 语法(原型继承的语法糖)
class Animal {
constructor(name, sound) {
this.name = name;
this.sound = sound;
}
speak() {
console.log(`${this.name}发出${this.sound}的声音`);
}
static create(name, sound) {
return new Animal(name, sound);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name, '汪汪'); // 调用父类构造函数
this.breed = breed;
}
// 重写父类方法
speak() {
console.log(`${this.name}(${this.breed})汪汪叫`);
}
fetch() {
console.log(`${this.name}去捡球`);
}
}
const dog = new Dog('旺财', '金毛');
dog.speak(); // 旺财(金毛)汪汪叫
dog.fetch(); // 旺财去捡球
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
// 静态方法
const dog2 = Animal.create('小白', '喵喵');this 绑定规则
this 的值取决于函数的调用方式,而不是定义方式。
// 1. 默认绑定(全局对象或 undefined)
function globalFunction() {
console.log(this);
}
globalFunction(); // 非严格模式: Window, 严格模式: undefined
// 2. 隐式绑定(对象调用)
const obj = {
name: '对象',
greet() {
console.log(`你好, ${this.name}`);
}
};
obj.greet(); // 你好, 对象 (this 指向 obj)
// 隐式绑定丢失
const greet = obj.greet;
greet(); // 你好, undefined (this 指向全局对象)
// 3. 显式绑定(call, apply, bind)
function introduce(greeting, punctuation) {
console.log(`${greeting}, 我是${this.name}${punctuation}`);
}
const person = { name: '张三' };
// call: 立即调用,参数逐个传递
introduce.call(person, '你好', '!'); // 你好, 我是张三!
// apply: 立即调用,参数数组传递
introduce.apply(person, ['早上好', '。']); // 早上好, 我是张三。
// bind: 返回新函数
const boundIntroduce = introduce.bind(person);
boundIntroduce('晚上好', '~'); // 晚上好, 我是张三~
// 4. new 绑定(构造函数)
function Person(name) {
this.name = name;
}
const p = new Person('李四');
console.log(p.name); // 李四
// 5. 箭头函数(没有自己的 this,继承外层)
const obj2 = {
name: '箭头函数对象',
greet: function() {
// 普通函数
setTimeout(function() {
console.log(`普通函数: ${this.name}`); // undefined
}, 100);
// 箭头函数
setTimeout(() => {
console.log(`箭头函数: ${this.name}`); // 箭头函数对象
}, 100);
}
};
obj2.greet();
// 实际应用:保持 this 上下文
class Button {
constructor(element) {
this.element = element;
this.clickCount = 0;
// 使用 bind 保持 this
this.element.addEventListener('click', this.handleClick.bind(this));
// 或者使用箭头函数
this.element.addEventListener('click', () => this.handleClick());
// 或者在定义时使用箭头函数
this.element.addEventListener('click', this.handleClickArrow);
}
handleClick() {
this.clickCount++;
console.log(`点击次数: ${this.clickCount}`);
}
handleClickArrow = () => {
this.clickCount++;
console.log(`点击次数: ${this.clickCount}`);
}
}实用技巧
防抖与节流
// 防抖:在事件触发 n 秒后才执行,如果在这段时间内再次触发,则重新计时
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用示例:搜索框输入
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
console.log('搜索:', e.target.value);
// 发送 API 请求
}, 500));
// 节流:在固定时间间隔内只执行一次
function throttle(func, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
func.apply(this, args);
}
};
}
// 使用示例:滚动事件
window.addEventListener('scroll', throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 100));深拷贝与浅拷贝
// 浅拷贝
const shallowCopy1 = { ...original };
const shallowCopy2 = Object.assign({}, original);
const shallowCopy3 = [...originalArray];
// 深拷贝
function deepClone(obj, hash = new WeakMap()) {
// 处理基本类型和 null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理循环引用
if (hash.has(obj)) {
return hash.get(obj);
}
// 处理日期
if (obj instanceof Date) {
return new Date(obj);
}
// 处理正则表达式
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 处理数组
if (Array.isArray(obj)) {
const clone = [];
hash.set(obj, clone);
obj.forEach((item, index) => {
clone[index] = deepClone(item, hash);
});
return clone;
}
// 处理对象
const clone = {};
hash.set(obj, clone);
Object.keys(obj).forEach(key => {
clone[key] = deepClone(obj[key], hash);
});
return clone;
}
// 现代方法:structuredClone(浏览器和 Node.js 支持)
const original = {
name: '张三',
nested: { value: 1 },
date: new Date(),
regex: /test/gi
};
const cloned = structuredClone(original);