js中的闭包

一、什么是闭包?

特点:
1 函数嵌套函数
2 内部函数可以引用外部的参数和变量
3 参数和变量不会被垃圾回收机制所回收

举一个简单的闭包的例子,例子中符合闭包的三个特点

function aaa(a){
    var b = 5;
    function bbb(){
        alert(a);
        alert(b);
    }
}
aaa();  

关于js 中的垃圾回收机制,这里说明一下。请看下面代码,当aaa函数执行完毕之后,aaa中的变量a会被js的垃圾回收机制所回收

function aaa(){
    var a = 1;
}
aaa(); // 改函数执行完毕时候,函数中的变量a会被js的垃圾回收机制所回收  

再举一个闭包例子

当aaa函数执行完毕之后,调用c函数仍然可以弹出1,说明了a这个变量没有被垃圾回收

function aaa(){
    var a = 1;
    function bbb(){
        alert(a);
    }
    return bbb;
}
var c = aaa(); // 当aaa函数执行完毕之后,调用c函数仍然可以弹出1,说明了a这个变量没有被垃圾回收
c();  

二、闭包有什么好处?应用在哪?

2.1 好处:

1 可以使一个变量常驻内存
2 避免全局变量的污染
3 私有成员变量的存在

举个例子,例子中,使用全局变量a实现每次调用aaa函数,变量a++,过多使用全局变量不容易维护(全局变量的污染)。

var a = 1;
function aaa(){
    a++;
    alert(a);
}
aaa(); //2
aaa(); //3
alert(a); // 这里的a是全局变量  

下面,使用闭包实现这个例子:
由于a是局部变量,防止了全局变量污染

function aaa(){
    var a = 1;
    return function bbb(){
        a++;
        alert(a);
    }
}
var b = aaa();
b(); //2
b(); //3
alert(a); // 由于a是局部变量,外面无法引用,程序报错  

使用函数表达式的形式,改造上面例子
关于函数表达式的知识点,可以查看JS函数声明与函数表达式的区别 http://www.cnblogs.com/linhp/p/6085567.html

var aaa = (function (){
    var a = 1;
    return function bbb(){
        a++;
        alert(a);
    }
})();// 这里已经执行了
aaa(); //2
aaa(); //3  

私有成员变量的例子:
将表达式中的函数bbb与ccc通过json的方式返回,外部只能调用返回的json中的函数bbb和ccc,而无法访问a这个成员变量。

var aaa = (function (){
    var a = 1;
    function bbb(){
        a++;
        alert(a);
    }
    function ccc(){
        a++;
        alert(a);
    }
    return {b:bbb,c:ccc}
})();
aaa.b();
aaa.c();  

2.2 用法:

1 模块化代码
2 在循环中,直接找到对应元素的索引

模块化代码就不举例了,上面的代码都能体现
下面举例(在循环中,直接找到对应元素的索引)
先看下面例子:

<script>
window.onload = function (){
    var oLi = document.getElementsByTagName("li");
    for (var i = 0; i < oLi.length; i++) {
        oLi[i].onclick = function (){
            alert(i);
        }
    }
}
</script>
<ul>
    <li>111111111111</li>
    <li>222222222222</li>
    <li>333333333333</li>
</ul>  

上面例子中,想通过给每一个li添加点击事件,点击每个li,弹出li的索引,而上面例子中,由于for循环已经结束,而onclick事件还没触发,此时i已经等于3了,所以你无论点击哪一个li都无法获取索引,弹出的值都是3

下面,通过闭包的形式,改造,获取索引

window.onload = function (){
    var oLi = document.getElementsByTagName("li");
    for (var i = 0; i < oLi.length; i++) {
        (function(i){
            oLi[i].onclick = function (){
                alert(i);
            }
        })(i); //循环过程中,这里已经执行了。参数i永驻内存,所以当你点击li的时候,可以获取得到0,1,2
    }
}   

也可以改造成下面的样子

window.onload = function (){
    var oLi = document.getElementsByTagName("li");
    for (var i = 0; i < oLi.length; i++) {
        oLi[i].onclick = (function (i){
            return function(){
                    alert(i)
                };
        })(i);
    }
}   

三、闭包需要注意的地方?

3.1 IE下会引发内存泄露

内存泄露会导致浏览器内存占用过高,内存中的变量无法变量,必须关闭浏览器,关闭这个网页也无法清除

window.onload = function(){
    var oDiv = document.getElementById("div1");
    oDiv.onclick = function(){
        alert(oDiv.id); // 由于内部引用了oDiv,导致oDiv无法释放
    }
}  

可以通过onunload事件,将onclick取消,来释放oDiv

window.onload = function(){
    var oDiv = document.getElementById("div1");
    oDiv.onclick = function(){
        alert(oDiv.id); // 由于内部引用了oDiv,导致oDiv无法释放
    }

    window.onunload = function(){
        oDiv.onclick = null;
    }
}  

或者:

window.onload = function(){
    var oDiv = document.getElementById("div1");
    var id = oDiv.id;
    oDiv.onclick = function(){
        alert(id); // 由于内部引用了oDiv,导致oDiv无法释放
    }
    oDiv = null;
}  
<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、付费专栏及课程。

余额充值