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 parsefloat parseint 转换函数
Jan 21 Javascript
将函数的实际参数转换成数组的方法
Jan 25 Javascript
JavaScript中创建类/对象的几种方法总结
Nov 29 Javascript
jQuery bt气泡实现悬停显示及移开隐藏功能的方法
Jul 12 Javascript
JavaScript 身份证号有效验证详解及实例代码
Oct 20 Javascript
JS基于递归实现倒计时效果的方法
Nov 26 Javascript
JavaScript 函数节流详解及方法总结
Feb 09 Javascript
Android中Okhttp3实现上传多张图片同时传递参数
Feb 18 Javascript
详解使用路由延迟加载 Angular 模块
Oct 12 Javascript
js判断数组是否包含某个字符串变量的实例
Nov 24 Javascript
Vue使用json-server进行后端数据模拟功能
Apr 17 Javascript
javascript实现用户必须勾选协议实例讲解
Mar 24 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/10/09 PHP
ThinkPHP使用心得分享-分页类Page的用法
2014/05/15 PHP
Yii实现多按钮保存与提交的方法
2014/12/03 PHP
php rsa 加密,解密,签名,验签详解
2016/12/06 PHP
JavaScript中提前声明变量或函数例子
2014/11/12 Javascript
jQuery网页版打砖块小游戏源码分享
2015/08/20 Javascript
JavaScript给input的value赋值引发的关于基本类型值和引用类型值问题
2015/12/07 Javascript
vue+vuex+axio从后台获取数据存入vuex实现组件之间共享数据
2017/04/22 Javascript
微信小程序实现获取自己所处位置的经纬度坐标功能示例
2017/11/30 Javascript
Express的HTTP重定向到HTTPS的方法
2018/06/06 Javascript
vue监听键盘事件的快捷方法【推荐】
2018/07/11 Javascript
javascript function(函数类型)使用与注意事项小结
2019/06/10 Javascript
vue实现微信浏览器左上角返回按钮拦截功能
2020/01/18 Javascript
python学习基础之循环import及import过程
2018/04/22 Python
python实现下载pop3邮件保存到本地
2018/06/19 Python
pyqt5 comboBox获得下标、文本和事件选中函数的方法
2019/06/14 Python
python自动生成model文件过程详解
2019/11/02 Python
Python 用turtle实现用正方形画圆的例子
2019/11/21 Python
python不使用for计算两组、多个矩形两两间的iou方式
2020/01/18 Python
Scrapy爬虫文件批量运行的实现
2020/09/30 Python
python如何编写类似nmap的扫描工具
2020/11/06 Python
matplotlib自定义鼠标光标坐标格式的实现
2021/01/08 Python
HTML5之SVG 2D入门3—文本与图像及渲染文本介绍
2013/01/30 HTML / CSS
手机配件第一品牌:ZAGG
2017/05/28 全球购物
创意活动策划书
2014/01/15 职场文书
初二生物教学反思
2014/02/03 职场文书
基层干部2014全国两会学习心得体会
2014/03/10 职场文书
土建专业大学生自荐信范文
2014/04/09 职场文书
党员干部一句话承诺
2014/05/30 职场文书
2014年为民办实事工作总结
2014/12/20 职场文书
2015年护士长个人工作总结
2015/04/24 职场文书
实习介绍信范文
2015/05/05 职场文书
工人先锋号事迹材料(2016精选版)
2016/03/01 职场文书
Python上下文管理器Content Manager
2021/06/26 Python
Android Rxjava3 使用场景详解
2022/04/07 Java/Android
pytest实现多进程与多线程运行超好用的插件
2022/07/15 Python