JavaScript 匿名函数(anonymous function)与闭包(closure)


Posted in Javascript onOctober 04, 2011

引入
匿名函数
闭包
变量作用域
函数外部访问函数内部的局部变量
用闭包实现私有成员
引入
闭包是用匿名函数来实现。闭包就是一个受到保护的变量空间,由内嵌函数生成。“保护变量”的思想在几乎所有的编程语言中都能看到。

先看下 JavaScript 作用域:
JavaScript 具有函数级的作用域。这意味着,不能在函数外部访问定义在函数内部的变量。
JavaScript 的作用域又是词法性质的(lexically scoped)。这意味着,函数运行在定义它的作用域中,而不是在调用它的作用域中。这是 JavaScript 的一大特色,将在后面说明。
把这两个因素结合在一起,就能通过把变量包裹在匿名函数中而对其加以保护。你可以这样创建类的私有变量:

var baz; 
(function() { 
var foo = 10; 
var bar = 2; 
baz = function() { 
return foo * bar; 
}; 
})(); 
baz();

尽管在匿名函数外执行,但 baz 仍然可以访问 foo 和 bar。

说明:

1,第 1 行,baz 是全局变量;

2,第 3 ~第 9 行,定义一个匿名函数;

3,第 4 和 5 行,foo 和 bar 是匿名函数内的局部变量;第 6 ~ 8 行,在匿名函数内定义一个匿名函数,并将其赋值给全局变量 baz;

4,第 10 行,调用 baz。若改成 "alert(baz());",将显示 20;

5,按理说,在匿名函数外不能访问 foo 和 bar,但是现在可以。

在说明闭包前,先了解一下匿名函数。

匿名函数
匿名函数是指那些无需定义函数名的函数。匿名函数与 Lambda 表达式(拉姆达表达式)是一回事。唯一的不同——语法形式不同。Lambda 表达式更进一步。本质上,它们的作用都是:产生方法——内联方法,也就是说,省去函数定义,直接写函数体。

Lambda 表达式一般形式:

(input parameters) => {statement;}
其中:

参数列表,可以有多个、一个或者无参数。参数可以隐式或者显式定义。
表达式或者语句块,也就是函数体。
上面代码,第 6 ~ 8 行,没有函数名,是个匿名函数,采用 Lambda 表达式,严格意义上,虽然语法有差异,但目的一样。

示例1:

var baz1 = function() { 
var foo = 10; 
var bar = 2; 
return foo * bar; 
}; 
function mutil() { 
var foo = 10; 
var bar = 2; 
return foo * bar; 
}; 
alert(baz1()); 
var baz2 = mutil(); 
alert(baz2);

说明:

1,baz1 与 baz2 完全一样,但 baz1 与 baz2 相比,省去了函数定义,直接函数体——看上去多简约。

闭包
变量作用域
示例2:函数内部可以访问全局变量。

var baz = 10; 
function foo() { 
alert(baz); 
} 
foo();

这是可以。

示例3:函数外部不能访问函数内部的局部变量。

function foo() { 
var bar = 20; 
} 
alert(bar);

这会报错。

另外,函数内部声明变量时,一定要使用 var 关键字,否则,声明的是一个全局变量。

示例4:

function foo() { 
bar = 20; 
} 
alert(bar);

 函数外部访问函数内部的局部变量
实际情况,需要我们从函数外部获得函数内部的局部变量。先看示例5。

示例5:

function foo() { 
var a = 10; 
function bar() { 
a *= 2; 
} 
bar(); 
return a; 
} 
var baz = foo(); 
alert(baz);

a 定义在 foo 内,bar 可以访问,因为 bar 也定义在 foo 内。现在,如何让 bar 在 foo 外部被调用?

示例6:

function foo() { 
var a = 10; 
function bar() { 
a *= 2; 
return a; 
} 
return bar; 
} 
var baz = foo(); 
alert(baz()); 
alert(baz()); 
alert(baz()); 

