JavaScript 函数嵌套与闭包详解:从作用域到实际应用

作者:异步漫步

时间:2026-10-25

浏览:361

本文深入解析 JavaScript 中的函数嵌套与闭包机制。从变量作用域入手,阐述函数返回值、内部函数声明等基础概念,进而揭示闭包如何允许外部访问内部变量。通过 setTimeout 传参、事件回调及函数防抖等实际案例,展示闭包在隐藏变量、维持状态及优化性能方面的核心应用,并对比类实现方案。

文章目录

前言:

本文将探讨对闭包的理解。在深入讨论之前,首先需要明确变量的作用域概念。在 JavaScript 中,变量存在全局作用域与局部作用域的区别。es6 中引入的新变量声明关键字,旨在解决部分变量作用域混乱的问题。此处暂不讨论全局作用域,重点分析函数的作用域特性。

一、作用域

简而言之,函数的作用域指代函数花括号内部的区域。通过以下示例可以更直观地理解这一概念。


function f1(){
  let n = 999
  console.log(n)
}
f1() // 999

function f2(){
  let n = 999
}
alert(n); // 报错

二、函数的返回值

在讨论闭包之前,有必要先理解函数的返回值机制。对于没有显式返回值的函数,执行完毕后返回 undefined;而有返回值的函数,执行后则返回对应的具体值。例如:


// 没有返回值的函数
function f1(){
  alert(666)
}
console.log(f1()) // 出现弹窗之后,在控制台输出undefind

// 存在返回值
function f2(){
  alert(666)
  return 'over'
}
console.log(f2()) // 出现弹窗之后,在控制台输出over。当然,可以返回字符串,也可以返回Bealon,还可以返回函数。

三、函数嵌套

根据《重构——改善既有代码的设计》中的观点,JavaScript 语法支持函数内部嵌套函数。需要注意的是,并非所有编程语言都具备此特性。所谓代码嵌套,即在函数内部声明另一个函数。

具体示例如下:


function outer(){
  let name = 'lilei'
  function inner(){
    console.log(name)
  }
}  

四、闭包

前文已说明 JavaScript 中局部变量的作用域限制。在实际项目中,若需在函数外部访问其内部变量,依据局部作用域规则这通常是不可能的。闭包的引入正是为了解决这一难题。


function outer(){
  let name = 'lilei'
  function inner(){
    return name
  }
  return inner
}

上述代码展示了一个典型的闭包函数。在使用该闭包时,可以按以下方式调用:


let g = outer()
console.log(g()) // lilei


至此,我们实现了在全局环境中访问函数内部局部变量的功能。然而,笔者曾思考:是否可以通过更简单的函数结构满足相同需求?


function outer(){
  let name = 'lilei'
  return name
}

console.log(outer()) // lilei  


虽然上述代码与控制台输出的闭包结果一致,但为何仍需引入闭包?经过深入思考,这类似于从变量到函数再到类的演进过程。每上一层抽象都能实现更复杂的逻辑,例如数据处理仅靠变量无法完成,而函数可以。

五、闭包的实际应用

了解了闭包的基本概念后,接下来探讨其在实际项目中的应用场景。参考以下代码:

1、隐藏内部变量名称和函数执行暂停


function outer() {
    let name = 1
    function inner() {
        return name ++
    }
    return inner
}
let g = outer()
console.log(g()) // 2
console.log(g()) // 3
console.log(g()) // 4
console.log(g()) // 5

2、setTimeout 函数传递参数

默认的 setTimeout 使用方式如下:

一、作用域

笔者曾尝试如下写法:


function f1(p) {
    console.log(p)
}
setTimeout(f1(666),3000) // 并没有延时,直接输出666

若要通过延时机制向函数传递参数,此时闭包的优势便显现出来。


function f1(a) {
    function f2() {
        console.log(a);
    }
    return f2;
}
var fun = f1(1);
setTimeout(fun,1000); // 一秒之后打印出1

3、回调

定义行为并将其关联到用户事件(如点击或按键)。代码通常作为回调函数(事件触发时调用)绑定到事件中,如下所示:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>
    <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  id="size-12">12</a>
    <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  id="size-20">20</a>
    <a href="#" rel="external nofollow"  rel="external nofollow"  rel="external nofollow"  id="size-30">30</a>

    <script type="text/javascript">
        function changeSize(size){
            return function(){
                document.body.style.fontSize = size + 'px';
            };
        }

        var size12 = changeSize(12);
        var size14 = changeSize(20);
        var size16 = changeSize(30);

        document.getElementById('size-12').onclick = size12;
        document.getElementById('size-20').onclick = size14;
        document.getElementById('size-30').onclick = size16;
</script>
</body>
</html>

4、函数防抖

防抖机制指在事件触发 n 秒后执行回调,若在这 n 秒内再次触发,则重新计时。

实现的关键在于 setTimeout 函数。由于需要变量来保存计时器状态,为保持全局命名空间纯净,可借助闭包来实现,示例如下:


/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null    //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeOut(fn,delay) 
        }else{
            timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}

六、使用类实现类似闭包中隐藏内部变量功能

上文介绍了闭包的一个实际应用。笔者曾思考,相同的需求是否可以通过类来实现?经过验证,答案是肯定的,具体实现如下:


class Adder{
    constructor(c){
        this._c = c
    }
    increace(){
        this._c ++ 
    }
    decreace(){
        this._c --
    }
    get finalNum(){
        return this._c
    }
}
let c = new Adder(1)
c.increace()
console.log(c.finalNum) // 2
c.increace()
console.log(c.finalNum) // 3
c.increace()
console.log(c.finalNum) // 4
c.decreace()
console.log(c.finalNum) // 3

参考文章:

https://www.cnblogs.com/gg-qq...

https://www.cnblogs.com/pikac...

https://developer.mozilla.org...

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章