1. 看见闭包
在定义闭包之前,我们先写一个简单的闭包样例,并通过chrome debug工具尝试看到闭包。
例1
function fn1(){
var a = 1;
function fn2(){
var b = 2;
debugger;
console.log(a, b);
}
fn2();
}
fn1();
在右侧工具栏中的Scope窗口中可以看到当前函数fn2的作用域链。fn2内调用了两个变量 a 和 b,其中 fn2 定义的 b 在Local中,而 fn1 定义的 a 则在Closure(fn1)中。Closure即为闭包的英文。
从上面的例子中可能对闭包有了些认识,下面再尝试通过几个例子,更加深入了解闭包。
例2
function fn1(){
var a = 1;
function fn2(){
var b = 2;
function fn3() {
var c = 3;
debugger;
console.log(a, b, c);
}
fn3();
}
fn2();
}
fn1()
当内部函数fn3同时调用其外部函数fn2中的b 以及最外部函数fn1时,将出现两个闭包。
例3
function fn1(){
var a = 1;
function fn2(){
var b = 2;
function fn3() {
var c = 3;
debugger;
//console.log(a, b, c);
console.log(a, c);
}
fn3();
}
fn2();
}
fn1()
当内部函数fn3不再调用外部函数fn2中的变量时,fn2将在作用域链中消失。同理,如果不再引用fn1内的变量时,fn1也会在作用域链中消失。
2. 定义闭包
2.1 闭包
从chrome的展示中,我们可以尝试对闭包进行下定义——闭包:当内层函数调用外层函数中的变量时,外层函数的作用域(变量对象)在内层函数的作用域链中称为闭包。
2.2 闭包的定义
闭包没有一个标准定义,所以在不同地方对闭包的定义是不同的,上面这个定义仅作参考。
-
在《JavaScript高级程序设计(第3版)》中,对闭包的定义是“闭包是指有权访问另一个函数作用域中的变量的函数。”。即认为闭包是样例中的内部函数。
-
在《深入理解JavaScript》中,对闭包的定义是“函数以及它所连接的周围作用域中的变量即为闭包。”。即认为内部函数以及调用的外部函数的变量共同组成闭包。
-
在《javascript权威指南中文第六版》中,对闭包的定义是“函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为‘闭包’。”。该定义写得相对笼统,但也可能是相对正确的一版。可以理解为——闭包是一种引用关系,引用方是内部函数,被引用方是外部函数的变量。
2.3 闭包三要素
虽然定义各不相同,但基本内涵是一致的,我们可以从中总结出闭包三要素。
- 函数嵌套
- 内部函数引用外部函数的变量
- 内部函数和外部函数都要被调用
2.4 闭包的生命周期
- 产生:外部函数被调用时。注:内部函数并不需要被调用或是引用,函数在定义时,作用域链就已经确定了,闭包已经形成。但由于内部函数没有被调用,闭包刚产生就死亡。
- 死亡:内部函数被浏览器作为垃圾回收时,即没有再调用或是被其他变量引用。
3. 使用闭包
3.1 闭包的作用
-
延长外部函数变量的生命周期。
-
可以隐藏一些属性和方法,仅暴露一个接口供他人使用。在一些情况下,函数功能的实现需要借助一些外部变量,但不想这些变量直接暴露在全局作用域中,就可以借助闭包实现。
3.2 闭包的常用方式
-
作为立即执行函数(IIFE)的返回值
var fn = (function(){ var value = 1; return function(){ console.log(value++); } })(); fn();
正常情况下,IIFE执行完毕,value会被垃圾回收,但由于引用value的内部匿名函数被变量fn引用着,所以value不会被垃圾回收。即延长了value的生命周期。
-
模块化
在开发中,实现不同的功能模块时,极有可能出现变量名或是函数名冲突的情况。
<body> <header><input value="请输入搜索内容"></header> <footer><input value="请留下联系方式"></footer> <script> function headerModule() { var iptNode = document.querySelector('header input'); function show() { console.log('这是头部模块'); } return { iptNode : iptNode, show : show } } function footerModule(){ var iptNode = document.querySelector('footer input'); function show() { console.log('这是脚部模块'); } return { iptNode : iptNode, show : show } } var header = headerModule(); var footer = footerModule(); console.log(header.iptNode.value); console.log(footer.iptNode.value); header.show(); footer.show(); </script> </body>
4. 闭包的缺点
由于闭包机制的存在,使得外部函数的变量无法正常释放,若滥用闭包可能会出现以下情况
- 内存泄漏:外部函数的变量无法正常释放。
- 内存溢出:内存被延长了生命周期外部函数的变量占满。
解决方案:当内部函数不再需要被调用和引用时,将引用内部函数的变量置为null。