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 相关文章推荐
javascript StringBuilder类实现
Dec 22 Javascript
JavaScript是否可实现多线程  深入理解JavaScript定时机制
Dec 22 Javascript
Web跨浏览器进程通信(Web跨域)
Apr 17 Javascript
JavaScript中匿名函数用法实例
Mar 23 Javascript
JS使用eval解析JSON的注意事项分析
Nov 14 Javascript
Express实现前端后端通信上传图片之存储数据库(mysql)傻瓜式教程(二)
Dec 10 Javascript
全面接触神奇的Bootstrap导航条实战篇
Aug 01 Javascript
详解js界面跳转与值传递
Nov 22 Javascript
jQuery实现文字自动横移
Jan 08 Javascript
vue组件如何被其他项目引用
Apr 13 Javascript
在webstorm开发微信小程序之使用阿里自定义字体图标的方法
Nov 15 Javascript
javascript设计模式 ? 代理模式原理与用法实例分析
Apr 16 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/11/23 PHP
php smarty函数扩展
2010/03/15 PHP
php-cli简介(不会Shell语言一样用Shell)
2013/06/03 PHP
如何使用纯PHP实现定时器任务(Timer)
2015/07/31 PHP
PHP实现的mongoDB数据库操作类完整实例
2018/04/10 PHP
node.js中的fs.fchmodSync方法使用说明
2014/12/16 Javascript
Nodejs学习笔记之Stream模块
2015/01/13 NodeJs
javascript实现按回车键切换焦点
2015/02/09 Javascript
基于javascript实现简单计算器功能
2016/01/03 Javascript
利用Jquery队列实现根据输入数量显示的动画
2016/09/01 Javascript
Javascript面试经典套路reduce函数查重
2017/03/23 Javascript
nodeJS服务器的创建和重新启动的实现方法
2018/05/12 NodeJs
Js中使用正则表达式验证输入是否有特殊字符
2018/09/07 Javascript
webpack+vue-cli项目中引入外部非模块格式js的方法
2018/09/28 Javascript
微信小程序实现swiper切换卡内嵌滚动条不显示的方法示例
2018/12/20 Javascript
Vue内部渲染视图的方法
2019/09/02 Javascript
layui字体图标 loading图标静止不旋转的解决方法
2019/09/23 Javascript
jQuery实现手风琴效果(蒙版)
2020/01/11 jQuery
vue实现编辑器键盘抬起时内容跟随光标距顶位置向上滚动效果
2020/05/28 Javascript
[43:51]2014 DOTA2国际邀请赛中国区预选赛 Dream Times VS TongFu
2014/05/22 DOTA
Python读写ini文件的方法
2015/05/28 Python
python使用paramiko实现远程拷贝文件的方法
2016/04/18 Python
对numpy中轴与维度的理解
2018/04/18 Python
用pyqt5 给按钮设置图标和css样式的方法
2019/06/24 Python
树莓派4B+opencv4+python 打开摄像头的实现方法
2019/10/18 Python
Pycharm创建文件时自动生成文件头注释(自定义设置作者日期)
2020/11/24 Python
HTML5去掉输入框type为number时的上下箭头的实现方法
2020/01/03 HTML / CSS
白俄罗斯大卖场:21vek.by
2019/07/25 全球购物
Athleta官网:购买女士瑜伽服、技术运动服和休闲运动服
2020/11/12 全球购物
Java里面有没有全局变量?为什么?
2015/02/06 面试题
四年大学生活的个人自我评价
2013/12/11 职场文书
安全教育实施方案
2014/03/02 职场文书
2019最新版劳务派遣管理制度
2019/08/16 职场文书
2019幼儿教师求职信(3篇)
2019/09/20 职场文书
Python中的xlrd模块使用整理
2021/06/15 Python
css3中transform属性实现的4种功能
2021/08/07 HTML / CSS