我把JS复习分成六个模块,从基础到进阶:
| 模块 | 内容 | 重要程度 |
|---|---|---|
| 基础语法 | 变量、数据类型、运算符 | ⭐⭐⭐ 必会 |
| 函数 | 定义、作用域、闭包 | ⭐⭐⭐ 必会 |
| 对象/数组 | 增删改查、方法 | ⭐⭐⭐ 必会 |
| DOM操作 | 找元素、改内容、绑事件 | ⭐⭐⭐ 必会 |
| 异步 | 回调、Promise、async/await | ⭐⭐⭐⭐ 进阶 |
| 进阶概念 | 原型链、this、事件循环 | ⭐⭐⭐⭐ 面试 |
1. 基础语法(快速过)
1.1 变量声明
// 三种方式
var name = '旧时代的'; // 函数作用域,可重复声明
let age = 18; // 块级作用域,不能重复声明
const PI = 3.14; // 常量,不能改
1.2 数据类型
// 基本类型(存值)
let str = 'hello'; // 字符串
let num = 123; // 数字
let bool = true; // 布尔
let und = undefined; // 未定义
let nul = null; // 空
let s1 = Symbol(); // symbol(符号)ES6
let big = 123456n; // bigint(大整数)ES2020
// 引用类型(存地址)
let obj = { name: 'Tom' }; // 对象
let arr = [1, 2, 3]; // 数组
let fn = function() {}; // 函数
1.3 类型判断
// 1. typeof(最常用)
typeof 'hello'; // "string"
typeof 123; // "number"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof Symbol(); // "symbol"
typeof 123n; // "bigint"
typeof null; // "object"(bug)
typeof []; // "object"
typeof {}; // "object"
// 2. instanceof
[] instanceof Array; // true
[] instanceof Object; // true
// 3. Array.isArray
Array.isArray([]); // true
Array.isArray({}); // false
// 4. 终极方案
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(null); // "[object Null]"
2. 函数(重点)
2.1 作用域
let global = '全局'; // 全局作用域
function fn() {
let local = '局部'; // 函数作用域
if (true) {
let block = '块级'; // 块级作用域(let/const)
var old = '老家伙'; // 函数作用域(var)
}
console.log(old); // ✅ 能访问
console.log(block); // ❌ 报错
}
2.2 闭包(面试必考)
// 闭包 = 函数 + 它能访问的外部变量
function createCounter() {
let count = 0; // 外部变量
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
// count 没有被销毁,被内部函数一直引用着
神奇之处:
createCounter执行完了,本该销毁的count变量居然还活着- 内部函数把它记住了,还能继续用
- 外面直接访问不到
count,安全!
3. 数组方法(必会)
const arr = [1, 2, 3, 4, 5];
// 增删改
arr.push(6); // 尾部加 → [1,2,3,4,5,6]
arr.pop(); // 尾部删 → [1,2,3,4,5]
arr.unshift(0); // 头部加 → [0,1,2,3,4,5]
arr.shift(); // 头部删 → [1,2,3,4,5]
// 遍历
arr.forEach(item => console.log(item));
// 映射(返回新数组)
const double = arr.map(item => item * 2); // [2,4,6,8,10]
// 过滤(返回新数组)
const even = arr.filter(item => item % 2 === 0); // [2,4]
// 查找
const found = arr.find(item => item > 3); // 4
const index = arr.findIndex(item => item > 3); // 3
// 判断
const hasEven = arr.some(item => item % 2 === 0); // true
const allEven = arr.every(item => item % 2 === 0); // false
// 累加
const sum = arr.reduce((acc, cur) => acc + cur, 0); // 15
4. 异步编程(最难也最重要)
4.1 为什么需要异步?
// 同步(会卡住)
console.log('开始');
for(let i = 0; i < 1000000000; i++) {} // 卡死
console.log('结束'); // 要等上面循环完
// 异步(不卡)
console.log('开始');
setTimeout(() => {
console.log('1秒后');
}, 1000);
console.log('结束'); // 先打印,不等setTimeout
4.2 回调函数(最原始)
// 这就是一个回调函数
function callback() {
console.log('回调被执行了');
}
// 这个函数接收一个函数作为参数
function doSomething(cb) {
console.log('做事中...');
cb(); // 执行回调
}
doSomething(callback);
// 输出:
// 做事中...
// 回调被执行了
4.3 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.log('失败:', error);
});
4.4 async/await(终极方案)
async/await 是 JavaScript 中处理异步操作的一种语法糖,建立在 Promise 之上,让异步代码写起来和读起来都像同步代码。
async 函数
在函数声明前加上 async 关键字,该函数就会返回一个 Promise:
async function fetchData() {
return '数据';
}
// 等价于
function fetchData() {
return Promise.resolve('数据');
}
// 调用
fetchData().then(data => console.log(data)); // 输出: 数据
await 关键字
await 只能用在 async 函数内部,它会暂停函数执行,等待 Promise 解决:
async function getData() {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
}
实际应用示例
基本用法对比
// Promise 链式调用
function getUserInfo(userId) {
return fetch(`/api/users/${userId}`)
.then(response => response.json())
.then(user => {
return fetch(`/api/posts?userId=${user.id}`);
})
.then(response => response.json())
.catch(error => console.error('错误:', error));
}
// Async/Await 版本
async function getUserInfo(userId) {
try {
const userResponse = await fetch(`/api/users/${userId}`);
const user = await userResponse.json();
const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
const posts = await postsResponse.json();
return posts;
} catch (error) {
console.error('错误:', error);
}
}
错误处理
async function fetchDataWithError() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`HTTP 错误! 状态码: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error.message);
// 可以返回默认值或重新抛出
return { error: true, message: error.message };
}
}
// 或者使用 .catch()
async function fetchData() {
const data = await fetch('https://api.example.com/data')
.catch(error => {
console.error('网络错误:', error);
return { error: true };
});
return data;
}
并行执行
// 串行执行(耗时较长)
async function loadSequential() {
const start = Date.now();
const user = await fetch('/api/user');
const posts = await fetch('/api/posts');
const comments = await fetch('/api/comments');
console.log('耗时:', Date.now() - start, 'ms');
return { user, posts, comments };
}
// 并行执行(更高效)
async function loadParallel() {
const start = Date.now();
const [user, posts, comments] = await Promise.all([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
]);
console.log('耗时:', Date.now() - start, 'ms');
return { user, posts, comments };
}
// 处理多个 Promise,但不想全部失败
async function loadWithSettled() {
const results = await Promise.allSettled([
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
]);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`请求 ${index} 成功:`, result.value);
} else {
console.log(`请求 ${index} 失败:`, result.reason);
}
});
}
5. 面试进阶概念
5.1 this 指向(经典难题)
this 的绑定规则(按优先级从高到低)
new 绑定
使用 new 调用函数时,this 指向新创建的对象:
function Person(name) {
this.name = name;
this.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
}
const p1 = new Person('Alice');
const p2 = new Person('Bob');
p1.sayHi(); // Hi, I'm Alice
p2.sayHi(); // Hi, I'm Bob
显式绑定(call, apply, bind)
通过 call、apply、bind 手动指定 this:
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: 'Alice' };
// call - 参数列表
greet.call(person, 'Hello', '!'); // Hello, Alice!
// apply - 参数数组
greet.apply(person, ['Hi', '...']); // Hi, Alice...
// bind - 返回新函数,永久绑定 this
const boundGreet = greet.bind(person, 'Hey');
boundGreet('?'); // Hey, Alice?
隐式绑定
函数作为对象的方法调用时,this 指向该对象:
const user = {
name: 'Alice',
age: 25,
greet() {
console.log(`Hello, I'm ${this.name}`);
},
friends: ['Bob', 'Charlie'],
showFriends() {
this.friends.forEach(function(friend) {
// 这里的 this 不是 user 对象!
console.log(`${this.name}'s friend: ${friend}`);
});
}
};
user.greet(); // Hello, I'm Alice
user.showFriends();
// undefined's friend: Bob (this 指向 window/全局对象)
默认绑定
独立函数调用时,this 指向全局对象(浏览器中的 window,Node.js 中的 global),严格模式下为 undefined:
function showThis() {
console.log(this);
}
// 非严格模式
showThis(); // window (浏览器) 或 global (Node.js)
// 严格模式
function showThisStrict() {
'use strict';
console.log(this);
}
showThisStrict(); // undefined
常见场景的 this 指向
箭头函数
箭头函数没有自己的 this,它继承定义时的外层作用域的 this:
const obj = {
name: 'Alice',
regularFunc: function() {
console.log('regular:', this.name);
setTimeout(function() {
console.log('regular timeout:', this.name); // undefined
}, 100);
setTimeout(() => {
console.log('arrow timeout:', this.name); // Alice
}, 100);
},
arrowFunc: () => {
console.log('arrow:', this.name); // undefined (this 是外层作用域的 this)
}
};
obj.regularFunc();
obj.arrowFunc();
事件处理函数
// DOM 事件
button.addEventListener('click', function() {
console.log(this); // 指向 button 元素
});
button.addEventListener('click', () => {
console.log(this); // 指向外层作用域的 this(可能是 window)
});
// 对象方法作为事件处理
const handler = {
name: 'MyHandler',
handleClick: function() {
console.log(this.name);
}
};
button.addEventListener('click', handler.handleClick);
// 点击时输出 undefined(this 指向 button)
// 正确方式
button.addEventListener('click', () => handler.handleClick());
// 或
button.addEventListener('click', handler.handleClick.bind(handler));
类中的 this
class MyClass {
constructor(name) {
this.name = name;
// 方法1:箭头函数(自动绑定)
this.sayHiArrow = () => {
console.log(`Hi, I'm ${this.name}`);
};
}
// 方法2:常规方法
sayHiRegular() {
console.log(`Hi, I'm ${this.name}`);
}
// 方法3:在构造函数中 bind
constructor(name) {
this.name = name;
this.sayHiRegular = this.sayHiRegular.bind(this);
}
}
const obj = new MyClass('Alice');
const { sayHiArrow, sayHiRegular } = obj;
sayHiArrow(); // Hi, I'm Alice(箭头函数保持 this)
sayHiRegular(); // undefined(丢失 this)
5.2 原型链
// 每个对象都有__proto__,指向它的原型
const arr = [1,2,3];
arr.__proto__ === Array.prototype; // true
Array.prototype.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
// 实现继承
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
function Child(name, age) {
Parent.call(this, name); // 继承属性
this.age = age;
}
Child.prototype = Object.create(Parent.prototype); // 继承方法
Child.prototype.constructor = Child;
5.3 事件循环(Event Loop)
console.log('1'); // 同步
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步
// 打印顺序:1, 4, 3, 2
// 同步 → 微任务 → 宏任务
6. 常用工具方法
6.1 防抖和接楼
一句话理解
防抖:拖延症患者 - 一直催就一直推迟,不催了才开始干活
节流:公交车司机 - 不管多少人等,固定时间发一班车
防抖(搜索框)
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer); // 取消上次
timer = setTimeout(() => fn.apply(this, args), delay); // 重新计时
};
}
// 使用:input输入停止500ms后搜索
input.addEventListener('input', debounce(search, 500));
节流(滚动加载)
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime > delay) { // 时间到了才执行
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用:滚动时每200ms加载一次
window.addEventListener('scroll', throttle(loadMore, 200));
6.2 深拷贝和浅拷贝
一句话理解
浅拷贝:只复制第一层,内部对象还是共用的
深拷贝:完全复制所有层,新旧对象完全独立
核心区别
const original = {
name: 'Alice',
age: 25,
skills: ['js', 'react'], // 引用类型
address: {
city: '北京',
zip: '100000'
}
};
浅拷贝结果
const shallow = { ...original };
shallow.name = 'Bob'; // ✅ original.name 不变
shallow.skills.push('vue'); // ❌ original.skills 也被改了!
shallow.address.city = '上海'; // ❌ original.address.city 也被改了!
console.log(original.skills); // ['js', 'react', 'vue'] 😱
深拷贝结果
const deep = JSON.parse(JSON.stringify(original));
deep.name = 'Bob'; // ✅ original.name 不变
deep.skills.push('vue'); // ✅ original.skills 不变
deep.address.city = '上海'; // ✅ original.address.city 不变
console.log(original.skills); // ['js', 'react'] 😊
6.3 实现方式
浅拷贝方法
// 1. 扩展运算符
const copy1 = { ...obj };
const copyArr1 = [...arr];
// 2. Object.assign
const copy2 = Object.assign({}, obj);
// 3. 数组方法
const copyArr2 = arr.slice();
const copyArr3 = arr.concat();
// 4. 循环遍历
const copy4 = {};
for (let key in obj) {
copy4[key] = obj[key];
}
深拷贝方法
// 1. JSON 方法(最简单,但有坑)
const copy1 = JSON.parse(JSON.stringify(obj));
// 缺点:不能复制函数、undefined、Symbol、循环引用
// 2. 递归实现
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
const clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
// 3. 使用第三方库
// const copy = _.cloneDeep(obj); // lodash
// const copy = structuredClone(obj); // 现代浏览器内置