JavaScript中函数、闭包可能是用到很频繁的了,并且jQuery框架也是利用函数以及闭包的很多特性,所以掌握函数和闭包的概念对于我们掌握原生javaScript和jQuery源码来说都是非常有帮助的。
那么首先从JS函数的创建开始说起。函数的创建又这么三种方式:
1. 函数声明,直接上代码:
//声明函数
function FunctionDeclare(){
alert("函数声明方式创建函数");
}
//调用函数
FunctionDeclare();
上述代码以下面这种方式写也是可以的。
FunctionDeclare();
function FunctionDeclare(){
alert("函数声明方式创建函数");
}
因为在JavaScript中,有一个函数提升的概念,也就是浏览器首先在解析时,先解析函数声明,所以在执行的过程中不会发生错误。
2. 函数表达式
var Func = function (){
alert("函数表达式创建函数");
}
Func();
首先,将一个匿名函数赋值给一个变量,那么此匿名函数的名字变为了Func,之后调用函数时,直接利用函数名调就可以了。
看似这两种方法都差不多,其实两者还是有比较大的区别。
如果利用函数表达式实现下面的代码是不可行的
Func();
var Func = function(){
alert("函数表达式创建函数");
}
var max = new Function('num1', 'num2', 'return (num1 > num2) ? num1 : num2');
max(2,5); //返回5
var p1 = {
name : "Liu",
age: 22
}
var p2 = {
name : "Yang",
age : 25
}
function maxAge(property)
{
return function(obj1, obj2){
var obj1_Age = obj1[property];
var obj2_Age = obj2[property];
return ((obj1_Age > obj2_Age) ? obj1_Age : obj2_Age);
}
}
var m = maxAge('age')(p1,p2); //此时,m的值为25
这幅图可以这么理解:
首先,一开始的时候是全局作用域,也就是在window对象下,此时左边window对象的活动对象为右边window活动对象;
下来是maxAge函数的执行,此时它的作用域有两个,本身的活动对象以及外部(window对象)的作用域,这里本身的活动对象在作用域链的顶端,越处在外部,其越靠近作用域链的末端;每个函数活动对象中都有一个arguments属性,这个属性保存传入的参数,它是一个类数组。同时也有函数内部的参数(局部变量)。
匿名函数也是如此。但是匿名函数的作用域链上有maxAge函数的活动对象,也就是说匿名函数可以访问maxAge中的变量property,即使maxAge函数已经执行返回,但其还在内存中保存着没有释放。这就是闭包,闭包可以访问其他函数中的变量,但是这也就存在一个问题,容易造成内存泄露。
三、块级作用域
在原生js中,没有块级作用域的概念。所有的变量都是共有的,只有在函数中才存在私有变量。其实,js也可以构造出私有作用域来。看下面代码:
function A(){
for(var i = 0; i < 3; i++)
{
alert(i);
}
alert(i);
}
上面这段代码,有一个for循环,但这个循环块里面的i与外面的i其实是一个变量。那如何把for循环块里的 i 和外面的 i 隔离开呢?这就需要用到闭包。
function A(){
(function(){
for(var i = 0; i < 3; i++)
{
alert(i);
}
})();
alert(i); //错误,i没有定义
}
这里使用了一个匿名函数立即执行的方式,所以也不会存在内存一直释放不了的问题。因为这个匿名函数执行结束时匿名函数内部所有的变量都会被释放。
在JS中,没有私有成员的概念,但是有私有变量的概念,比如函数的参数,以及函数作用域中的变量或局部变量。这些私有变量在函数外部是不能访问的,但是如果在函数内部添加一个闭包,这个闭包可以访问该函数中的私有变量,利用这一点,就可以构造一个可以访问函数内私有变量的公有方法。看下面代码:
function Member(){
var salary = 5000;
function Up()
{
salary += 3000;
return salary;
}
this.Method = function(){
alert(salary);
return Up();
}
}
var m = new Member();
m.Method();
通过Member()构造函数创建一个对象,但该对象并不能访问m.salary和m.Up(),只能以m.Method()来访问其私有变量。这个Method方法又叫特权函数。
但上面方法存在与单纯使用构造函数有一个相同的缺点,就是创建多个对象时,会创建多个实质不同但命名相同的代码。其实,只需共享函数的一份拷贝就可以了,所以可以结合原型来做。【需深入了解可参考
上一篇博客】
(function(){
var salary = 5000;
function Up()
{
salary += 3000;
return salary;
}
Member = function(){};
Member.prototype.Method = function(){
alert(salary);
return Up();
}
})();
这样其实实现了所有私有变量和原型方法为实例对象所共享,如下的例子可以看出来:
var a = new Member();
a.Method(); //弹出5000,返回值为8000,变量salary值现在为8000
var b = new Member();
b.Method(); //弹出8000,返回值为11000,变量salary值现在为11000
var c = new Member();
c.Method(); //弹出11000,返回值为14000,变量salary值现在为14000
这样感觉类似与C++类的静态成员变量,可以实现一个共享变量的目的。不知道是不是基于这个原因,JS中这种变量叫静态私有变量。(纯属个人猜测)
还有一种叫模块模式:
这种模式适合单例模式,所谓单例,就是说只有一个实例对象,这种对象一般都是通过对象字面量的方式创建的。
var Member = {
name : value,
method : function(){
}
}
var Member = (function(){
var age = 3;
function Year(){
age++;
return age;
}
return {
method : function(){
return Year();
}
}
})();
这部分个人感觉有点偏工程一些了,没做过大的工程,所以也没有实战经验。不过JS高程中的介绍真的很有深度,值得好好细究。书中表示,在Web应用中,经常会使用一个单例来管理应用程序级的信息,我理解是这样的,可能因为系统级的应用信息在整个系统中是独一无二的,所以应用单例模式来实现。因此,为了访问单例中定义的一些接口(私有变量或这函数),所以引入模式模块。
这种方式得到的单例是没有类型的,所以instanceOf()也是没有意义的。因为它是由Object类型继承来的。如果想要增加有类型的对象,又有人提出了增强的模式模块。这部分还没有真正掌握,所以这部分先部总结,后续加上。