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

目录

一、闭包(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:闭包和作用域链的关系?

答:

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

相关推荐
IT_陈寒3 小时前
Redis的内存溢出坑把我整懵了,分享这个血泪教训
前端·人工智能·后端
m0_738120723 小时前
渗透测试基础ctfshow——Web应用安全与防护(五)
前端·网络·数据库·windows·python·sql·安全
Z_Wonderful3 小时前
基于 Vite 的 React+Vue 混部完整模板(含目录结构、依赖清单、启动脚本)
前端·vue.js·react.js
Rooting++3 小时前
腾讯无界微前端源码分析
前端
小嘿前端仔3 小时前
用AI读源码这件事:前端视角的实战方法论,附Vue3 reactivity源码解读示范
前端
其实防守也摸鱼3 小时前
XSS漏洞全景解析:从原理、实战利用到纵深防御
前端·网络·安全·xss·xss漏洞
戴维南3 小时前
DeepAgents 快速上手教程
前端
bigfatDone4 小时前
OpenSpec + Superpowers 联合开发工作流
前端
北漂大橙子4 小时前
OpenSpec 完全指南:让 AI 编码可预测的规范框架
前端