视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
什么是Javascript的闭包
2020-11-27 20:11:08 责编:小采
文档


什么是闭包?

闭包是什么?闭包是Closure,这是静态语言所不具有的一个新特性。但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是:闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。闭包就是就是函数的“堆栈”在函数返回后并不释放,我们也可以理解为这些函数堆栈并不在栈上分配而是在堆上分配,当在一个函数内定义另外一个函数就会产生闭包。

闭包 = 函数内部创建的函数(或者简称内部函数) + 该函数创建时所处环境信息

所以闭包并不等于匿名函数,虽然也有人称这些在函数内部创建的函数为闭包函数,但是我觉得其实并不准确。

我们看一下下面这段代码:

function init() {
 var name = "Zilongshanren"; // name 是在 init 函数里面创建的变量
 // displayName() 是一个内部函数,即一个闭包。注意,它不是匿名的。
 function displayName() {
 console.log(name);
 }
 //当 displayName 函数返回后,这个函数还能访问 init 函数里面定义的变量。
 return displayName;
}
var closure = init();
closure();
Zilongshanren
undefined

displayName 是一个在 init 函数内部创建的函数,它携带了 init 函数内部作用域的所有信息,比如这里的 name 变量。当 displayName 函数返回的时候,它本身携带了当时创建时的环境信息,即 init 函数里面的 name 变量。

闭包有什么作用?

在理解什么是闭包之后,接下来你可能会问:这东西这么难理解,它到底有什么用啊?

因为在 Js 里面是没有办法创建私有方法的,它不像 java 或者 C++有什么 private 关键字可以定义私有的属性和方法。 Js 里面只有函数可以创建出属于自身的作用域的对象,Js 并没有块作用域!这个我后面会再写一篇文章详细介绍。

编程老鸟都知道,程序写得好,封装和抽象要运用得好!不能定义私有的属性和方法,意味着封装和抽象根本没法用。。

不能定义私有的东西,所有变量和函数都 public 显然有问题, Global is Evil!

闭包是我们的救星!

我们看一下下面这段代码:

var makeCounter = function() {
 var privateCounter = 0;
 function changeBy(val) {
 privateCounter += val;
 }
 return {
 increment: function() {
 changeBy(1);
 },
 decrement: function() {
 changeBy(-1);
 },
 value: function() {
 return privateCounter;
 }
 }
};
var counter1 = makeCounter();
var counter2 = makeCounter();
console.log(counter1.value()); /* Alerts 0 */
counter1.increment();
counter1.increment();
console.log(counter1.value()); /* Alerts 2 */
counter1.decrement();
console.log(counter1.value()); /* Alerts 1 */
console.log(counter2.value()); /* Alerts 0 */
0
2
1
0
undefined

这里面的 privateCounter 变量和 changeBy 都是私有的,对于 makeCounter 函数外部是完全不可见的。这样我们通过 makeCounter 生成的对象就把自己的私有数据和私有方法全部隐藏起来了。

这里有没有让你想到点什么?

哈哈,这不就是 OO 么?封装数据和操作数据的方法,然后通过公共的接口调用来完成数据处理。

当然,你也许会说,我用原型继承也可以实现 OO 呀。没错,现在大部分人也正是这么干的,包括我们自己。不过继承这个东西,在理解起来总是非常困难的,因为要理解一段代码,你必须要理解它的所有继承链。如果一旦代码出 bug 了,这将是非常难调试的。

扯远了,接下来,让我们看看如何正确地使用闭包。

如何正确地使用闭包?

闭包会占用内存,也会影响 js 引擎的执行效率,所以,如果一段代码被频繁执行,那么要谨慎考虑在这段代码里面使用闭包。

让我们来看一个创建对象的函数:

function MyObject(name, message)
 { this.name = name.toString(); 
 this.message = message.toString(); 
 this.getName = function()
 { return this.name; }; 
 this.getMessage = function() 
 { return this.message; };}
 var myobj = new MyObject();
var myobj = new MyObject();

每一次被调用生成一个新对象的时候,都会生成两个闭包。如果你的程序里面有成千上万个这样的 MyObject 对象,那么会额外多出很多内存占用。

正确的做法应该是使用原型链:

function MyObject(name, message) {
 this.name = name.toString();
 this.message = message.toString();
}
MyObject.prototype.getName = function() {
 return this.name;
};
MyObject.prototype.getMessage = function() {
 return this.message;
};
var myobj = new MyObject();

现在 MyObject 原型上面定义了两个方法,当我们通过 new 去创建对象的时候,这两个方法只会在原型上面存有一份。

闭包的性能如何?

闭包也是一个函数,但是它存储了额外的环境信息,所以理论上它比纯函数占用更多的内存,而且 Js 引擎在解释执行闭包的时候消耗也更大。不过它们之间的性能差别在 3%和 5%之间(这是 Google 上得到的数据,可能不是太准确)。

但是,闭包的好处肯定是大大的。多使用闭包和无状态编程,让 Bug 从此远离我们。

理解了闭包,你就能理解大部分 FP 范式的 Js 类库及其隐藏在背后的设计思想。当然仅有闭包还不够,你还需要被 FP 和无状态,lambda calculus 等概念。

关于js闭包希望大家学完本篇内容之后就有所掌握。

下载本文
显示全文
专题