Js闭包学习笔记
好多内容摘抄了大神的博客内容,只为分享记录。如有冒犯,请见谅
参考文章
http://www.cnblogs.com/libin-1/p/5962269.html
http://www.cnblogs.com/happyzwt/p/6204121.html
http://www.cnblogs.com/zsxblog/p/5416648.html IIFE立即执行的函数好处
http://www.cnblogs.com/libin-1/p/5962269.html
首先看这篇博客吧,写的很清晰,浅显易懂
有关闭包你需要了解的一切
http://www.cnblogs.com/wujie520303/articles/4901613.html
function showPersonalInfo(addr) {
var name = 'jenemy';
return {
getInfo: function(age) {
return '我的英文名:' + name + ' 年龄:' + age + ' 住址:' + addr;
},
setInfoName: function(newName) {
name = newName;
}
}
}
var person = showPersonalInfo('上海市徐汇区');
person.getInfo(25); // '我的英文名:jenemy 年龄:25 住址:上海市徐汇区'
person.setInfoName('XiaoLu');
person.getInfo(25); // '我的英文名:XiaoLu 年龄:25 住址:上海市徐汇区'
我们将原先返回一个函数名换成了一个对象,并且拥有两个方法getInfo()
和setInfoName()
,然后我们发现当person.setInfoName('XiaoLu')
执行后name
得到了更新。实际上在调用setInfoName()
的时候我们并不知道函数showPersonalInfo()
内部有一个name
变量。使用闭包就是一个函数将一个内部函数返回给外部.从而在不公开内部变量的同时,允许外部利用该内部函数间接使用内部变量.类似c#的字段与属性
实际上闭包存储的是外部变量的引用,而不是它们的值的副本。这样会导致闭包只能取得包含函数中任何变量的最后一个值。
第二部分,深入理解闭包原理
1、有人提过“javascript中没用私有作用域的概念,如果在多人开发的项目上,你在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉”,并不是说全局变量和局部变量轻易的覆盖(可能我理解错他的意思)。经实测全局变量和局部即使命名相同,也不会混淆值,因为外部函数是取不到局部函数的值。
测试1,输出结果1,2,1。即a=2其实是省略var的全局变量写法,杜绝这样写。也就是出现了局部变量a和全局变量a互不影响,各走各的。
function test1(){
var a=1;
console.log(a);
}
test1();
a=2;//其实就是var a=2;
console.log(a);
test1();
测试结果为1,undefined,1,3
function test1(){
var a=1;
console.log(a);
}
test1();
console.log(a);
var a=3;
test1();
console.log(a);
测试2,输出1,2,1,3
var a=2;
function test1(){
var a=1;
console.log(a);
}
test1();
console.log(a);
a=3;
test1();
console.log(a);
2、
函数基本概念:
函数声明:function box(){}
函数表达式:var box = function(){};
匿名函数:function(){} 属于函数表达式
匿名函数的作用:如果将匿名函数赋值给一个变量,则声明了一个函数: var box= function(){};
如果将匿名函数赋予一个事件则成为事件处理程序: box.addEventListener("click",function(){alert("aaa")});
创建自执行函数:(function(){})()
函数定义的三种方法:
var box = function(){};
function box(){}
var box = new Function();
函数声明和函数表达式的不同:
js在进行预解析时函数声明会提升,而函数表达式必须js顺序执行到此函数代码时才会逐行解析
函数表达式后面加括号可以立即执行函数,函数声明不可以,只能以fnName()的方式调用才行
例如
a();//不会报错,因为函数a会提前解析
function a(){
console.log(1)
};
b();//会报错,因为函数b不会提前解析,b只会在顺序执行到的时候才解析。
var b= function(){
console.log(2)
};
3、自执行的匿名函数
1.
什么是自执行的匿名函数?
它是指形如这样的函数: function方法体外部包上(),后面再跟()
(function
{// code}
)();
自执行函数相当于我们上面两步的结合:
var b=function () {};
b();
自执行函数是非常有用的,可以用它创建命名空间,只要把自己所有的代码都写在这个特殊的函数包装内,在使用的时候只需要用这个奇怪的函数即可,伟大的JQuery就是通过这个奇怪的函数创建了一个$的对象,在这个匿名函数中,往往会定义一个属于自己的命名空间,或者返回一个属于自己的对象。独立的作用域,不会污染全局环境
2. 疑问
为什么(function {// code})();可以被执行, 而function {//
code}();却会报错?
3. 分析
(1). 首先, 要清楚两者的区别:
(function {// code})是表达式, function {//
code}是函数声明.
(2). 其次, js"预编译"的特点:
js在"预编译"阶段, 会解释函数声明, 但却会忽略表式.
(3). 当js执行到function() {//code}();时, 由于function() {//code}在"预编译"阶段已经被解释过, js会跳过function(){//code}, 试图去执行();, 故会报错;
当js执行到(function {// code})();时, 由于(function
{// code})是表达式, js会去对它求解得到返回值, 由于返回值是一
个函数, 故而遇到();时, 便会被执行.
4、根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。JQuery使用的就是这种方法,将JQuery代码包裹在( function (window,undefined){…jquery代码…} (window)中,在全局作用域中调用JQuery代码时,可以达到保护JQuery内部变量的作用。
自执行函数的理解,同时也是为了体验如何在JS中达到所谓的”封装“,实现c#类的概念。
由于函数是一个封闭的作用域范围,并且可以嵌套函数,所以可以使用这种匿名自执行函数来实现封装自己的所有函数和变量。
从而避免来自多个开发者的多个函数相互冲突,并且,他们位于同一个函数中所以可以相互应引用。
由于外部无法引用函数内部的变量,因此在执行完后很快就会被释放(有待考究),关键是这种机制不会污染全局对象。这同时也相当于定义了一个命名空间
来自不同的开发者的函数方法只位于自己的命名空间里。
5、JavaScript并不是面向对象的,所以它不支持封装。但是在不支持封装的语言里同样可以实现封装。而实现的方法就是匿名函数或者自执行函数,其实自执行函数是特殊的匿名函数。在JS中类是通过函数来模拟的,其实很难理解,但是理解了也是比较轻松的,都知道在C#/Java等语言中,类是创建对象的模板,也是属性和方法的集合,类中中可以定义多个方法,既然在JS中可以通过函数来模拟,那么函数中自然也就可以定义新的函数,或者说内部函数,如下面的就是一个类:
//定义
function F(x)
{
this.x = x;
function double(x){return x*x;}
this.getDoubleX = function(){
return double(this.x);
}
}
//使用
f = new F(12);
alert(f.getDoubleX());
函数F相当于一个构造函数,而函数里面的其他定义都是函数私有的外部无法访问,例如double函数。这样就变相实现了私有方法。其他打上“this.”前缀的成员相当于公开成员public,外部可以访问。
6、给自执行函数加参数的好处:对于当前作用域中,如果将window传入,就不用依赖全局对象了,一直控制在局部作用域
1 2 3 |
(function(window,document) { var div=document.getElementById('div'); })(window,document); |
7、在JavaScript里面,我们并没有块级作用域的概念,这个话怎么理解呢?下面来看一段代码:
function countNum(num){
for(var i=0;i<num;i++){
}
//这里我们调用一下i
console.log(i);
//这个时候我们控制台会输出i,一开始变量i的值是0,在java和C++等语言中,变量i在循环之后会被销毁,但是在JavaScript里面,这个i可以在函数的内部的任何地方被调用,输出结果100
}
如果你在函数里面重复声明了这个i,JavaScript是不是提示你重复定义了这个i,在java语言中,会直接报错,因为一个变量不能再一个函数中被重复定义多次,所以匿名函数可以用来模仿块级作用域来避免这些问题,像这样:
function countNum(num){
(for(var i=0;i<num;i++){})();
console.log(i);
//这里我们再调用一下i,这个时候会报错
}
所以这种技术通常用在全局作用域中被用在函数的外部,从而限制向全局作用域添加过多的变量和函数,同时可以避免命名冲突
这种技术我们比较关注的的点就是我们的this对象,首先我们必须明白这一点,我们的this是基于函数的执行环境的,在全局函数中,我们的this是window对象,当函数被当做某个对象的方法调用的时候,this是指向那个对象,不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window,当然根据我们编写闭包的方式,这个分界线并不总是那么清楚。
立即执行函数的作用
a.用来初始化
立即执行函数的一种用法是,进行一次性初始化。通常,你想要进行一些初始化,而又不会遗留下全局变量。如果这一初始化过程足够复杂,以至于需要临时变量,可以将所有的代码放到立即执行函数里面,让该函数为你执行清理工作。
如下代码是将单击事件处理程序分配给按钮,这是一种很好的选择:
(function() {
for(var i = 1; i <= 3; i++) {
document.getElementById("button-" + i).onclick = function() {
//事件处理的逻辑代码写在这里
}
}
}());
如果不用立即执行函数,变量i将保留在全局作用域里面,会污染作用域。
b.实现私有性
立即执行函数也可以实现私有性。你可以让全局函数定义在其他函数的闭包中,并且共享这个闭包的本地作用域。
闭包学习
http://www.cnblogs.com/wjw-blog/p/6137109.html
http://www.cnblogs.com/libin-1/p/5962269.html
闭包就是能够读取其他函数内部变量的函数。
在函数内部再定义一个函数,
function func1(){
n = 100;//没有用var 在全局作用域下
function func2(){
alert(n);//100
}
}
这样,函数func2就被包括在函数func1内部,这是func1内部的所有局部变量对func2都是可见的,反过来func2内部的局部变量对func1不可见。这就是JavaScript语言特有的“链式作用域”(chain scope)结构。子对象(function)会一级一级的向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之不成立。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义有权访问另一个函数作用域内变量的函数都是闭包。
。所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的用途:
闭包可以在很多地方使用。
1、读取函数内部的变量。(说白了,类似Java,父函数就是类Student,内部的变量就是私有字段name,子函数GetName(),SetName(),整体就是实现类的属性效果
2、让这些变量的值始终保持在内存中。
注意:
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作类对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
其实这句话通俗的来说就是:JavaScript中所有的function都是一个闭包。不过一般来说,嵌套的function所产生的闭包更为强大,也是大部分时候我们所谓的“闭包”。
function a() {
var i = 0;
function b() {
alert(++i);
}
return b;
}
var c = a();
c();//1
c();//2
当函数a的内部函数b被函数a外的一个变量c引用的时候,就创建了一个闭包。
让我们说的更透彻一些。所谓“闭包”,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标 对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目 标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新 的值,和上次那次调用的是各自独立的。
如果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。
当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
当执行函数a的时候,a会进入相应的执行环境(excution context)。
在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。
到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。
测试结果1,2,1,2。
function a相当于类,i相当于字段,b函数(闭包)相当于Get属性值。闭包就是想实现类get属性值。Javascript不是面向对象的,想模拟实现出面向对象。C调用时会创建闭包,会使a一直存在内存中,来间接保留字段i的值。虽然这个字段名已经销毁。c和d两个闭包是相互独立的。相当于构造了两个对象,无任何关系。而同一个c属于一个对象,两个调用,都是取的对象中i的值,因此第二次调用会沿用第一次的值。
function a() {
var i = 0;
function b() {
alert(++i);
}
return b;
}
var c = a();
c();
c();
var d= a();
d();
d();