(一)什么是闭包?
闭包就是有权访问另一个函数作用域中变量的函数.
下面列举一些闭包的例子:
function fn(){
var num=100;
return function(){
num++;
return num;
}
}
var b=fn();//获得函数(闭包)
/**因为执行fn()()后,fn函数已经执行完毕了,没有其他资源引用fn,它会被立即释放,
也就是说,fn()()执行完后,立即就释放了。**/
console.log(fn()());//101 本身的函数调用
console.log(fn()());//101 num每次都会被初始化
//b拥有fn的内部函数的引用,而内部函数又有num的引用,故垃圾回收机制并不会在b()执行后将fn的内存释放,
//所以局部变量num并没有在内存当中消失
console.log(b());//101
console.log(b());//102
//简单闭包
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 闭包
/**
**功能实现逐秒输出1,2,3,4,5
**/
//失败,在外部只有一个作用域只存在一个i,故每次setTimeout都是拿到循环结束的 i=6
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );//6 6 6 6 6
}, i*1000 );
}
//失败,虽然有了自己的作用域,但是并未将i转换内存中的i依然是循环结束的6
for (var i=1; i<=5; i++) {
(function() {
setTimeout( function timer() {
console.log( i );//6 6 6 6 6
}, i*1000 );
})();
}
//成功,在IIFE(立即执行函数表达式)中,成功将i存储进自身的函数作用域j中,实现功能
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );//1 2 3 4 5
}, j*1000 );
})( i );
}
//成功,let会每一次都创建块级作用域,故有5块i的块级作用域,i能正常传递
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );//1 2 3 4 5
}, i*1000 );
}
(二)闭包的特点,优点,缺点?
闭包的优点:1.能够读取函数内部的变量 2.让这些变量存在于内存中,不会在调用结束后,被垃圾回收机制回收
闭包的缺点:正所谓物极必反,由于闭包会使函数中的变量保存在内存中,内存消耗很大,所以不能滥用闭包,解决办法是,退出函数之前,将不使用的局部变量删除。
(三)闭包何时从内存中销除?
- 当对内部变量的引用消失时,闭包就会被垃圾回收机制回收。
//运用之前的例子
function fn(){
var num=100;
return function(){
num++;
return num;
}
}
var b=fn();//获得函数(闭包)
/**因为执行fn()()后,fn函数已经执行完毕了,没有其他资源引用fn,它会被立即释放,
也就是说,fn()()执行完后,立即就释放了。**/
console.log(fn()());//101 本身的函数调用
console.log(fn()());//101 num每次都会被初始化
//b拥有fn的内部函数的引用,而内部函数又有num的引用,故垃圾回收机制并不会在b()执行后将fn的内存释放,
//所以局部变量num并没有在内存当中消失
console.log(b());//101
console.log(b());//102
/**添加以下代码即可**/
// 手动释放fn的引用
b= null;
// fn的引用b被释放了,现在fn的作用域也被释放了。
- 如果是点击事件调用闭包函数,会在何时删除引用?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<html>
<body>
<button id="test" >测试点击事件绑定闭包</button>
</body>
</html>
<script type="text/javascript">
function fn(){
var num=100;
return function(){
num++;
return num;
}
}
var a = fn();
document.getElementById("test").onclick=function(){
var b = fn();
console.log(b());//101 每次进入都是
console.log(b());//102 每次进入都是
if(typeof a === "function"){
var aNum = a();
if(aNum > 108){//超过108,则释放闭包占用内存
a = null;
}else{
console.log(aNum);//按次数累加 101 102 103 ... 108
}
}
};
</script>
由此可见,点击事件会一直持有闭包的引用,除非将引用改变,才能释放闭包的内存。
(四)实际开发中闭包的使用?
- 封装
var person = function(){
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接访问,结果为undefined
print(person.getName());
person.setName("abruzzi");
print(person.getName());
得到结果如下:
undefined
default
abruzzi
-
结果缓存
我们开发中会碰到很多情况,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
var CachedSearchBox = (function(){
var cache = {},
count = [];
return {
attachSearchBox : function(dsid){
if(dsid in cache){//如果结果在缓存中
return cache[dsid];//直接返回缓存中的对象
}
var fsb = new uikit.webctrl.SearchBox(dsid);//新建
cache[dsid] = fsb;//更新缓存
if(count.length > 100){//设定缓存的大小<=100
delete cache[count.shift()];
}
return fsb;
},
clearSearchBox : function(dsid){
if(dsid in cache){
cache[dsid].clearSelection();
}
}
};
})();
CachedSearchBox.attachSearchBox("input");
//模拟容器
function isFirstLoad() {
var _list = []
return function (id) {
if (_list.indexOf(id) >= 0) {
return false
} else {
_list.push(id)
return true
}
}
}
// 使用
var firstLoad = isFirstLoad()
firstLoad(10) // true
firstLoad(10) // false
firstLoad(20) // true
- 类和继承
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var p = new Person();
p.setName("Tom");
alert(p.getName());
var Jack = function(){};
//继承自Person
Jack.prototype = new Person();
//添加私有方法
Jack.prototype.Say = function(){
alert("Hello,my name is Jack");
};
var j = new Jack();
j.setName("Jack");
j.Say();
alert(j.getName());
- 模块模式
模拟模块机制:
//自定义模块
var MyModules = (function Manager() {
var modules = {};
function define(name, deps, impl) {
for (var i=0; i<deps.length; i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply( impl, deps );
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
//使用模块
MyModules.define( "bar", [], function() {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
} );
MyModules.define( "foo", ["bar"], function(bar) {
var hungry = "hippo";
function awesome() {
console.log( bar.hello( hungry ).toUpperCase() );
}
return {
awesome: awesome
};
} );
var bar = MyModules.get( "bar" );
var foo = MyModules.get( "foo" );
console.log(
bar.hello( "hippo" )
); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO
ES6中的模块机制:
//bar.js文件
function hello(who) {
return "Let me introduce: " + who;
}
export hello;
//foo.js文件
// 仅从 "bar" 模块导入 hello()
import hello from "bar";
var hungry = "hippo";
function awesome() {
console.log(
hello( hungry ).toUpperCase()
);
}
export awesome;
//baz.js文件
// 导入完整的 "foo" 和 "bar" 模块
module foo from "foo";
module bar from "bar";
console.log(
bar.hello( "rhino" )
); // Let me introduce: rhino
foo.awesome(); // LET ME INTRODUCE: HIPPO