解密JavaScript闭包
译者按: 从最简单的计数器开始,按照需求对代码一步步优化,我们可以领会闭包的神奇之处。
译者: Fundebug
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
对于JavaScript新手来说,闭包(Closures)是一个很神奇的东西。这篇博客将通过一个非常浅显的代码示例来解释闭包。
计数器
我们的目标是实现一个计数器,它的效果如下:
|
increment(); // Number of events: 1
increment(); // Number of events: 2
increment(); // Number of events: 3
|
可知,每次执行increment()都会输出“Number of events: N”,且N每次都会加1。
这个计数器最直观的实现方式如下:
|
var counter = 0;
function increment()
{
counter = counter + 1;
console.log("Number of events: " + counter);
}
|
多个计数器
以上的代码非常简单。但是,当我们需要第二个计数器时,就会遇到问题了。当然,我们可以实现两个重复的计数器:
|
var counter1 = 0;
function incrementCounter1()
{
counter1 = counter1 + 1;
console.log("Number of events: " + counter1);
}
var counter2 = 0;
function incrementCounter2()
{
counter2 = counter2 + 1;
console.log("Number of events: " + counter2);
}
incrementCounter1(); // Number of events: 1
incrementCounter2(); // Number of events: 1
incrementCounter1(); // Number of events: 2
|
显然,以上的代码非常冗余,有待优化。当我们需要更多计数器时,使用这种方法将不太现实。这时,就需要神奇的闭包了。
使用闭包实现计数器
需要多个计数器,同时希望去除冗余代码的话,就可以使用闭包了:
|
function createCounter()
{
var counter = 0;
function increment()
{
counter = counter + 1;
console.log("Number of events: " + counter);
}
return increment;
}
var counter1 = createCounter();
var counter2 = createCounter();
counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3
|
在代码中,我们创建了两个独立的计数器counter1与counter2,分别进行计数,互不干挠。代码看着有点奇怪,我们不妨拆分起来分析。
首先,我们来看看createCounter:
- 创建了一个局部变量counter
- 创建了一个局部函数increment(),它可以对counter变量进行加1操作。
- 将局部函数increment()返回。注意,返回的是函数本身,而不是函数调用的结果。
看起来,createCounter()函数与我们最初定义的计数器非常相似。唯一的不同点在于:createCounter()将计数器封装在一个函数内,于是我们将它称作闭包。
难以理解的一点在于,当我们使用createCounter()函数创建计数器时,实际上创建了一个新的函数:
|
// fancyNewCounter是一个新创建的函数
var fancyNewCounter = createCounter();
|
闭包的神奇之处在于。每次使用createCounter()函数创建计数器increment时,都会创建一个对应的counter变量。并且,返回的increment函数会始终记住counter变量。
更重要的是,这个counter变量是相互独立的。比如,当我们创建2个计数器时,每个计数器都会创建一个新的counter变量:
|
// 每个计数器都会从1开始计数
var counter1 = createCounter();
counter1(); // Number of events: 1
counter1(); // Number of events: 2
// 第1个计数器不会影响第2个计数器
var counter2 = createCounter();
counter2(); // Number of events: 1
// 第2个计数器不会影响第1个计数器
counter1(); // Number of events: 3
|
为计数器命名
多个计数器的输出信息都是“Number of events: N”,这样容易混淆。如果可以为每个计数器命名,则更加方便:
|
var catCounter = createCounter("cats");
var dogCounter = createCounter("dogs");
catCounter(); // Number of cats: 1
catCounter(); // Number of cats: 2
dogCounter(); // Number of dogs: 1
|
通过给createCounter传递一个新的counterName参数,可以很容易地做到这一点:
|
function createCounter(counterName)
{
var counter = 0;
function increment()
{
counter = counter + 1;
console.log("Number of " + counterName + ": " + counter);
}
return increment;
}
|
这样,createCounter()函数返回的计数器将同时记住两个局部变量:counterName与counter。
优化计数器调用方式
按照之前的实现方式,我们通过调用createCounter()函数可以返回一个计数器,直接调用返回的计数器就可以加1,这样做并不直观。如果可以如下调用将更好:
|
var dogCounter = createCounter("dogs");
dogCounter.increment(); // Number of dogs: 1
|
实现代码:
|
function createCounter(counterName)
{
var counter = 0;
function increment()
{
counter = counter + 1;
console.log("Number of " + counterName + ": " + counter);
};
return { increment : increment };
}
|
可知,以上的代码返回了一个对象,这个对象包含了一个increment方法。
添加decrement方法
现在,我们可以给计数器添加一个decrement()方法
|
function createCounter(counterName)
{
var counter = 0;
function increment()
{
counter = counter + 1;
console.log("Number of " + counterName + ": " + counter);
};
function decrement()
{
counter = counter - 1;
console.log("Number of " + counterName + ": " + counter);
};
return {
increment : increment,
decrement : decrement
};
}
var dogsCounter = createCounter("dogs");
dogsCounter.increment(); // Number of dogs: 1
dogsCounter.increment(); // Number of dogs: 2
dogsCounter.decrement(); // Number of dogs: 1
|
添加私有方法
前面的代码有两行重复的代码,即console.log语句。因此,我们可以创建一个display()方法用于打印counter的值:
|
function createCounter(counterName)
{
var counter = 0;
function display()
{
console.log("Number of " + counterName + ": " + counter);
}
function increment()
{
counter = counter + 1;
display();
};
function decrement()
{
counter = counter - 1;
display();
};
return {
increment : increment,
decrement : decrement
};
}
var dogsCounter = createCounter("dogs");
dogsCounter.increment(); // Number of dogs: 1
dogsCounter.increment(); // Number of dogs: 2
dogsCounter.decrement(); // Number of dogs: 1
|
看起来,display()函数与increment()函数以及decrement()函数差不多,但是其实它们很不一样。我们并没有将display()函数添加到返回的对象中,这就意味着以下代码会出错:
|
var dogsCounter = createCounter("dogs");
dogsCounter.display(); // ERROR !!!
|
这时,display()相当于一个私有方法,我们只能在createCounter()函数内使用它。
闭包与面向对象编程
如果你接触过面向对象编程(OOP),则应该不难发现本文中所涉及的内容与OOP中的类、对象、对象属性、共有方法与私有方法等概念非常相似。
闭包,与OOP相似,就是把数据和操作数据的方法绑定起来。因此,在需要OOP的时候,就可以使用闭包来实现。
总结
闭包(Closure)是JavaScript一个非常棒的特性。掌握它,我们可以从容应对一些常见的编程需求。
版权声明:
转载时请注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/07/31/javascript-closure/
解密JavaScript闭包的更多相关文章
- 我从来不理解JavaScript闭包,直到有人这样向我解释它...
摘要: 理解JS闭包. 原文:我从来不理解JavaScript闭包,直到有人这样向我解释它... 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 正如标题所述,JavaScript闭包 ...
- 《Web 前端面试指南》1、JavaScript 闭包深入浅出
闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...
- JavaScript 闭包深入浅出
闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...
- JavaScript闭包(Closure)
JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...
- Javascript闭包和C#匿名函数对比分析
C#中引入匿名函数,多少都是受到Javascript的闭包语法和面向函数编程语言的影响.人们发现,在表达式中直接编写函数代码是一种普遍存在的需求,这种语法将比那种必须在某个特定地方定义函数的方式灵活和 ...
- javascript闭包理解
//闭包理解一 function superFun(){ var _super_a='a'; function subfuc(){ console.log(_super_a); } return su ...
- Javascript闭包深入解析及实现方法
1.什么是闭包 闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.闭包的特点:1. 作为一个函数变量的一个引用,当函数返回时 ...
- javascript闭包和作用域链
最近在学习前端知识,看到javascript闭包这里总是云里雾里.于是翻阅了好多资料记录下来本人对闭包的理解. 首先,什么是闭包?看了各位大牛的定义和描述各式各样,我个人认为最容易一种说法: 外部函数 ...
- JavaScript闭包深入解析
for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); } --上面这段 ...
随机推荐
- 剑指offer面试题26:复杂链表的复制
题目:请实现一个函数,复制一个复杂链表. 在复杂链表中,每个结点除了有一个next指针指向下一个结点外,还有一个sibling指针指向链表中的任意结点或者nulL 直观解法: 1.遍历链表,复制链表节 ...
- C++ 知识回顾总结 -- queue 队列容器
一.说明 queue 是一种队列适配器,专门设计用于FIFO中操作(先进先出),元素从一端插入容器并从另一端提取. 相关API地址为:http://www.cplusplus.com/referenc ...
- Android NDK学习(五):Java调用Native代码流程总结
编写一个Java类,并且在某个方法签名的修饰符中加上native修饰符. 使用Javac命令编译第一步中的Java类,使之成为一个class文件. 使用Javah -jni 包名.类名 生成Jni接口 ...
- Javascript高级编程学习笔记(43)—— 动态脚本
动态脚本 大多数情况下,DOM操作都很简洁明了 因为DOM主要就是用来操作页面中的可视节点的 但有些时候我们又希望可以动态的来进行DOM操作 其中的一部分也就是今天我们的内容动态脚本 动态脚本是什么意 ...
- Java核心技术卷一基础知识-第12章-泛型程序设计-读书笔记
第12章 泛型程序设计 本章内容: * 为什么要使用泛型程序设计 * 定义简单泛型类 * 泛型方法 * 类型变量的限定 * 泛型代码和虚拟机 * 约束与局限性 * 泛型类型的继承规则 * 通配符类型 ...
- Android开发技术周报183学习记录
Android开发技术周报183学习记录 教程 Android性能优化来龙去脉总结 记录 一.性能问题常见 内存泄漏.频繁GC.耗电问题.OOM问题. 二.导致性能问题的原因 1.人为在ui线程中做了 ...
- 吴恩达机器学习笔记31-梯度检验(Gradient Checking)
当我们对一个较为复杂的模型(例如神经网络)使用梯度下降算法时,可能会存在一些不容易察觉的错误,意味着,虽然代价看上去在不断减小,但最终的结果可能并不是最优解.为了避免这样的问题,我们采取一种叫做梯度的 ...
- 使用Java面向对象单词必备
第一章 class 班级,用声明类 object 目标,整个程序集对大 static 静态的 final 不可更改的,用声明常量 private 私有的,用访问权限 public ...
- 常见排序算法整理(python实现 持续更新)
1 快速排序 快速排序是对冒泡排序的一种改进. 它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行 ...
- 动车上的书摘-java对象流与序列化
摘要: 摘要: 原创出处: http://www.cnblogs.com/Alandre/ 泥沙砖瓦浆木匠 希望转载,保留摘要,谢谢! 钢笔不限贵便宜,书法是来自心对手的交流.-泥沙砖瓦浆木匠 一.对 ...