语法:
function name (arg[0],arg[1],arg[2]....){
//函数内容
}
关键点:
function关键字
函数名name!!!
特征:
函数声明提升——在执行代码之前会先读取每一个函数的声明,所以这种方法定义的函数,调用可以在定义之前。
最常见的语法:
var name = function (arg[0],arg[1],arg[2]....) {
//函数内容
}
理解:创建一个函数并把它赋值给变量name,但是注意function后面没有名称,name不是函数名
函数声明:
会把函数名与函数体进行绑定,所以对这种情况来说,若代码中重复定义了这个函数,js会尝试修复这个错误,不同浏览器的理解方式会有不同,所以尽量不要这样做。
此外就是之前提到的,会在代码执行前预先读函数声明。
函数表达式:
相当于把函数当成值来用,所以var定义的变量重名也无所谓。
官方定义:有权访问另一个函数作用域中的变量的函数
常见的创建方式:在一个函数内部创建另一个函数。
注意,创建的新函数可以用函数声明,也可以用匿名函数,只要创建了就是个闭包。
理解:
function A () {
var a =0; //定义A的变量
function B(){
alert (a); // B算是A的局部,所以可以调用A 的局部变量
}
}
alert(a); //错误! a算是function A 的局部变量,外部访问不到的
正因为闭包有可以访问一个函数内部的局部变量,那么我们可以利用这点,使用闭包,来在函数外部获取内部的值。
此处先引用阮一峰老师的闭包博文,引一下内容:
————————————————————————————————————————————
一、变量的作用域
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
另一方面,在函数外部自然无法读取函数内的局部变量。
function f1(){
var n=999;
}
alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){
n=999;
}
f1();
alert(n); // 999
二、如何从外部读取局部变量?
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
所以,使用闭包的精髓在于:
闭包中引用局部变量 + return
返回这个闭包,在外部进行接收,那么在这里我们一般使用var一个变量进行接收,那么实际上就是
var a = funtion(); //这个地方实际是一个匿名函数的定义式,闭包与匿名函数这两个概念在这个地方才会相交。
再进行下一步操作 a();
function A (){
var result = new Array();
for(var i = 0; i<10 ; i++){
result[i] = function(){ //相当于用循环写了10个闭包函数,每个函数返回自己的索引值
return i;
};
}
return result; //返回这10个闭包函数
}
var a = A();
a(); //10,10,10,10,10,10.....
原因:每个闭包函数的作用域链中都保存着function A 的变量result,所以对它们来说引用的是同一个i,当return时,i已经累加到10,所以返回是全都是10
自己的理解:
就相当于 var a = A(); 此时a接受A中的闭包,就相当于已经将function A 运行了一遍,只除了闭包的内容,
就是除了function(){
return i;
};
其他像之前的for循环已经完事了,i已经加到10了
a中保存的状态是运行一遍之后的初始化状态,实际上就是有10个闭包函数待执行
然后再a(); 此时就直接执行10个闭包函数内容:
function(){
return i;
}
那么就是执行10次这个函数,返回10 个10
在闭包中使用this时也容易出错,因为this其实是一个指代,指代当前真正运行这个函数时的环境。
那么就同上一个循环中会有的问题,闭包函数真正把它打开封装开始运行是在外面调用时,所以非常有可能此时this时全局变量了。
上一个例子:
var name = "window";
var object = {
name: "my",
getname : function(){
return function(){
return this.name;
};
}
};
alert(object.getname()()); //"window"
因为在外部才开始打开闭包,执行return this.name; 而此时this已经变成了全局变量。
若变成 return object.name,那么就会是“my”了。
如果非要使用this,那么可以将this变成另一个变量,然后在闭包中调用
var name = "window";
var object = {
name: "my",
getname : function(){
var that = this; //将this也变成一个变量
return function(){
return that.name; //在闭包中调用这个变量,这样就相当于将this与这个函数建立了联系,不会随着外部调用而丢失
};
}
};
alert(object.getname()()); //"window"
function f1(){
var n=999;
nAdd=function(){n+=1} //相当于用匿名函数的方法也创建了一个闭包函数,并赋值给了一个全局变量
//为啥此处用了一个全局变量写函数?因为要是nAdd也用return,那么f1就有两个返回函数了,外部没法调用了
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 A (){
for(var i =0 ; i<10: i++)
{
alert(i); //这句感觉像是一个块级作用域,i为for私有的,但是js无块级作用域,花括号相当于没有
}
alert(i); //这句不会报错,前面的花括号相当于没有
}
即使你在 function A中又声明了多个i,js不会报错,但是会自动忽略后面所有的i声明,只会进行i操作。
步骤:创建并立即调用这个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用
格式:
(function () {
//块级作用域
}) ();
将function套在一个括号里,正常如果匿名方式创建一个函数需要var一个变量来对function进行保存,但是这不用保存,马上跟着下一对括号就代表着执行。所以没有变量来对function进行接收保存,执行完就会全部被销毁。
有种那叫什么,一直飞的鸟,不能落地?落地就死了的赶脚......
用处:
避免有过多的全局变量,通过这种方式创建完就销毁,可以在全局环境中任意添加函数了
可以减少闭包占用内存的问题。
任何在函数中定义的变量,都可以理解为私有变量,那么对于构造函数的方法来创建对象来说,可以利用私有成员和特权成员来隐藏对象内部的数据。
举例:
function Person(name) {
this.getname = function () {
return this.name;
};
this.setname = function (value) {
name = value;
};
}
var person = new Person("jack");
alert(person.getname()); //"jack"
person.setname ("tom");
alert(person.getname()); //"tom"
在Person对象中,定义了两个特权方法,实际上就是在构造函数中写了两个闭包,可以访问到对象中的变量name。
那么在实例化后,只有通过getname函数和setname函数才可以访问到name参数,相当于保护了name
缺点:这种方式是通过构造函数中实现的对象方法,缺点就是与构造函数的缺点,每一次实例方法都会被重建一次。
改进:
静态私有变量
通过在私有作用域中定义变量或者函数
还有一种模块模式
其实没什么关系。闭包就是函数中定义的函数,具体怎么定义的都可以,可以是函数声明式,也可以是匿名函数式。
但是在利用闭包,通过return闭包函数来传递内部参数时,一般就会用匿名函数式进行接收这个函数包。
递归就是在一个函数中通过函数名,自己调用自己。
最经典的阶乘函数:
function A (num) {
if(num<=1){
return 1;
}
else{
return num * A(num-1);
}
}
var B = A;
A = null;
B(5); //ERROR!!
改进:
利用arguments.callee来代替函数名。(之前在讲函数时说到过)
function A (num) {
if(num<=1){
return 1;
}
else{
return num * arguements.callee(num-1);
}
}