JavaScript 闭包学习笔记
2016-02-20 23:00
751 查看
本文简单记录学习 JavaScript 闭包的知识点。由于闭包和函数是密不可分的,所以先简单总结下 JavaScript 函数的使用方法,然后介绍闭包的定义、闭包的应用和使用闭包常见误区。
函数指针;
构造方法;
正常定义;
自调用;
函数的嵌套;
函数指针
构造方法
正常定义
自调用
函数嵌套
下面的方式是可以的:
只要调用
下面的代码可以达到这个目的吗?
这样做将 counter 由全局变量改为函数内的局部变量,无法在函数外部对其进行访问,但是每次调用
下面这种方式可以吗?
或者
这种方式是可以,我们只要调用
首先,变量 count 的值是一个函数表达式:
闭包就是把函数以及变量包起来,使得变量的生存周期延长。
JavaScript 的世界里没有单身狗,万事万物都是对象(简直比 Java 还具有更彻底的面向对象性质)。变量是对象,函数是对象,对象更是对象。所以,闭包是对象,这个对象由两部分组成:函数和其附带的外部环境。
以上述计数器的实现为例。
函数是表达式
这就解释了为什么局部变量 counter 能脱离其作用域而存在了,其实并没有脱离,而是连同作用域被一起带走了。
来体会一下原味的定义(引自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures):
Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure ‘remembers’ the environment in which it was created.
A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.
效果请点击 jsfiddle。
上述代码的初衷是在每个输入框获得焦点时都出现对应的提示语。但实际运行结果是提示语始终是“Your age (you must be over 16)”(实际效果请点击 jsfiddle)。
究其原因,在执行完
显然,上述问题的原因是三个方法使用了相同的外部环境变量所致,解决的思路是使三者各自有自己的外部环境变量:
这样,每个 showHelp(item) 的外部环境变量都是 makeShowHelp() 函数中的变量 item,这个 item 是三个不同的值。
示例1:
实例2:
0. JavaScript 函数
JavaScript 函数的几种使用方法:函数指针;
构造方法;
正常定义;
自调用;
函数的嵌套;
函数指针
var func = function (a, b) { return a * b; }
构造方法
var func = new Function (a, b) { return a * b; }
正常定义
function func(a, b) { return a * b; } var a = 1; var b = 2; console.log(func(a, b));
自调用
(function (a, b) { return a * b; }).(1, 2);
函数嵌套
function func (a, b) { return function (a, b) { return a * b; } }
1. 变量的作用域
1.1 全局变量 & 局部变量
关于全局变量和局部变量,详见《JavaScript 变量的作用域》。1.2 计数器
那么问题来了:我们如何实现一个计数器?下面的方式是可以的:
var counter = 0; var count = function () { return counter++; }
只要调用
count()便可以得到计数器的值。但是,counter 是全局变量,即该文件的任何地方都可以操作它,这显然是不安全的。我们想做下限制,限制用户只能通过
count()函数得到计数器的值,而不能直接访问 counter。
下面的代码可以达到这个目的吗?
var count = function () { var counter = 0; return counter++; }
这样做将 counter 由全局变量改为函数内的局部变量,无法在函数外部对其进行访问,但是每次调用
count()都会对 counter 赋初值 0,返回值总为 1。显然,这是不行的。
下面这种方式可以吗?
var outer = function () { var counter = 0; return function () { return counter++; }; }; var count = outer();
或者
var count = (function () { var counter = 0; return function () { return counter++; }; })();
这种方式是可以,我们只要调用
count()就可以得到对应的计数器的值。
首先,变量 count 的值是一个函数表达式:
function () { return counter++; }
count()则是上述函数表达式的自调用。
1. 什么是闭包
Java 和 Ruby 里面也有闭包。Ruby 之父松本行弘在《代码的未来》一书中解释的最好:闭包就是把函数以及变量包起来,使得变量的生存周期延长。
JavaScript 的世界里没有单身狗,万事万物都是对象(简直比 Java 还具有更彻底的面向对象性质)。变量是对象,函数是对象,对象更是对象。所以,闭包是对象,这个对象由两部分组成:函数和其附带的外部环境。
以上述计数器的实现为例。
函数是表达式
function () {return counter++;},附带的外部环境是 counter 变量。
这就解释了为什么局部变量 counter 能脱离其作用域而存在了,其实并没有脱离,而是连同作用域被一起带走了。
来体会一下原味的定义(引自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures):
Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure ‘remembers’ the environment in which it was created.
A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.
2. 如何使用闭包
下面总结一下闭包的使用场景。2.1 场景一:动态设置网页属性
如下代码可以动态设置网页字体的大小(查看运行效果请点击 jsfiddle)。body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; } h1 { font-size: 1.5em; } h2 { font-size: 1.2em; } <a href="#" id="size-12">12</a> <a href="#" id="size-14">14</a> <a href="#" id="size-16">16</a> <script> function makeSizer(size) { return function() { document.body.style.fontSize = size + 'px'; }; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16); document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16; </script>
效果请点击 jsfiddle。
2.2 场景二:模拟私有变量
JavaScript 没有提供原生的对私有变量的支持,但是我们可以通过闭包来实现这个效果。在下面的代码中,我们没法直接访问局部变量 privateCounter,只能通过increment()、
decrement()、
value()函数来操作和访问,从而达到了私有变量的效果。
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(); alert(counter1.value()); /* Alerts 0 */ counter1.increment(); counter1.increment(); alert(counter1.value()); /* Alerts 2 */ counter1.decrement(); alert(counter1.value()); /* Alerts 1 */ alert(counter2.value()); /* Alerts 0 */
3. 常见误区
在循环中使用闭包,是一个常见的误区。举例如下:<p id="help">Helpful notes will appear here</p> <p>E-mail: <input type="text" id="email" name="email"></p> <p>Name: <input type="text" id="name" name="name"></p> <p>Age: <input type="text" id="age" name="age"></p> function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp();
上述代码的初衷是在每个输入框获得焦点时都出现对应的提示语。但实际运行结果是提示语始终是“Your age (you must be over 16)”(实际效果请点击 jsfiddle)。
究其原因,在执行完
setupHelp()后,三个方法公用的外部环境中的变量 item 已经是“{‘id’: ‘age’, ‘help’: ‘Your age (you must be over 16)’}”,当输入框获得焦点时触发对应的
showHelp()方法,该方法获得的 item 均为上面的值,导致结果始终相同。
显然,上述问题的原因是三个方法使用了相同的外部环境变量所致,解决的思路是使三者各自有自己的外部环境变量:
function makeShowHelp(item) { return function() { showHelp(item.help); } } function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = makeShowHelp(item); } } setupHelp();
这样,每个 showHelp(item) 的外部环境变量都是 makeShowHelp() 函数中的变量 item,这个 item 是三个不同的值。
4. 一点联想
Java 中没有闭包的概念,但是却有类似的用法,在使用非静态嵌套类(内部类)时,内部类实例可以使用其外部类实例的成员变量和成员方法。举2个例子:示例1:
final MyClass myObj = new MyClass(); Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(build(myObj)); View.OnClickListener build(final MyClass myObj) { myObj.member1 = "i am a local variable"; return new View.OnClickListener() { update(myObj); }; }
实例2:
public class Outer { private String mem1 = "i am Outer's private property"; private void method1() { System.out.println(mem1); } public Inner getInnerInstance() { return new Inner(); } class Inner { public void print() { method1(); } } } public class Main { public static void main(String[] args) { Outer o = new Outer(); o.getInnerInstance().print(); } }
5. 参考文献
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures相关文章推荐
- JQuery1——基础($对象,选择器,对象转换)
- Android学习笔记(二九):嵌入浏览器
- Android java 与 javascript互访(相互调用)的方法例子
- 深入理解PHP之匿名函数
- JavaScript演示排序算法
- javascript实现10进制转为N进制数
- 2019年开发人员应该学习的8个JavaScript框架
- HTML中的script标签研究
- 对一个分号引发的错误研究
- 异步流程控制:7 行代码学会 co 模块
- ES6 走马观花(ECMAScript2015 新特性)
- JavaScript拆分字符串时产生空字符的原因
- Canvas 在高清屏下绘制图片变模糊的解决方法
- Redux系列02:一个炒鸡简单的react+redux例子
- JavaScript 各种遍历方式详解
- call/apply/bind 的理解与实例分享
- 如何创建对象以及jQuery中创建对象的方式