JavaScript 闭包与作用域
闭包是 JavaScript 的核心概念之一,理解它对于深入学习 JavaScript 至关重要。
作用域
全局作用域
const globalVar = "全局变量";
function test() {
console.log(globalVar); // 可以访问全局变量
}
函数作用域
function outer() {
const outerVar = "外部变量";
function inner() {
const innerVar = "内部变量";
console.log(outerVar); // 可以访问外部变量
console.log(innerVar); // 可以访问内部变量
}
inner();
console.log(innerVar); // 错误:无法访问内部变量
}
块级作用域
// let 和 const 有块级作用域
if (true) {
let blockVar = "块级变量";
const constVar = "常量";
var functionVar = "函数变量"; // var 没有块级作用域
}
console.log(functionVar); // "函数变量"
console.log(blockVar); // 错误:无法访问
词法作用域
const x = 10;
function outer() {
const x = 20;
function inner() {
console.log(x); // 20,引用最近的 x
}
inner();
}
outer(); // 20
闭包
什么是闭包
闭包是指函数能够记住并访问其词法作用域,即使函数在其词法作用域之外执行。
function createCounter() {
let count = 0; // 私有变量
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 被"封闭"在 counter 函数的作用域中
闭包的形成
function outer() {
const outerVar = "外部变量";
return function inner() {
console.log(outerVar); // 闭包
};
}
const fn = outer();
fn(); // "外部变量" - 即使 outer 已经执行完毕
闭包经典案例
计数器
function makeCounter(initial = 0) {
let count = initial;
return {
increment() {
return ++count;
},
decrement() {
return --count;
},
getCount() {
return count;
},
reset() {
count = initial;
return count;
}
};
}
const counter = makeCounter(10);
console.log(counter.increment()); // 11
console.log(counter.increment()); // 12
console.log(counter.decrement()); // 11
console.log(counter.getCount()); // 11
console.log(counter.reset()); // 10
防抖函数
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
const handleSearch = debounce((query) => {
console.log("搜索:", query);
}, 300);
节流函数
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(this, args);
}
};
}
const handleScroll = throttle(() => {
console.log("滚动位置:", window.scrollY);
}, 200);
缓存函数(记忆化)
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.log(memoizedFib(10)); // 55
循环中的闭包
// 问题:所有函数共享同一个 i
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 3, 3, 3
}
// 解决方案1:使用 let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// 解决方案2:使用 IIFE
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 100);
})(i); // 0, 1, 2
}
// 解决方案3:使用闭包创建工厂函数
function createLogger(num) {
return () => console.log(num);
}
for (var i = 0; i < 3; i++) {
setTimeout(createLogger(i), 100); // 0, 1, 2
}
原型链
prototype 属性
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
return `你好,我叫${this.name},今年${this.age}岁`;
};
const person = new Person("张三", 20);
console.log(person.greet()); // 你好,我叫张三,今年20岁
console.log(Person.prototype); // { greet: f, constructor: f }
proto 属性
const obj = { name: "张三" };
console.log(obj.__proto__ === Object.prototype); // true
function Person() {}
const person = new Person();
console.log(person.__proto__ === Person.prototype); // true
console.log(person.__proto__.__proto__ === Object.prototype); // true
console.log(person.__proto__.__proto__.__proto__); // null
原型链
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name}正在吃东西`);
};
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(`${this.name}汪汪叫`);
};
const dog = new Dog("旺财", "金毛");
// 访问顺序:
// dog.eat() -> dog.__proto__ -> Dog.prototype -> Animal.prototype -> Object.prototype
原型链继承
// 定义父类
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name}发出声音`);
}
}
// 定义子类
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
console.log(`${this.name}汪汪叫`);
}
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
原型方法
function Person(name) {
this.name = name;
}
const person1 = new Person("张三");
const person2 = new Person("李四");
// isPrototypeOf - 检查是否是另一个对象的原型
console.log(Person.prototype.isPrototypeOf(person1)); // true
// hasOwnProperty - 检查属性是否是对象自身的
console.log(person1.hasOwnProperty("name")); // true
console.log(person1.hasOwnProperty("greet")); // false
// propertyIsEnumerable - 检查属性是否可枚举
console.log(person1.propertyIsEnumerable("name")); // true
// getPrototypeOf - 获取对象的原型
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true
// setPrototypeOf - 设置对象的原型(不推荐)
const proto = { greeting: "你好" };
Object.setPrototypeOf(person1, proto);
原型链图解
person 对象
↓ __proto__
Person.prototype
↓ __proto__
Object.prototype
↓ __proto__
null
继承方式对比
原型链继承
function Parent() {
this.name = "parent";
}
Parent.prototype.say = function() {
console.log(this.name);
};
function Child() {
this.name = "child";
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child = new Child();
child.say(); // child
构造函数继承
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.say = function() {
console.log(this.name);
};
function Child(name) {
Parent.call(this, name); // 继承属性
}
const child = new Child("child");
// 缺点:无法继承原型上的方法
组合继承
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.say = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 继承属性
this.age = age;
}
Child.prototype = new Parent(); // 继承方法
Child.prototype.constructor = Child;
原型继承
function createObject(o) {
function F() {}
F.prototype = o;
return new F();
}
const parent = {
name: "parent",
friends: ["A", "B"]
};
const child = createObject(parent);
child.friends.push("C");
console.log(parent.friends); // ["A", "B", "C"] - 引用共享
寄生式继承
function createChild(parent) {
const child = Object.create(parent);
child.sayHi = function() {
console.log("你好");
};
return child;
}
寄生组合继承(最佳方案)
function inheritPrototype(Child, Parent) {
const prototype = Object.create(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;
}
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
const child = new Child("张三", 20);
child.say();
小结
- 作用域决定了变量的可见性和生命周期
- 闭包是函数能记住并访问其词法作用域的机制
- 闭包常用于创建私有变量和函数工厂
- 原型链实现了 JavaScript 的继承机制
- 每个函数都有 prototype 属性
- 对象通过 proto 指向其原型
练习
- 使用闭包实现一个私有变量的案例
- 使用闭包实现一个模块模式
- 实现一个简易的 EventEmitter(发布/订阅模式)
- 使用原型链实现一个继承结构
- 比较各种继承方式的优缺点