jquery匿名函数和闭包(它山之石)笔记
前言:最近在项目上遇见了一些莫名其妙的js代码和错误;请教了同事后得知原来是匿名函数的调用和js闭包的问题,匿名函数我倒是不陌生,但是js闭包是什么鬼??于是在博客园里面找了相关的资料,并做下记录以免以后忘记。
一、匿名函数
1.要使用函数就必须存在该函数,声明一个函数最常用的就是通过function来定义(官方文档):
function functionname ([arg1 [, arg2 [,...[, argN]]]]) { statements }参数functionname
必需。 函数的名称。
- arg1...argN
可选。 函数理解的参数的可选的用逗号分隔的列表。
- statements
可选。 一个或多个 JavaScript 语句。
但是,无论你怎么定义函数,js解释器都会把它翻译成一个Function对象,也就是说每个函数都是一个Function对象:
1 function test(){ 2 //do something 3 } 4 console.log(typeof test);//function
2.Function对象
Function对象是用于创建新函数的,是 JavaScript 中的基本数据类型。那么我们也可以直接利用该对象创建一个函数(官方文档):
functionName = new Function( [argname1, [... argnameN,]] body );
- functionName
必需。 新创建的函数的名称。
- argname1...argnameN
可选。 该函数接受的参数的列表。
- body
可选。 包含在调用函数时要执行的 JavaScript 代码块的字符串。
例如:
1 var test = new Function("a", "b", "console.log(a+b);"); 2 test(3, 6);//9
了解了函数的声明后我来看看“匿名函数”
3.匿名函数
顾名思义,没有名字的函数就是匿名函数。
1 function(){ 2 //do something 3 }
那么问题来了,没有名字的函数我们怎么调用呢?顿时脑海里突然闪现出一句话‘都不知道怎么用你,我要你来干嘛呀?’,那不就是浪费内存吗?说到内存似乎又有种柳暗花明的感觉!!既然是存在内存中的话我们只有找到该内存地址就可以了嘛,哈哈...幸好我还是懂面向对象的;要使用一个对象,就必须存在一个变量来指向该对象的内存地址(也就是引用),有了对象的引用当然就可以访问对象了,那么匿名函数就是这么用了:
1 var test = function (a, b) { 2 console.log(a * b); 3 } 4 test(3, 6);//18
这就说到我遇到的问题了,大概代码如下:
1 var a = (function(a,b){ 2 //do something 3 })(x,y);
我一看到就蒙了,哎呀!这什么代码呀??那么多小括号
下面我就说说我个人的理解吧:
首先把代码分块来看,就是等号后面的两对小括号,第一对小括号内放其实是一个匿名函数的声明,第二对小括号我们留到最后不攻自破;那么第一队小括号内干嘛要声明一个函数呢?这不就是废话嘛!声明匿名函数当然是供其他地方用的,上面说过了只要有函数的引用就能调用函数,所以稍微改了一下代码:
var func = function(a,b){ //do something } var a = (func)(x,y);
我就把函数的声明部分提出来赋值给func变量,然后下面的代码就变成:var a = (func)(x,y);,是不是很像调用func函数并传入x和y参数值呀?不错这就是调用匿名函数的一种方式,所以第二对小括号就真的不攻自破了,这就是调用匿名函数的参数列表;但是我还有一点搞不明白,既然func已经是匿名函数的引用为什么就不能这样调用呢(就是不用第一对小括号,当然这时编译是出错的,难道又是那句铁一般的话“这是规定”?):
var a = function(a,b){ //do something }(x,y);
二、闭包
以下是我找到闭包相关的定义:
1、$(document).ready()的参数
我们在写jQuery时都会把一系列的函数放在$(document).ready()中,这其实就是一个闭包,这有效避免了命名冲突;
2、绑定事件或者循环绑定事件
$("btn").click(function(){});
3、如果用一个对象作为参数来调用 函数
foo
,那么foo
中传入的实际上是原始对象的引用,所以这个原始对象也相当于被闭包了4、“闭包”(closure)闭包就是能够读取其他函数内部变量的函数。
5、闭包的基本写法:
(function(){do someting})();
//这个你就理解为定义一个匿名函数并立即执行
带参数的话就这样:
(function(形参){do someting})(实参);6、闭包是什么?闭包是指某种程序语言中的代码块允许一级函数存在并且在一级函数中所定义的自由变量能不被释放,直到一级函数被释放前,一级函数外也能应用这些未释放的自由变量。
怎样?看得一头冒汗吧……没事,我也是(虽然是我是了解的,只是表达能力的问题)。让我们换个更加简单的方法说明:闭包,其实是一种语言特性,它是指的是程序设计语言中,允许将函数看作对象,然后能像在对象中的操作搬在函数中定义实例(局部)变量,而这些变量能在函数中保存到函数的实例对象销毁为止,其它代码块能通过某种方式获取这些实例(局部)变量的值并进行应用扩展。
不知道这么再解释后会否更加清晰,如果还是不明白,那么我们再简化一下:闭包,其实就是指程序语言中能让代码调用已运行的函数中所定义的局部变量。
以我的理解来说吧。是否应用了闭包特性,必须确定该段代码有没有最重要的要素:未销毁的局部变量。那么很显然,没有任何实现的匿名函数不可能应用了闭包特性。但如果匿名函数里面有实现呢?那也还得确定它的实现中有没有用到那些未销毁的局部变量。所以如果问你那个开篇中的jQuery代码片段是应用了JS里的什么特性?那么它只是匿名函数与匿名函数的调用而已。但是,它隐含了闭包的特性,并且随时可以实现闭包应用。因为JS天生就是有这个特性的!(这只是我的理解,我也想知道你的理解,欢迎交流!关于闭包,有机会还是独立再开一个专题吧!)
官方文档:
在 JavaScript 中,内部(嵌套)函数将存储对局部变量的引用(即使在函数返回之后),这些局部变量存在于与函数本身相同的范围中。 这一组引用称为闭包。 在以下示例中,对内部函数的第二次调用所输出的消息与第一次调用相同(“Hello Bill”),因为外部函数的输入参数 name 是存储在内部函数闭包中的局部变量。
1 function send(name) { 2 // Local variable 'name' is stored in the closure 3 // for the inner function. 4 return function () { 5 sendHi(name); 6 } 7 } 8 9 function sendHi(msg) { 10 console.log('Hello ' + msg); 11 } 12 13 var func = send('Bill'); 14 func(); 15 // Output: 16 // Hello Bill 17 sendHi('Pete'); 18 // Output: 19 // Hello Pete 20 func(); 21 // Output: 22 // Hello Bill
不管你们说什么,我还是参考官方的说法吧!根据官方文档的描述和我个人的理解,形成闭包的必要因素有:1.一个内部函数(这个函数是存储对局部变量的引用),2.必须返回该内部函数的引用或立即执行该函数;
还是说说我遇到的问题吧,我在一个页面上动态添加了几个按钮并为这些按钮绑定了click事件(打印出当前点击的是第几个按钮),但是奇怪的问题出现了,点击每一个按钮打印出来的信息都是一样的:
1 <div class="btn-contaner"></div>
1 for (var i = 0; i < 10; i++) { 2 var index = i + 1; 3 var $btn = $("<input id=\"btn-" + index + "\" type=\"button\" value=\"按钮" + index + "\"/>").bind("click", function () { 4 console.log("你点击的是第" + index + "个按钮"); 5 }); 6 $(".btn-contaner").append($btn); 7 }
打印出来的结果如下:
很离谱呀,我不是点击了第10按钮10次,而是每个按钮都点击一次的哦!然后我把js修改了一下,有两个版本打印出来的结果是正确的:
//第一方案 var $btn = $("<input id=\"btn-" + index + "\" type=\"button\" value=\"按钮" + index + "\" onclick=\"console.log('你点击的是第" + index + "个按钮')\"/>"); //第二方案(只是把第一个方案的console.log()提出来而已) var $btn = $("<input id=\"btn-" + index + "\" type=\"button\" value=\"按钮" + index + "\" onclick=\"printBtnInfo('你点击的是第" + index + "个按钮')\"/>"); function printBtnInfo(msg) { console.log(msg); }
但是我不习惯这样绑定事件呀,所以找来了同事帮我看看,同事一看就说是这是闭包导致的,叫我看看闭包和如何逃离闭包的相关资料,所有我就到博客园里搜搜,哎 正好有这方面的资料,所以我就认真的看几篇,最后把代码修改正确了:
1 for (var i = 0; i < 10; i++) { 2 var index = i + 1; 3 (function (index) { 4 var $btn = $("<input id=\"btn-" + index + "\" type=\"button\" value=\"按钮" + index + "\"/>").bind("click", function () { 5 console.log("你点击的是第" + index + "个按钮"); 6 }); 7 $(".btn-contaner").append($btn); 8 })(index); 9 }
我看了看,这不是匿名函数的调用方法吗?果然是啊!!!!!难道匿名函数也是一个‘闭包’?我再看看上面我总结的闭包形成因素,还真都符合条件!不错,这就是动态新增并绑定按钮事件时,对应的闭包保存下了对应的index值,所以每次触发按钮的点击事件的时候就能打印出正确的信息;
写了一天的博文了,头晕了,反正也写的差不多了,我就在附上‘闭包’的优缺点,有时间在回来整理了:
javascript"闭包"很容易造成"内存泄漏", 而jQuery已经自动为我们规避、处理了由"闭包"引发的"内存泄漏"
内存泄漏可以狭隘地理解为:应用程序中变量对应一块内存,由于某些原因(比如代码上的漏洞),始终没有对变量及时实施手动或自动垃圾回收,内存没有得到及时释放,造成内存泄漏。
与"闭包"相关的包括:变量的作用域、javascript垃圾回收、内存泄漏,需在实践多体会。闭包的好处:
不增加额外的全局变量,
执行过程中所有变量都是在匿名函数内部。
有效避免了命名冲突如果在一个外部函数中再定义一个内部函数,那么内部函数就是一个闭包!!
要实现闭包的话,需要将内部函数作为外部函数的返回值返回,内部函数在返回前,会将所有已访问过的外部函数中的变量在内存中锁定??
如果用一个对象作为参数来调用 foo,那么 foo 中传入的实际上是原始对象的引用,所以这个原始对象也相当于被闭包了优点:
1. 逻辑连续,当闭包作为另一个函数调用的参数时,避免你脱离当前逻辑而单独编写额外逻辑。
2. 方便调用上下文的局部变量。
3. 加强封装性,第2点的延伸,可以达到对变量的保护作用。
缺点:
闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅因为它常驻内存,更重要的是,对闭包的使用不当会造成无效内存的产生
闭包的缺点就是常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
最后:因本人技术知识比较单薄,有哪里总结不合理或错误的地方欢迎大家指点或交流;一起学习、一起进步。
2017-02-22 18:54:05