引言
看如下例子,注意内部函数form
是如何引用定义定义在外部函数formFamily
内的requiredWho变量的?我们需要知道JavaScript允许你引用当前函数以外定义的变量。
function formFamily (){
var requiredWho = "Father and Mother ";
function form (who){
return requiredWho + "need " + who ;
}
return form ("YingBao and MaoTan");
}
formFamily(); // "Father and Mother need YingBao and MaoTan"
例子演进,这个例子和上面几乎完全相同,唯一区别是,不是在外部函数formFamily
中立即调用f("YingBao and MaoTan"),
而是返回form
函数本身。因此f的值为内部的函数form
,调用f实际是调用form函数。但即使formFamily
函数已经返回,form
仍能记住requiredWho的值。
这里我们需要知道,即使外部函数返回,当前函数仍然可以引用在外部函数所定义的变量。这就意味着,你可以返回一个内部函数,并在稍后调用它。
function formFamily (){
var requiredWho = "Father and Mother ";
function form (who){
return requiredWho + "need " + who ;
}
return form;
}
var f = formFamily();
f("YingBao"); // "Father and Mother need YingBao"
f("MaoTan"); // "Father and Mother need MaoTan"
f("YingBao and MaoTan"); // "Father and Mother need YingBao and MaoTan"
这是如何工作的?
在外部函数 formFamily
中定义内部函数
form
,而 form
又引用了 formFamily
作用域内的俩个变量requiredWho
和who
。每当form
函数被调用时,其代码都能引用到这俩个变量,因为该闭包存储了这个俩个变量。form
函数就是一个闭包。
在外部函数 formFamily 之外使用内部 form 函数,并且引用外部函数的变量,则形成了闭包。
定义
JavaScript的函数值包含了比调用它们时执行所需要的代码还要多的信息。而且JavaScript函数值会在内部存储它们可能会引用的定义在其封闭作用域的变量。那些在其所涵盖的作用域内跟踪变量的函数被称为闭包。
tips1:函数可以引用定义在其外部作用域的变量。tips2:闭包比创建它们的函数有更长的生命周期。
优化
函数可以引用其作用域内的任何变量,包括参数和外部函数变量。利用这点我们编写更加通用的formFamily
函数。
function formFamily (requiredWho){
function form (who){
return requiredWho + "need " + who ;
}
return form;
}
var f = formFamily("Father ");
f("YingBao and MaoTan"); // "Father need YingBao and MaoTan"
var m = formFamily("Mother ");
m("YingBao and MaoTan"); // "Mother need YingBao and MaoTan"
该例子创建了f
和m
俩个完全不同的函数。尽管它们都是由相同的form
函数定义的,但是它们时俩个截然不同的对象。
闭包是JavaScript最优雅、最有表现力的特性之一。JavaScript甚至还提供了一种更为方便的构建闭包字面量语法——函数表达式。
function formFamily (requiredWho){
return function(who){
return requiredWho + "need " + who ;
}
}
请注意,该函数表达式是匿名的。我们只需其能产生一个新的函数值,没打算在局部调用它,因此根本没必要给该函数命名。
扩展
闭包可以更新外部变量的值。实际上,闭包存储的是外部变量的引用,而不是它们的副本。因此,对于任何具有访问这些外部变量的闭包,都可以进行更新。如下例子。
function box(){
var val = undefined;
return {
set: function(newVal) { val = newVal; },
get: function() { return val; },
type: function() { return typeof val; }
};
}
var b = box();
b.type(); // "undefined"
b.set(13.2);
b.get(); // 13.2.6
b.type(); // "number"
box
函数存储了一个可读写的内部值。该例子产生了一个包含三个闭包的对象,这三个闭包是set
、get
和type
属性。它们都共享访问val
变量。set
闭包更新val
的值,随后调用get
和type
查看更新的结果。
知识补充
function outter(){
var n = 0;
return function (){
return n++;
}
}
var o1 = outter();
o1();//n == 0
o1();//n == 1
o1();//n == 2
var o2 = outter();
o2();//n == 0
o2();//n == 1
外部函数outter
执行后返回的是一个function
,赋值给了 o1
,o1
可以访问 n
。return
回来的function
可以访问里面的变量,就会导致 n
不能被回收,因为有o1
有用到它。
匿名函数 function(){return n++;} 中
包含对外部函数 outter
的局部变量 n
的引用,因此当外部函数 outter
返回时,n
的值被保留 ( 不会被垃圾回收机制回收 ),持续调用 o1()
,将会改变 n
的值。而 o2
的值并不会随着 o1()
被调用而改变,第一次调用 o2
会得到 n==0
的结果,用面向对象的术语来说,就是 o1
和 o2
为不同的实例,互不干涉。
需要注意闭包的一个缺陷:闭包会导致内存泄漏。
参考资料
- 书籍:《Effective JavaScript 编写高质量JavaScript代码的68个优效方法》
- 博客:《JavaScript 中的函数式编程实践》