闭包的学习

本文主要记录了自己关于闭包的理解,和闭包的应用

闭包的概念

闭包,就是函数在调用的时候,产生的那个内存结构,外界不能访问里面,它就是一个封闭的包裹结构,即闭包。

注意

  • 函数定义后,放置在那里,只是一个普通的对象,不会创建任何内存,也不会影响到其他任何东西;
  • 只有函数运行起来,在函数执行过程中,在 结束之前,函数会构成一个封闭的空间;
  • 根据词法作用域,函数内在访问变量的时候,如果函数内没有可以到函数外面查找,反之不行;
  • 同时函数也包含了一定的范围;
  • 结合一下, 此时在函数结束之前,函数构成了一个封闭的包裹的结构,此为闭包。

如何保留函数内存的访问能力

函数在执行完之后,会释放其占用的内存。
如果可以让函数调用之后,不释放其内存,这个闭包才会有作用。
函数执行结束之后,如果保存其内存的访问能力,该内存就不会被释放。

闭包要使用,有一个经典的代码,一定要有:

1
2
3
4
5
6
7
8
function foo(){
var num = 123;
function func(){
return num;
}
return func;
}
var f = foo();

思考:
闭包到底构成了什么东西(有闭包和没有闭包有什么不同,闭包给我们提供了一个什么样的功能)?

1
2
3
发现,闭包改变了变量的访问规则( 改变了作用域 ).
闭包 给我们提供了一个 私有作用域.
凡是需要 保护 使用 的数据都可以放到私有作用域中.

思考:函数就是闭包?

这种理解并不准确。当函数 **被调用** 的时候,才会产生闭包。
举个例子,有一个铅笔,把它放在头发上来回摩擦,摩擦这个动作会产生静电。
但如果把铅笔放在桌子上,啥也不做,就不会产生静电。
而且摩擦产生静电之后,如果不继续保持摩擦,静电就会消失。因此我们需要保持持续的摩擦。

闭包的几种应用

1.沙箱模式

那么什么是沙箱呢?

1
2
3
4
5
6
7
沙箱 可以用一个我们生活中的例子来描述: 网上下载的软件,但是我们并不知道下载的软件有没有病毒,一般的杀毒软件都会提供一个沙箱运行模式。
其特点是在里面运行的任何程序,在沙箱重启后其影响会全部消失。但是沙箱会模拟当前操作系统的所有访问资源。
在很多手机 app 中也有相应的沙箱模型。
例如一个软件是病毒,安装之后,理论只允许其在自己所在的沙箱里面运行,不允许访问其他 app 所在的文件。
因此沙箱的特点就是:既可以运行,又可以脱离。

我们知道,自己在封装一些东西的时候,容易出现 全局污染.
我们可以把其放到一个沙箱中,留出一个允许别人使用的 接口( 函数, 对象 ) . 我们的代码不会或者最小程度的影响到外界代码。

如何构成沙箱呢,我们可以使用下面的代码构成沙箱

1
2
3
4
5
6
7
8
(function (){
// 代码
// 1,...
// 2,...
// 3,...
})();

沙箱模式 可以保证数据内外隔离,但在实际开发中,我们需要预留一些 api 以供外界调用,有几个常见的返回模型。

1.利用 返回值,返回一个 接口

1
2
3
4
var func= (function () {
// ...
return func;
})();

2.利用 window 的属性返回多个数据

1
2
3
4
(function () {
// ...
window.jQuery = window.$ =jQuery;
})();

3.利用 函数的调用中的函数调用模式

函数在调用的时候,默认 函数中的 this 就是 window. 因此第二种写法有一个变体:

1
2
3
4
( function () {
// ...
this.jQuery = this.$ = jQuery;
})

2. 利用闭包实现私有作用域(带私有作用域的函数)

代码模型:

1
2
3
4
5
6
7
8
9
10
11
12
//一般写函数:
var foo =function () {
//函数体
};
//可以使得函数具有私有内存
var foo = (function () {
//私有内存空间
return function () {
// 函数体
};
})();
//这个带有私有内存的函数,在实际开发中可以不使用,但是如果使用会比较方便

让我们来看一个例子:

需求:

1> 将骆驼命名规则的字符串转换成使用 连字符连接的 字符串, 并且全小写

例如: 'getElementById' => 'get-element-by-id'

2> 将用连字符连接的字符串, 转成骆驼命名规则的字符串, 例如:

'get-element-by-id' => 'getElementById'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//将骆驼命名规则的字符串转换成使用 连字符连接的 字符串, 并且全小写
var toCamlize = ( function () {
var cache = {};//将缓存包裹到函数中
return function toCamlize( str ) {
var ret = cache[ str ];
if ( ret ) {
//判断内存中是否存在这个参数,如果存在,返回这个数的值
return ret;
} else {//如果缓存中不存在这个数
ret = str.replace( /-(.)/g, function (_,g) {
return g.toUpperCase();
});
//replace 的第二个参数可以是一个函数, 利用正则匹配字符,
//每一个匹配会调用一次函数, 并且用函数的返回值来替换匹配到的结果.
//重点是函数的参数, 回调函数的参数是如下方式分配的
//function ( 匹配到的项, 第1组, 第2组, 第3组, ... ) { ... }
cache [ str ] = ret;//将这个数添加到内存
return ret;
}
};
})();
//2> 将用连字符连接的字符串, 转成骆驼命名规则的字符串
var toBarCase = (function () {
var cache = {};
return function toBarCase( str ) {
var ret = cache[ str ];
return ret ||
cache[ str ] = str.replace( /[A-Z]/g, function ( s ) {
return '-' + s.toLowerCase();
} );
};
})();

3.保留运行时的数据

–> 闭包 是函数在执行过程中产生的一个私有的内存空间
这个内存空间如果在函数执行结束后不释放,也可以保留数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 例如:
var func = (function (num) {
// 私有的内存空间
return function () {
// ...
}
})(2);
//抽象一下
function createFuncWidthSavable( value ) {
return function () {
};
}
var fn = createFuncWidthSavable( 123 );

利用这点,我们可以使用闭包实现0-9的打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for ( var i = 0; i < 10; i++ ) {
setTimeout((function ( value ) {
return function () {
console.log( value );
}
})( i ), 200);
}
// for ( let i = 0; i < 10; i++ ) {
// setTimeout(function () {
// console.log( i );
// }, 200);
// }