前端八股---闭包和作用域链

目录

一、闭包(Closure)

[1.1 一句话定义](#1.1 一句话定义)

[1.2 核心三点(必背)](#1.2 核心三点(必背))

[1.3 代码示例](#1.3 代码示例)

二、闭包的常见应用场景

[1. 私有变量(模块化)](#1. 私有变量(模块化))

[2. 防抖函数](#2. 防抖函数)

[3. 节流函数](#3. 节流函数)

[4. 循环中的经典问题(面试常考)](#4. 循环中的经典问题(面试常考))

三、闭包的优缺点

优点

缺点

[四、作用域链(Scope Chain)](#四、作用域链(Scope Chain))

[4.1 定义](#4.1 定义)

[4.2 图解作用域链](#4.2 图解作用域链)

[4.3 作用域的类型](#4.3 作用域的类型)

五、作用域链的特点

[1. 只能由内向外查找](#1. 只能由内向外查找)

[2. 词法作用域(静态作用域)](#2. 词法作用域(静态作用域))

[3. 内层共享外层变量](#3. 内层共享外层变量)

六、闭包和作用域链的关系(面试必问)

七、面试高频问题

Q1:什么是闭包?有什么作用?

Q2:闭包会导致内存泄漏吗?

Q3:什么是作用域链?

Q4:闭包和作用域链的关系?


一、闭包(Closure)

1.1 一句话定义

函数嵌套函数,内部函数引用了外部函数的变量,就形成了闭包。这些变量不会被垃圾回收,会一直保留在内存中。

1.2 核心三点(必背)

复制代码
1. 函数嵌套函数
2. 内部函数引用外部函数的变量/参数
3. 外部函数执行完后,变量依然保存在内存中

1.3 代码示例

javascript 复制代码
function outer() {
  let a = 1                    // 外部函数的变量
  
  function inner() {           // 1. 函数嵌套函数
    console.log(a)             // 2. 内部函数引用外部变量 → 形成闭包
  }
  
  return inner                 // 把内部函数返回出去
}

const fn = outer()             // outer 执行完了
fn()                           // 3. 依然能访问到 a → 输出 1

执行过程图解:

复制代码
outer() 执行:
┌─────────────────────────────────────────┐
│  outer 作用域                            │
│  a = 1                                  │
│  inner = function                       │
└─────────────────────────────────────────┘
         │
         │ 返回 inner
         ↓
outer 执行完毕,按理说 a 应该被回收
         │
         │ 但 inner 还在引用 a
         ↓
┌─────────────────────────────────────────┐
│  闭包:a 被保留在内存中                   │
│  fn 可以继续访问 a                        │
└─────────────────────────────────────────┘

二、闭包的常见应用场景

1. 私有变量(模块化)

javascript 复制代码
function createCounter() {
  let count = 0           // 私有变量,外部无法直接访问
  
  return {
    increment() {
      count++
      return count
    },
    decrement() {
      count--
      return count
    },
    getCount() {
      return count
    }
  }
}

const counter = createCounter()
console.log(counter.getCount())  // 0
counter.increment()               // 1
counter.increment()               // 2
console.log(counter.count)        // undefined(无法直接访问)

2. 防抖函数

javascript 复制代码
function debounce(fn, delay) {
  let timer = null               // 闭包保存 timer
  
  return function(...args) {
    clearTimeout(timer)          // 每次调用都能访问到同一个 timer
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

const debouncedSearch = debounce(search, 500)

3. 节流函数

javascript 复制代码
function throttle(fn, interval) {
  let lastTime = 0               // 闭包保存 lastTime
  
  return function(...args) {
    const now = Date.now()
    if (now - lastTime >= interval) {
      fn.apply(this, args)
      lastTime = now
    }
  }
}

4. 循环中的经典问题(面试常考)

javascript 复制代码
// ❌ 错误:var 没有块级作用域
for (var i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i)  // 输出 3, 3, 3(不是 0,1,2)
  }, 100)
}

// ✅ 解决方案1:闭包
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => {
      console.log(j)  // 输出 0,1,2
    }, 100)
  })(i)
}

// ✅ 解决方案2:let(推荐)
for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i)  // 输出 0,1,2
  }, 100)
}

块级作用域让每次循环都能"新建"一个独立的变量,而不是共用一个变量。

var let
只有一个 i,循环结束后变成 3 每次循环都有一个独立的 i
所有定时器都读取同一个 i(3) 每个定时器读取自己的 i(0,1,2)
输出 3 3 3 输出 0 1 2

三、闭包的优缺点

优点

优点 说明
私有化变量 避免污染全局作用域
变量长期驻留 实现缓存、累加器等
模块化 暴露方法,隐藏内部数据
函数工厂 动态生成函数

缺点

缺点 说明 解决方案
内存泄漏 变量不被回收 不用时手动赋值为 null
性能开销 比普通函数多占用内存 谨慎使用,及时释放
javascript 复制代码
// 手动释放闭包引用
function createBigData() {
  let bigData = new Array(1000000).fill('data')
  
  return function() {
    return bigData.length
  }
}

const fn = createBigData()
console.log(fn())  // 使用

// 不再需要时,解除引用
fn = null          // bigData 才会被垃圾回收

四、作用域链(Scope Chain)

4.1 定义

当你在代码中使用一个变量时,JS 会先在当前作用域找,找不到就去外层作用域找,再找不到继续往外,直到全局作用域。这条从内到外的查找路径,就叫作用域链。

4.2 图解作用域链

javascript 复制代码
// 全局作用域
let globalVar = 'global'

function outer() {
  // outer 函数作用域
  let outerVar = 'outer'
  
  function inner() {
    // inner 函数作用域
    let innerVar = 'inner'
    
    console.log(innerVar)   // 找自己 → 找到 ✅
    console.log(outerVar)   // 自己找不到 → 去 outer 找 → 找到 ✅
    console.log(globalVar)  // outer 找不到 → 去全局找 → 找到 ✅
    console.log(xxx)        // 全局也找不到 → 报错 ❌
  }
  
  inner()
}

outer()

作用域链图示:

复制代码
inner 作用域
    │
    │ 找不到就去
    ↓
outer 作用域
    │
    │ 找不到就去
    ↓
全局作用域
    │
    │ 找不到就报错
    ↓
  undefined / 报错

4.3 作用域的类型

作用域类型 说明 示例
全局作用域 在任何地方都能访问 windowglobalThis
函数作用域 函数内部声明的变量,外部访问不到 function fn() { var a = 1 }
块级作用域 {} 内部声明的变量,只在块内有效 if () { let a = 1 }

五、作用域链的特点

1. 只能由内向外查找

javascript 复制代码
let a = 'global'

function outer() {
  let a = 'outer'    // 同名变量会遮蔽外层的
  
  function inner() {
    let a = 'inner'
    console.log(a)   // 'inner'(找到就停,不会继续向外)
  }
  
  inner()
}

2. 词法作用域(静态作用域)

作用域在函数定义时就确定了,不是调用时!

javascript 复制代码
let a = 'global'

function fn1() {
  console.log(a)     // 定义时 a 指向全局
}

function fn2() {
  let a = 'fn2'
  fn1()              // 调用 fn1,但 fn1 的作用域在定义时就确定了
}

fn2()                // 输出 'global',不是 'fn2'!

图解:

复制代码
fn1 定义时:
┌─────────────────────────────────────────┐
│  fn1 的作用域链:                        │
│  1. 自己的作用域(没有 a)                │
│  2. 全局作用域(a = 'global')           │
└─────────────────────────────────────────┘

不管在哪里调用 fn1,都按这个链查找!

3. 内层共享外层变量

javascript 复制代码
function createCounter() {
  let count = 0
  
  return {
    increment() { count++ },
    decrement() { count-- },
    getCount() { return count }
  }
}

const counter = createCounter()
counter.increment()
counter.increment()
console.log(counter.getCount())  // 2
// 多个方法共享同一个 count 变量

六、闭包和作用域链的关系(面试必问)

概念 定义 关系
作用域链 变量查找的机制 底层原理
闭包 函数+外部变量的现象 作用域链的具体表现

一句话总结:

作用域链是机制,闭包是现象。闭包能实现,就是因为内部函数被返回后,依然保留着原来的作用域链。


七、面试高频问题

Q1:什么是闭包?有什么作用?

答:

闭包是函数嵌套函数,内部函数引用外部函数的变量形成的特性。即使外部函数执行完毕,这些变量也不会被回收。

作用:

  1. 私有化变量,避免全局污染

  2. 让变量长期驻留内存(缓存、累加器)

  3. 实现防抖、节流

  4. 模块化封装

Q2:闭包会导致内存泄漏吗?

答:

会。因为闭包引用的变量不会被垃圾回收,如果不及时释放,会导致内存泄漏。解决方法是:不用时将变量赋值为 null

Q3:什么是作用域链?

答:

当访问变量时,JS 会从当前作用域开始,逐级向外层作用域查找,直到全局作用域。这条查找路径就是作用域链。它是词法作用域的体现,在函数定义时就已确定。

Q4:闭包和作用域链的关系?

答:

作用域链是底层机制,闭包是这个机制的具体表现。闭包之所以能访问外部函数的变量,就是因为内部函数保留了原来的作用域链。

相关推荐
爱喝水的鱼丶29 分钟前
SAP-ABAP:SAP 与 ABAP 关联逻辑与入门路径:业务×开发的协作指南
服务器·前端·数据库·学习·sap·abap
小陈的进阶之路35 分钟前
Python系列课(2)——判断
java·前端·python
2301_8156453842 分钟前
html.
前端·html
qq_381338501 小时前
CSS @layer 级联层实战指南:从样式冲突到分层架构
前端·css
广州华水科技1 小时前
深度测评2026年好用的单北斗GNSS变形监测系统推荐,提升GNSS位移监测精度,引领智能监控新风尚
前端
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_33:(Attr 接口与属性节点的深入理解)
前端·javascript·ui·html·音视频·html5
神所夸赞的夏天2 小时前
如何获取多层json数据,存成dictionary,并取最大最小值
java·前端·json
红色的小鳄鱼2 小时前
前端面试js手写
开发语言·前端·javascript
焦糖玛奇朵婷2 小时前
健身房预约小程序开发、设计
java·大数据·服务器·前端·小程序