JS中闭包的介绍

闭包的概念

闭包就是能够读取其他函数内部变量的函数。

一、变量的作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域。

变量的作用域无非就是两种:全局变量和局部变量。

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。


Js代码  

var n=999;

function f1(){
  alert(n);
}

f1(); // 999

另一方面,在函数外部自然无法读取函数内的局部变量。

Js代码  

    function f1(){
    var n=999;
  }

  alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

Js代码

  function f1(){
    n=999;
  }

  f1();

  alert(n); // 999

二、如何从外部读取局部变量?

出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。

那就是在函数的内部,再定义一个函数。

Js代码

  function f1(){

    n=999;

    function f2(){
      alert(n); // 999
    }

  }

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1 就是不可见的。这就是Javascript语言特有的“链式作用域”结构(chain scope),

子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!


Js代码

  function f1(){

    n=999;

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

四、闭包的用途

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

怎么来理解这句话呢?请看下面的代码。


Js代码

  function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个

匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

五、使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便

改变父函数内部变量的值。

六、思考题

如果你能理解下面代码的运行结果,应该就算理解闭包的运行机制了。

Js代码 

  var name = "The Window";   
  var object = {   
    name : "My Object",   
    getNameFunc : function(){   
      return function(){   
        return this.name;   
     };   
    }   
};   
alert(object.getNameFunc()());  //The Window

JavaScript闭包例子

function outerFun()
 {
  var a=0;
  function innerFun()
  {
   a++;
   alert(a);
  }    
 }
innerFun()

上面的代码是错误的.innerFun()的作用域在outerFun()内部,所在outerFun()外部调用它是错误的.

改成如下,也就是闭包:

Js代码

function outerFun()
{
 var a=0;
 function innerFun()
 {
  a++;
  alert(a);
 }
 return innerFun;  //注意这里
}
var obj=outerFun();
obj();  //结果为1
obj();  //结果为2
var obj2=outerFun();
obj2();  //结果为1
obj2();  //结果为2

什么是闭包:

当内部函数 在定义它的作用域 的外部 被引用时,就创建了该内部函数的闭包 ,如果内部函数引用了位于外部函数的变量,当外部函数调用完毕后,这些变量在内存不会被 释放,因为闭包需要它们.

再来看一个例子

Js代码

function outerFun()
{
 var a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);

结果是 0,4 .  因为在函数内部使用了var关键字 维护a的作用域在outFun()内部.

再看下面的代码:

Js代码 
 

function outerFun()
{
 //没有var 
 a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);


结果为 0,0 真是奇怪,为什么呢?

作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0时,因为没有使用var关键字,因此赋值操作会沿着作用域链到var a=4;  并改变其值。

<think>首先,用户的问题是关于JavaScript闭包的常见示例和用法。我需要根据系统指令来构建回答。 系统指令关键点: - 所有行内数学表达式用 `$...$` 格式,但这里没有数学内容,所以可能不需要。 - 独立公式用 `$$...$$`,但用户问题不涉及数学,所以跳过。 - LaTeX语法正确:同样,不适用。 - 使用中文回答:整个回答要用中文。 - 生成相关问题:在回答结束后,添加相关问题部分。 - 回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。引用来自提供的参考。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 用户提供的引用: - [^1]: 关于闭包的实际应用:封装私有变量、延迟执行、保存状态。 - [^2]: 闭包的核心特点:记住词法作用域。 - [^3]: 常见闭包示例:函数作为返回值、作为实参传递、高阶函数与柯里化。 - [^4]: 闭包的影响和实例:定时器问题。 - [^5]: 另一个示例:事件处理中的闭包。 用户问题: "我想了解JavaScript中闭包的常见示例 请问JavaScript 闭包常用示例及用法" 我需要基于这些引用构建回答。参考是站内引用,我应该自然地融入回答中。 回答结构: 1. **介绍闭包**:简要解释闭包是什么,基于引用[^2]。 2. **常见示例**:详细描述常见示例,从引用[^3]和[^5]。 - 函数作为返回值(计数器示例)。 - 函数作为实参传递(setTimeout示例)。 - 事件处理(按钮点击示例)。 - 定时器问题(引用[^4]的for循环问题)。 3. **用法**:讨论闭包的用途,基于引用[^1],如封装私有变量、延迟执行、保存状态。 4. **注意事项**:提到闭包可能的影响,如性能问题(引用[^4])。 5. **结尾**:总结并添加相关问题。 确保回答清晰、逐步解释。使用中文。 在引用段落末尾添加引用标识,例如:在描述一个概念后加 `[^1]`。 生成相关问题: - 基于主题,如闭包的优缺点、如何避免内存泄漏、实际应用场景等。 大纲回答: - 开头:定义闭包。 - 主体:常见示例和用法。 - 示例1:计数器(函数返回)。 - 示例2:setTimeout。 - 示例3:事件处理。 - 示例4:定时器问题。 - 用法:封装、延迟、状态保存。 - 注意事项:性能影响。 - 结尾:总结。 - 相关问题:3-5个问题。 现在,整合引用: - 当提到封装私有变量时,引用[^1]。 - 当描述核心特点时,引用[^2]。 - 当给出示例时,引用[^3]、[^4]、[^5]。 最终输出格式: - 回答部分。 - 相关问题部分以 "§§相关问题§§" 开头。</think>### JavaScript 闭包常用示例及用法详解 在 JavaScript 中,闭包(Closure)是一个核心概念,它允许函数访问并记住其定义时的词法作用域(Lexical Scope),即使函数在其作用域外部执行。闭包的核心特点包括:函数能“捕获”外部变量,并保持对这些变量的引用,从而在函数执行时访问或修改它们[^2]。闭包在 JavaScript 中有广泛的实际应用,例如封装私有变量、实现延迟执行或保存状态。下面我将逐步解释闭包的常见示例和用法,帮助您深入理解。 #### 一、闭包的常见示例 闭包的常见应用场景可以通过具体代码示例来展示。这些示例基于实际开发中的高频用例,确保您能直观掌握闭包的运作机制。 1. **函数作为返回值(计数器示例)** 这是闭包最经典的用法之一,常用于创建私有变量或状态管理。例如,定义一个函数返回另一个函数,内部函数访问并修改外部函数的变量: ```javascript function createCounter() { let count = 0; // 私有变量,通过闭包访问 return function() { count++; console.log(count); }; } const counter = createCounter(); counter(); // 输出: 1 counter(); // 输出: 2 ``` 在这个示例中,`createCounter` 函数返回的匿名函数形成了一个闭包,它“记住”了 `count` 变量,每次调用都会递增并输出其值。这实现了数据的封装和状态保存[^3]。 2. **函数作为实参传递(延迟执行示例)** 闭包常用于异步操作,如 `setTimeout` 或事件处理,以保存函数执行时的上下文。例如,传递一个闭包作为回调函数: ```javascript function showMessage(msg, delay) { setTimeout(function() { console.log(msg); // 闭包捕获了 msg 参数 }, delay); } showMessage('Hello, World!', 2000); // 2秒后输出 "Hello, World!" ``` 这里,匿名函数作为 `setTimeout` 的回调,通过闭包访问了 `msg` 参数,即使 `showMessage` 函数已执行完毕。这展示了闭包如何实现延迟执行和上下文保存[^3]。 3. **事件处理(按钮点击示例)** 在 DOM 操作中,闭包常用于事件监听器,以绑定特定数据到事件回调。例如,为多个按钮添加点击事件: ```javascript function setupButtons() { for (let i = 0; i < 3; i++) { document.getElementById(`btn${i}`).addEventListener('click', function() { console.log(`Button ${i} clicked`); // 闭包捕获了循环变量 i }); } } setupButtons(); // 每个按钮点击时输出对应索引 ``` 使用 `let` 声明循环变量 `i`,确保每个闭包捕获独立的 `i` 值。如果使用 `var`,会导致所有按钮输出相同值(如 `Button 3 clicked`),这是因为 `var` 的作用域问题。闭包在这里帮助保存了事件触发时的状态[^5]。 4. **定时器中的常见问题(循环闭包示例)** 闭包在循环中可能引发意外行为,需特别注意作用域管理。例如,一个经典的定时器问题: ```javascript for (var i = 1; i <= 3; i++) { setTimeout(function() { console.log(i); // 输出: 4, 4, 4(而非预期的 1,2,3) }, 1000); } ``` 问题原因:`var i` 是全局作用域,所有闭包共享同一个 `i`(循环结束后 `i=4`)。解决方案是使用 `let` 或创建新作用域: ```javascript for (let i = 1; i <= 3; i++) { // 使用 let 创建块级作用域 setTimeout(function() { console.log(i); // 输出: 1, 2, 3 }, 1000); } ``` 这突显了闭包在异步代码中的陷阱,需合理控制变量作用域[^4]。 #### 二、闭包的常见用法 闭包不仅用于示例场景,还广泛应用于以下功能,提升代码模块化和可维护性[^1]: - **封装私有变量和方法**:闭包可以创建模块化组件,避免全局污染。例如,实现一个简单的“私有”计数器: ```javascript const counterModule = (function() { let privateCount = 0; return { increment: function() { privateCount++; }, getCount: function() { return privateCount; } }; })(); counterModule.increment(); console.log(counterModule.getCount()); // 输出: 1 ``` 这里,IIFE(立即执行函数)返回的对象方法通过闭包访问 `privateCount`,外部无法直接修改该变量[^1]。 - **延迟函数执行**:闭包保存参数和上下文,用于定时器或事件队列。例如,在 AJAX 回调中保持数据状态。 - **保存状态**:闭包使函数能“记住”先前操作,如实现状态机或缓存机制。例如,记忆化(Memoization)优化递归计算: ```javascript function memoize(fn) { const cache = {}; return function(...args) { const key = JSON.stringify(args); if (cache[key]) return cache[key]; const result = fn.apply(this, args); cache[key] = result; return result; }; } const factorial = memoize(n => n <= 1 ? 1 : n * factorial(n-1)); console.log(factorial(5)); // 计算并缓存结果 ``` #### 三、注意事项 尽管闭包功能强大,但需注意潜在问题: - **性能影响**:频繁创建闭包可能增加内存开销,因为每个闭包都捕获整个作用域链。在性能敏感场景(如高频事件),应避免不必要的闭包创建[^4]。 - **内存泄漏风险**:如果闭包引用 DOM 元素或其他大对象,可能导致垃圾回收器无法释放内存。解决方法是手动解除引用(如移除事件监听器)。 - **作用域管理**:确保正确使用 `let` 或 `const` 避免变量共享问题,如前述循环示例。 #### 总结 闭包是 JavaScript 的核心特性,通过示例(如计数器、事件处理和定时器)和用法(封装、延迟执行、状态保存),它实现了强大的模块化和异步处理能力。理解闭包的工作原理(词法作用域捕获)和注意事项(性能优化),能帮助您编写更健壮的代码。在实际开发中,闭包常用于框架(如 React Hooks)和库设计,提升代码复用性[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值