在很多情况下,我们经常可以看到类似于这样的js写法:
(function(){ ...})(window)
(function(){})()
这其实就相当于是js的闭包。表明立刻执行程序
那对于这个方法到底怎么理解呢?在解释完闭包之后,在回来看这个函数写法。
1、什么是js闭包:
闭包: 指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
简单可以理解为通常是一个函数或者表达式,这个函数或者表达式定义在另外一个函数的内部,但是可以访问外部函数的局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。
Object对象都会有一个prototype的值
/* 创建 - MyObject1 - 类型对象的函数*/
function MyObject1(formalParameter){
/* 给创建的对象添加一个名为 - testNumber - 的属性
并将传递给构造函数的第一个参数指定为该属性的值:*/
this.testNumber = formalParameter;
}
/* 创建 - MyObject2 - 类型对象的函数*/
function MyObject2(formalParameter){
/* 给创建的对象添加一个名为 - testString - 的属性
并将传递给构造函数的第一个参数指定为该属性的值:*/
this.testString = formalParameter;
}
/* 接下来的操作用 MyObject1 类的实例替换了所有与 MyObject2 类的实例相关联的原型。而且,为 MyObject1 构造函数传递了参数 - 8 - ,因而其 - testNumber - 属性被赋予该值:*/
MyObject2.prototype = new MyObject1( 8 );
/* 最后,将一个字符串作为构造函数的第一个参数,创建一个 - MyObject2 - 的实例,并将指向该对象的引用赋给变量 - objectRef - :*/
var objectRef = new MyObject2( “String_Value” );
MyObject1.prototype
MyObject1 {}
MyObject2.prototype
MyObject1 {testNumber: 8}
objectRef
MyObject2 {testString: "String_Value", testNumber: 8}
1、作函数变量引用 - 当函数返回时其处于激活状态
2、闭包当函数返回时没有释放资源栈区
实上面两点合成点,闭包函数返回时,该函数内部变量处于激活状态,函数所栈区依保留
function a(){
var i=0;
function b(){
alert(++i);
}
return b;
}
var c = a();
c();
这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:
当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。
那么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。
闭包的应用场景 1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。 2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。
1闭包外层函数. 2闭包内部都有函数. 3闭包会return内部函数. 4闭包返回函数内部能有return.(因样真结束了) 5执行闭包,闭包内部变量会存,而闭包内部函数内部变量会存. 闭包应用场景(呵呵,复制参考资料) 1、保护函数内变量安全开始例子例函数ai只有函数b才能访问而无法通过其途径访问因此保护了i安全性 2、内存维持变量依前例由于闭包函数ai直存于内存因此每次执行c()都会给i自加1
js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等.
作用域链的定义:
作用域链就是函数在定义的时候创建的,用于寻找使用到的变量的值的一个索引,而他内部的规则是,把函数自身的本地变量放在最前面,把自身的父级函数中的变量放在其次,把再高一级函数中的变量放在更后面,以此类推直至全局对象为止.当函数中需要查询一个变量的值的时候,js解释器会去作用域链去查找,从最前面的本地变量中先找,如果没有找到对应的变量,则到下一级的链上找,一旦找到了变量,则不再继续.如果找到最后也没找到需要的变量,则解释器返回undefined.
在一个demo:
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个
匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
Js代码
function outerFun()
{
//没有var
a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);
结果为 0,0 真是奇怪,为什么呢?
这个是作用域链的影响:
作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0时,因为没有使用var关键字,因此赋值操作会沿着作用域链到var a=4; 并改变其值.var quantaty = 5; //全局变量
function addGlobalQueryOnClick(linkRef){
/* 如果可以将参数 - linkRef - 通过类型转换为 ture
(说明它引用了一个对象):
*/
if(linkRef){
/* 对一个函数表达式求值,并将对该函数对象的引用
指定给这个链接元素的 onclick 事件处理器:
*/
linkRef.onclick = function(){
/* 这个内部函数表达式将查询字符串
添加到附加事件处理器的元素的 - href - 属性中:
*/
this.href += ('?quantaty='+escape(quantaty));
return true;
};
}
}
这时候不使用闭包:
/* 定义一个全局变量,通过下面的函数将它的值
作为查询字符串的一部分添加到链接的 - href - 中:
*/
var quantaty = 5;
/* 当把一个链接(作为函数中的参数 - linkRef -)传递给这个函数时,
会给这个链接添加一个 onclick 事件处理器,该事件处理器会
将全局变量 - quantaty - 的值作为查询字符串的一部分添加到
链接的 - href - 中,然后返回 true,以便单击链接时定位到由
作为 - href - 属性值的查询字符串所指定的资源:
*/
function addGlobalQueryOnClick(linkRef){
/* 如果 - linkRef - 参数能够通过类型转换为 true
(说明它引用了一个对象):
*/
if(linkRef){
/* 将一个对全局函数的引用指定给这个链接
的事件处理属性,使函数成为链接元素的事件处理器:
*/
linkRef.onclick = forAddQueryOnClick;
}
}
/* 声明一个全局函数,作为链接元素的事件处理器,
这个函数将一个全局变量的值作为要添加事件处理器的
链接元素的 - href - 值的一部分:
*/
function forAddQueryOnClick(){
this.href += ('?quantaty='+escape(quantaty));
return true;
}
function ExampleConst(param){
/* 通过对函数表达式求值创建对象的方法,
并将求值所得的函数对象的引用赋给要创建对象的属性:
*/
this.method1 = function(){
... // 方法体。
};
this.method2 = function(){
... // 方法体。
};
this.method3 = function(){
... // 方法体。
};
/* 把构造函数的参数赋给对象的一个属性:*/
this.publicProp = param;
}
function ExampleConst(param){
/* 将构造函数的参数赋给对象的一个属性:*/
this.publicProp = param;
}
/* 通过对函数表达式求值,并将结果函数对象的引用
指定给构造函数原型的相应属性来创建对象的方法:
*/
ExampleConst.prototype.method1 = function(){
... // 方法体。
};
ExampleConst.prototype.method2 = function(){
... // 方法体。
};
ExampleConst.prototype.method3 = function(){
... // 方法体。
};
这那种情况下,只创建一次函数对象,并把它们指定给构造函数 prototype 的相应属性显然更有效率。这样一来,它们就能被构造函数创建的所有对象共享了.
那么,现在我们再来分析开头说到的那个问题:
(function(){})
这个就相当于是个闭包,闭包是一个匿名函数,
所以要给 function 添加括弧是为了让它形成一个表达式 (expression), 有了表达式,并且确定它的类型是个函数 (Function 实例), 就可以直接调用它。
后面的一对括弧是可以工作的,它的意义是:我要调用 (call) 这个函数。既然是函数调用,那就可以像一般的函数那样,在调用时传入参数,传入 window 参数。
(function(win) {// ...})(window);
你可以在闭包内任何地方使用 win, 它都会指向 window 对象。
不过,便利的同时也会带来陷阱。在 IE 上,window 总是指向当前窗口对象,这个没有问题,但是在某些场景下,使用闭包内的 win 变量会导致拒绝访问错误 (Access denied). 重现方式大致是这样的:当页面引用其他域名的脚本,并且该脚本调用了闭包内的 window.document, 而且这个闭包代码是来自另一个域名的脚本。在这种情况下,使用 win 会保持对 window 最早的引用,通过另一个域的脚本访问 win 会导致 IE 认为脚本产生了跨越冲突,从而拒绝了对 win.document 的访问。解决办法是不使用形参 win, 而是直接使用 window. 需要说明的是,给闭包传入 document 也会导致 IE 出现同样的问题。
这个,为什么要将window和undefined作为参数传给它?
因为 ecmascript 执行JS代码是从里到外,因此把全局变量window或jQuery对象传进来,就避免了到外层去寻找,提高效率。undefined在老一辈的浏览器是不被支持的,直接使用会报错,js框架要考虑到兼容性,因此增加一个形参undefined。
//方式一
(function(undefined ) {
window.property1 = ……;
window.property2 = ……;
……
})();
//方式二
(function( window, undefined ) {
... // code goes here
})(window);
//方式三
(function(undefined ) {
var tmp = window;
tmp.property1 = ……;
tmp.property2 = ……;
……
})();