【JS进阶-Day1】作用域、闭包与ES6语法
📺 对应视频:P152-P163 | 🎯 核心目标:深入理解作用域链与垃圾回收、掌握闭包原理与应用、ES6常用语法
一、作用域链深入
1.1 词法作用域(静态作用域)
JS 采用词法 作用域:函数的作用域在定义时就确定了,而不是调用时。
let x = 'global'
function outer() {
let x = 'outer'
function inner() {
console.log(x) // 'outer'(定义时就确定了,向外找到 outer 的 x)
}
return inner
}
const fn = outer()
fn() // 仍然输出 'outer',不是 'global'
// 因为 inner 的作用域链在 outer 内部定义时就绑定了
1.2 变量查找规则
作用域链查找过程:
当前作用域 → 外层作用域 → ... → 全局作用域 → 找不到 → ReferenceError
// 内层可以访问外层,外层不能访问内层
function outer() {
let a = 1
function inner() {
let b = 2
console.log(a) // ✅ 1(内层访问外层)
}
console.log(b) // ❌ ReferenceError(外层访问内层)
}
二、垃圾回收机制
2.1 内存生命周期
分配内存 → 使用内存 → 释放内存(垃圾回收)
2.2 引用计数法(旧,已废弃)
// 问题:循环引用导致内存泄漏
function createCircle() {
let a = {}
let b = {}
a.ref = b // a 引用 b
b.ref = a // b 引用 a(循环引用!引用计数永远不为0)
}
createCircle()
// 旧算法:a 和 b 永远不会被回收
2.3 标记清除法(现代,主流)
1. 从根(window/全局)出发
2. 标记所有可达的对象
3. 清除未被标记的对象(不可达 = 垃圾)
2.4 内存泄漏常见场景
// 1. 意外的全局变量
function fn() {
leak = '未声明的变量' // 成为全局变量,永不释放
}
// 2. 忘记清除的定时器
const timer = setInterval(() => {
// 持有 DOM 引用
document.querySelector('.box').style.color = 'red'
}, 1000)
// 应在不需要时 clearInterval(timer)
// 3. 未移除的事件监听
const btn = document.querySelector('button')
function handler() { ... }
btn.addEventListener('click', handler)
// 组件销毁时应 btn.removeEventListener('click', handler)
// 4. 闭包持有不再需要的大对象
function createLeak() {
const bigData = new Array(1000000).fill('*')
return () => bigData.length // 闭包持有 bigData
}
三、闭包(Closure)
3.1 什么是闭包?
闭包 = 函数 + 其能访问的外部作用域
当一个函数能够访问并记住其词法作用域中的变量,即使该函数在其词法作用域外执行,就产生了闭包。
function makeCounter() {
let count = 0 // 外部函数的局部变量
return function() { // 返回的内部函数形成闭包
count++ // 访问并记住了 count
return count
}
}
const counter = makeCounter()
counter() // 1
counter() // 2
counter() // 3
// count 不会消失,被闭包保持着!
3.2 闭包的核心特性
// 1. 每次调用外部函数都创建独立的闭包
const counter1 = makeCounter()
const counter2 = makeCounter()
counter1() // 1(counter1 自己的 count)
counter1() // 2
counter2() // 1(counter2 独立的 count,互不影响)
// 2. 闭包可以修改外部变量
function makeAdder(base) {
return function(num) {
base += num // 修改外部变量 base
return base
}
}
const add = makeAdder(10)
add(5) // 15(base 变成 15)
add(3) // 18(base 变成 18)
3.3 闭包的实际应用
// 1. 私有变量(模块模式)
function createBankAccount(initialBalance) {
let balance = initialBalance // 私有变量,外部无法直接访问
return {
deposit(amount) {
balance += amount
console.log(`存入 ${amount},余额:${balance}`)
},
withdraw(amount) {
if (amount > balance) {
console.log('余额不足')
return
}
balance -= amount
console.log(`取出 ${amount},余额:${balance}`)
},
getBalance() {
return balance
}
}
}
const account = createBankAccount(1000)
account.deposit(500) // 存入 500,余额:1500
account.withdraw(200) // 取出 200,余额:1300
// account.balance // undefined(私有,访问不到!)
// 2. 函数工厂
function multiplier(factor) {
return num => num * factor
}
const double = multiplier(2)
const triple = multiplier(3)
double(5) // 10
triple(5) // 15
// 3. 记忆化(Memoization)
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
}
}
const expensiveCalc = memoize((n) => {
// 假设是耗时计算
return n * n
})
expensiveCalc(5) // 25(计算)
expensiveCalc(5) // 25(从缓存取)
四、ES6 重要语法
4.1 解构赋值
// 数组解构
const [a, b, c] = [1, 2, 3] // a=1, b=2, c=3
const [x, , z] = [1, 2, 3] // 跳过:x=1, z=3
const [first, ...rest] = [1, 2, 3, 4] // first=1, rest=[2,3,4]
const [p = 10, q = 20] = [5] // 默认值:p=5, q=20
// 交换变量(经典用法)
let m = 1, n = 2
;[m, n] = [n, m] // m=2, n=1
// 对象解构
const { name, age } = { name: '张三', age: 18 }
const { name: myName, age: myAge } = { name: '张三', age: 18 } // 重命名
const { address: { city } } = { address: { city: '北京' } } // 嵌套解构
const { score = 100, grade = 'A' } = { score: 85 } // 默认值:grade='A'
// 函数参数解构(非常常用!)
function showUser({ name, age, gender = '未知' }) {
console.log(`${name},${age}岁,${gender}`)
}
showUser({ name: '张三', age: 18 }) // '张三,18岁,未知'
4.2 展开运算符与剩余参数
// 展开运算符(...):将可迭代对象展开
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const merged = [...arr1, ...arr2] // [1, 2, 3, 4, 5, 6]
// 展开对象(浅拷贝)
const obj1 = { a: 1, b: 2 }
const obj2 = { ...obj1, c: 3 } // { a: 1, b: 2, c: 3 }
const clone = { ...obj1 } // 浅拷贝
// 函数调用时展开
Math.max(...[1, 5, 3, 2]) // 5
// 剩余参数(...args):收集剩余参数为数组
function sum(first, ...others) {
return others.reduce((acc, n) => acc + n, first)
}
sum(1, 2, 3, 4) // 10
4.3 箭头函数进阶
// 箭头函数的 this 继承自定义时的外层作用域(词法this)
class Timer {
constructor() {
this.seconds = 0
}
start() {
// 普通函数:this 丢失(undefined 或 window)
setInterval(function() {
this.seconds++ // ❌ this 不是 Timer 实例
}, 1000)
// 箭头函数:this 正确(继承自 start 方法的 this = Timer 实例)
setInterval(() => {
this.seconds++ // ✅ this 是 Timer 实例
}, 1000)
}
}
4.4 Symbol(简介)
// Symbol:唯一的原始值,常用作对象的唯一属性键
const id = Symbol('id')
const id2 = Symbol('id')
id === id2 // false(永远不相等)
const obj = {
[id]: 123,
name: '张三'
}
obj[id] // 123(只能通过 Symbol 访问)
// for...in 和 Object.keys 不会遍历 Symbol 属性
五、知识图谱
作用域、闭包与ES6
├── 作用域链
│ ├── 词法作用域(定义时确定)
│ └── 查找规则(内→外→全局→报错)
├── 垃圾回收
│ ├── 标记清除(主流)
│ └── 内存泄漏:全局变量/定时器/监听器/闭包
├── 闭包
│ ├── 本质:函数+词法环境
│ ├── 特性:记住外部变量、每次调用独立
│ └── 应用:私有变量/函数工厂/记忆化
└── ES6 语法
├── 解构:数组/对象/函数参数解构
├── 展开运算符(...):数组合并、对象拷贝
├── 剩余参数(...args):收集剩余
└── 箭头函数(词法 this)
六、高频面试题
Q1:什么是闭包?
闭包是指函数能够访问并记住其定义时所在词法作用域中的变量,即使函数在词法作用域外执行。常见用途:私有变量、函数工厂、记忆化、模块化。
Q2:闭包会引起内存泄漏吗?
闭包本身不会导致内存泄漏,但如果闭包持有不再需要的大对象引用,且闭包自身一直存在(如绑定在全局事件上),就会导致内存无法回收。解决方案:及时解除引用、移除事件监听。
Q3: var 在循环中的闭包陷阱?
// 经典问题
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0)
}
// 输出:3 3 3(var 没有块级作用域,所有回调共享同一个 i)
// 解决方案一:改用 let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0) // 0 1 2
}
// 解决方案二:IIFE
for (var i = 0; i < 3; i++) {
;(function(j) {
setTimeout(() => console.log(j), 0) // 0 1 2
})(i)
}
⬅️ 上一篇:Web APIs Day6&7 - 正则与综合实战 ➡️ 下一篇:JS进阶Day2 - 构造函数与内置对象方法