var blat = foo(); 
alert(blat());

说明:

1,现在可以从外部访问 a;
2,JavaScript 的作用域是词法性的。a 是运行在定义它的 foo 中,而不是运行在调用 foo 的作用域中。 只要 bar 被定义在 foo 中,它就能访问 foo 中定义的变量 a,即使 foo 的执行已经结束。也就是说,按理,"var baz = foo()" 执行后,foo 已经执行结束,a 应该不存在了,但之后再调用 baz 发现,a 依然存在。这就是 JavaScript 特色之一——运行在定义,而不是运行的调用。

其中, "var baz = foo()" 是一个 bar 函数的引用;"var blat= foo()" 是另一个 bar 函数引用。

用闭包实现私有成员
现在,需要创建一个只能在对象内部访问的变量。用闭包再适合不过,因为通过闭包你可以创建只允许特定函数访问的变量,而且这些变量在这些函数的各次调用间依然存在。

为了创建私有属性,你需要在构造函数的作用域中定义相关变量。这些变量可以被定义于该作用域中的所有函数访问,包括那些特权方法。

示例7:

var Book = function(newIsbn, newTitle, newAuthor) { 
// 私有属性 
var isbn, title, author; 
// 私有方法 
function checkIsbn(isbn) { 
// TODO 
} 
// 特权方法 
this.getIsbn = function() { 
return isbn; 
}; 
this.setIsbn = function(newIsbn) { 
if (!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.'); 
isbn = newIsbn; 
}; 
this.getTitle = function() { 
return title; 
}; 
this.setTitle = function(newTitle) { 
title = newTitle || 'No title specified.'; 
}; 
this.getAuthor = function() { 
return author; 
}; 
this.setAuthor = function(newAuthor) { 
author = newAuthor || 'No author specified.'; 
}; 
// 构造器代码 
this.setIsbn(newIsbn); 
this.setTitle(newTitle); 
this.setAuthor(newAuthor); 
}; 

// 共有、非特权方法 
Book.prototype = { 
display: function() { 
// TODO 
} 
};

说明:

1,用 var 声明变量 isbn、title 和 author,而不是 this,意味着它们只存在 Book 构造器中。checkIsbn 函数也是,因为它们是私有的;

2,访问私有变量和方法的方法只需声明在 Book 中即可。这些方法称为特权方法。因为,它们是公共方法,但却能访问私有变量和私有方法,像 getIsbn、setIsbn、getTitle、setTitle、getAuthor、setAuthor(取值器和构造器)。

3,为了能在对象外部访问这些特权方法,这些方法前边加了 this 关键字。因为这些方法定义在 Book 构造器的作用域里,所以它们能够访问私有变量 isbn、title 和 author。但在这些特权方法里引用 isbn、title 和 author 变量时,没有使用 this 关键字,而是直接引用。因为它们不是公开的。

4,任何不需要直接访问私有变量的方法,像 Book.prototype 中声明的,如 display。它不需要直接访问私有变量,而是通过 get*、set* 简介访问。

5,这种方式创建的对象可以具有真正私有的变量。其他人不能直接访问 Book 对象的任何内部数据,只能通过赋值器和。这样一切尽在掌握。

但这种方式的缺点是:

“门户大开型”对象创建模式中,所有方法都创建在原型 prototype 对象中,因此不管生成多少对象实例,这些方法在内存中只有一份。
而采用本节的做法,没生成一个新的对象实例,都将为每个私有方法(如,checkIsbn)和特权方法(如,getIsbn、setIsbn、getTitle、setTitle、getAuthor、setAuthor)生成一个新的副本。
因此,本节方法,只适于用在真正需要私有成员的场合。另外,这种方式也不利于继承。

Javascript 相关文章推荐
js中document.getElementByid、document.all和document.layers区分介绍
Dec 08 Javascript
jQuery(非HTML5)可编辑表格实现代码
Dec 11 Javascript
jcrop基本参数一览
Jul 16 Javascript
详解JavaScript中undefined与null的区别
Mar 29 Javascript
Javascript中的方法链(Method Chaining)介绍
Mar 15 Javascript
jQuery实现获取绑定自定义事件元素的方法
Dec 02 Javascript
js实现无缝循环滚动
Jun 23 Javascript
超全面的vue.js使用总结
Feb 12 Javascript
vue如何从接口请求数据
Jun 22 Javascript
element-ui 关于获取select 的label值方法
Aug 24 Javascript
JS返回页面时自动回滚到历史浏览位置
Sep 26 Javascript
微信小程序实现录音功能
Nov 22 Javascript
通过JavaScript控制字体大小的代码
Oct 04 #Javascript
jQuery图片预加载 等比缩放实现代码
Oct 04 #Javascript
jQuery EasyUI API 中文文档 - Menu菜单
Oct 03 #Javascript
Dom 结点创建 基础知识
Oct 01 #Javascript
JavaScript 的继承
Oct 01 #Javascript
Jquery 的扩展方法总结
Oct 01 #Javascript
jQuery EasyUI API 中文文档 - Tabs标签页/选项卡
Oct 01 #Javascript
You might like
[转帖]PHP世纪万年历
2006/12/06 PHP
将word转化为swf 如同百度文库般阅读实现思路及代码
2013/08/09 PHP
PHP date()函数警告: It is not safe to rely on the system解决方法
2014/08/20 PHP
thinkPHP3.2简单实现文件上传的方法
2016/05/16 PHP
Laravel中日期时间处理包Carbon的简单使用
2017/09/21 PHP
Laravel框架实现的rbac权限管理操作示例
2019/01/16 PHP
php基于 swoole 实现的异步处理任务功能示例
2019/08/13 PHP
js截取函数(indexOf,join等)
2010/09/01 Javascript
JavaScript操作DOM元素的childNodes和children区别
2015/04/01 Javascript
AngularJS自定义插件实现网站用户引导功能示例
2016/11/07 Javascript
jQuery插件echarts设置折线图中折线线条颜色和折线点颜色的方法
2017/03/03 Javascript
vuex + keep-alive实现tab标签页面缓存功能
2019/10/17 Javascript
Python实现堆排序的方法详解
2016/05/03 Python
浅谈终端直接执行py文件,不需要python命令
2017/01/23 Python
Android基于TCP和URL协议的网络编程示例【附demo源码下载】
2018/01/23 Python
Python延时操作实现方法示例
2018/08/14 Python
Python读取xlsx文件的实现方法
2019/07/04 Python
python连接PostgreSQL数据库的过程详解
2019/09/18 Python
python中封包建立过程实例
2021/02/18 Python
英国女鞋购物网站:Moda in Pelle
2019/02/18 全球购物
在家更换处方镜片:Lensabl
2019/05/01 全球购物
某IT外企面试题-二分法求方程!看看大家的C++功底
2015/07/04 面试题
TCP协议通讯的过程和步骤是什么
2015/10/18 面试题
软件测试工程师笔试题带答案
2015/03/27 面试题
生物制药毕业生自荐信
2013/10/16 职场文书
政府信息公开实施方案
2014/05/09 职场文书
文明寝室申报材料
2014/05/12 职场文书
民事诉讼授权委托书范文
2014/08/02 职场文书
年检委托书
2014/08/30 职场文书
争当四好少年演讲稿
2014/09/13 职场文书
农村党员干部承诺书
2015/05/04 职场文书
公司安全管理制度范本
2015/08/05 职场文书
CSS 还能这样玩?奇思妙想渐变的艺术
2021/04/27 HTML / CSS
CSS+HTML 实现顶部导航栏功能
2021/08/30 HTML / CSS
MySQL悲观锁与乐观锁的实现方案
2021/11/02 MySQL
类和原型的设计模式之复制与委托差异
2022/07/07 Javascript