一、作用域 (Scope)
作用域决定了变量和函数的可访问性(生命周期)。理解作用域是理解闭包和this的基础。
1.1 全局作用域
- 在函数和代码块
{}之外定义的变量。 - 在浏览器中,全局作用域是
window,在Node.js中是global。 - 慎用:过多的全局变量容易造成命名冲突和污染。
1.2 函数作用域
- 在函数内部定义的变量,只能在函数内部访问。
- 适用于
var声明的变量。
function func() {
var a = 10; // 函数作用域
console.log(a); // 10
}
console.log(a); // ReferenceError: a is not defined
1.3 块级作用域
- ES6 引入
let和const,实现了块级作用域。 - 由
{}包裹的代码块(如if、for、while)。
if (true) {
let b = 20;
const c = 30;
var d = 40; // 没有块级作用域,会泄露到全局
}
console.log(d); // 40
console.log(b); // ReferenceError: b is not defined
1.4 词法作用域(静态作用域)
- 函数的作用域在定义时确定,而非调用时。
- 作用域链:当在函数内部访问变量时,会从当前作用域向上级作用域逐级查找,直到全局作用域。
let name = "Global";
function outer() {
let name = "Outer";
function inner() {
console.log(name);
}
inner(); // 输出 "Outer"
}
outer();
二、解构 (Destructuring)
解构是一种方便快捷的从数组或对象中提取值并赋给变量的语法糖。
2.1 数组解构
- 按位置匹配。
const arr = [1, 2, 3];
const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3
// 跳过元素、设置默认值
const [x, , y, z = 10] = [4, 5, 6];
console.log(x, y, z); // 4 6 10
// 交换变量
let i = 1, j = 2;
[i, j] = [j, i];
console.log(i, j); // 2 1
2.2 对象解构
- 按属性名匹配,顺序无关。
const person = { name: "Alice", age: 25, city: "Beijing" };
const { name, age } = person;
console.log(name, age); // Alice 25
// 别名与默认值
const { name: userName, gender = "Unknown" } = person;
console.log(userName, gender); // Alice Unknown
// 嵌套解构
const student = { info: { id: 101, score: 95 } };
const { info: { id, score } } = student;
console.log(id, score); // 101 95
2.3 函数参数解构
- 极大提高可读性,常用于配置对象。
function greet({ name, age = 18 }) {
console.log(`Hello, I'm ${name}, ${age} years old.`);
}
greet({ name: "Bob" }); // Hello, I'm Bob, 18 years old.
三、箭头函数 (Arrow Function)
箭头函数提供了一种更简洁的函数书写方式,并在 this 处理上有根本性不同。
3.1 基本语法
(参数) => { 函数体 }- 只有一个参数时可省略小括号;函数体只有一条返回语句时可省略
{}和return。
const add = (a, b) => a + b;
const square = x => x * x;
const sayHi = () => console.log("Hi!");
3.2 与普通函数的区别(重点)
- 没有自己的
this:this指向定义时所在的外部作用域(继承外层),且无法通过call、apply、bind改变。 - 不能作为构造函数:不能使用
new调用。 - 没有
arguments对象:可以使用 rest 参数...args代替。 - 没有
prototype属性。
const obj = {
name: "FrontEnd",
regular: function() {
setTimeout(function() {
console.log(this.name); // undefined (指向window/global)
}, 100);
},
arrow: function() {
setTimeout(() => {
console.log(this.name); // FrontEnd (指向外层obj)
}, 100);
}
};
obj.regular();
obj.arrow();
四、构造函数 (Constructor)
构造函数是用于创建和初始化对象的特殊函数。ES6 之前,这是实现“类”的主要方式。
4.1 基本使用
- 函数名首字母大写(约定)。
- 使用
new关键字调用。 - 构造函数内部使用
this绑定新对象的属性和方法。
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
}
const p1 = new Person("Tom", 20);
p1.sayHi(); // Hi, I'm Tom
4.2 原型链与共享方法
- 构造函数的
prototype属性指向原型对象,原型对象上的方法被所有实例共享。 - 每个实例通过
__proto__指向构造函数的prototype。
Person.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
p1.eat(); // Tom is eating.
console.log(p1.__proto__ === Person.prototype); // true
4.3 实现继承
- 原型链继承:
Child.prototype = new Parent() - 组合继承(借用构造函数 + 原型链)
- 寄生组合继承(最理想方式)
function Student(name, age, grade) {
Person.call(this, name, age); // 继承属性
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
const s1 = new Student("Jerry", 22, "A");
s1.sayHi(); // Hi, I'm Jerry
五、数据常用函数
掌握数组和对象的常用高级函数,能让你写出更简洁高效的代码。
5.1 数组函数(高阶函数为主)
forEach:遍历,不返回新数组。map:映射,返回新数组。filter:过滤,返回满足条件的新数组。reduce:累加器,强大且灵活。some/every:判断是否至少一个/全部满足条件。find/findIndex:查找第一个满足条件的元素或索引。
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(n => n * 2); // [2,4,6,8,10]
const evens = nums.filter(n => n % 2 === 0); // [2,4]
const sum = nums.reduce((acc, cur) => acc + cur, 0); // 15
const hasEven = nums.some(n => n % 2 === 0); // true
5.2 对象函数
Object.keys():获取所有键名(数组)。Object.values():获取所有值。Object.entries():获取键值对数组。Object.assign():合并对象(浅拷贝)。Object.freeze():冻结对象,不可修改。
const obj = { a: 1, b: 2 };
console.log(Object.entries(obj)); // [['a',1], ['b',2]]
const merged = Object.assign({}, obj, { c: 3 });
六、面向对象 (OOP)
ES6 引入 class 语法糖,让 JS 的面向对象更清晰、更接近传统语言。
6.1 Class 基本语法
constructor是构造方法。- 方法直接定义在
class中,自动挂在原型上。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
6.2 静态属性与方法
- 使用
static关键字,属于类本身,不属于实例。
class MathUtil {
static PI = 3.14159;
static areaOfCircle(radius) {
return this.PI * radius * radius;
}
}
console.log(MathUtil.areaOfCircle(5));
6.3 继承 (extends)
- 使用
extends继承父类。 - 子类
constructor中必须调用super()才能使用this。
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造
this.breed = breed;
}
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Buddy", "Golden");
d.speak(); // Buddy barks.
6.4 私有属性 (Private Fields)
- 使用
#前缀,ES2022 正式支持。
class Wallet {
#balance = 0;
deposit(amount) {
this.#balance += amount;
}
getBalance() {
return this.#balance;
}
}
七、高阶技巧
这些技巧能帮你写出更优雅、健壮和高效的代码。
7.1 闭包 (Closure)
- 定义:函数 + 其词法环境的组合。即内部函数可以访问外部函数的变量,即使外部函数已执行完毕。
- 用途:数据私有化、柯里化、防抖/节流。
function counter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const myCounter = counter();
myCounter(); // 1
myCounter(); // 2
7.2 防抖 (Debounce) 与节流 (Throttle)
- 防抖:在连续触发中,只执行最后一次。场景:搜索输入框。
- 节流:在连续触发中,每隔一段时间执行一次。场景:滚动监听。
// 防抖简易实现
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
7.3 深拷贝与浅拷贝
- 浅拷贝:
Object.assign()、扩展运算符...(仅拷贝一层)。 - 深拷贝:
JSON.parse(JSON.stringify(obj))(有局限,无法处理函数、循环引用等),或使用structuredClone()(现代浏览器/Node.js 17+)。
7.4 Promise 与异步进阶
Promise.all:所有Promise成功才成功,返回所有结果数组。Promise.race:返回第一个完成的Promise结果。Promise.allSettled:所有Promise完成(无论成功/失败)后返回。async/await:同步写法处理异步,更优雅。
7.5 可选链 (?.) 与空值合并 (??)
- 可选链:安全地访问深层嵌套属性,避免
Cannot read property of undefined。 - 空值合并:只有当左侧为
null或undefined时,才返回右侧值。
const user = { profile: { name: "John" } };
console.log(user?.profile?.age ?? "Age unknown"); // Age unknown