函数表达式是JavaScript中一个很强大又容易令人困惑的特性。
创建函数有两种方式:
// 方式1
function sayHi(){
alert("Hi!");
}
// 方式2
var sayHi = function(){
alert("Hi!");
};
函数有个很重要的特性就是函数声明提升:意思就是说,在执行代码之前会先读取函数的声明。
闭包
闭包指的是有权访问另一个函数作用域的变量的函数。创建闭包的另一个方式,就是在一个函数中创建另一个函数。
function createComparisonFunction(propertyName) {
return function(object1, object2){
var value1 = object1[propertyName];
var value2 = object2[propertyName];
if (value1 < value2){
return -1;
} else if (value1 > value2){
return 1;
} else {
return 0;
}
};
}
// 请注意下面这两行代码:
//var value1 = object1[propertyName];
//var value2 = object2[propertyName];
这段代码中,需要注意的两行代码如上所示,这两行代码访问了外部的函数属性propertyName。即使这个内部的函数被返回了,并且在其他地方被调用了,它仍然可以访问变量propertyName。
1、闭包与变量
注意,闭包只能取得包含函数变量中任何变量的最后一个值。也就是说,闭包保存的是整个变量对象,而不是某个特殊的变量。
下面这段代码很能说明问题:
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i; };
}
return result;
}
createFunctions函数返回后,result中的所有返回值都是10;如果我们想要返回值是期望的0-10,应该如下这样写:
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(num){
return function(){
return num;
};
}(i);
return result;
}
上面这段代码通过创建匿名函数达到了目的。
2、关于this对象
我们知道,this对象是在运行的时候根据函数的执行环境绑定的。在全局函数中,this等于window。当函数被某个对象调用时,this则等于那个对象。但是,匿名函数的执行环境具有全局性,因此其this对象通常指向window。
例如:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
} };
alert(object.getNameFunc()()); //"The Window"
如果在getNameFunc想返回object的name,该如何做呢?只需要在闭包中,将this变量保存下来即可:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"My Object"
3、内存泄漏
由于IE9之前的版本对JS对象和DOM对象使用不同的垃圾收集例程,因此闭包在这些IE浏览器下就会有一些特殊的问题。具体来说就是,如果闭包的作用域链中保存了HTML元素,那么就意味着,该元素不会被销毁。
看下面的例子:
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
上述代码创建了一个作为element元素事件处理的闭包。由于匿名函数保存了一个对assignHandler活动对象的引用,因此就会导致无法减少element元素的引用数,那么它所占用的内存就永远不会被回收。因此,这段代码应该修改为:
function assignHandler(){
var element = document.getElementById("someElement"); var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
模仿块级作用域
JavaScript没有块级作用域的概念,这意味着,在块语句中定义的变量,实际上是包含在函数中,而非语句中的。例如:
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i);
}
这个函数中定义了一个for循环,在java语言中,一旦for循环结束,变量i也会被销毁。但是在js语言中,i变量会在函数中一直存在,不会被销毁。
匿名函数可以模仿块级作用域来避免这个问题。
(function(){
// 这里是块级作用域
})();
以上代码创建了一个匿名函数并立即调用了这个函数。
利用匿名函数,上面的一段代码可以修改为:
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); // 错误
}
这种技术经常用来限制向全局作用域中添加过多的变量和函数。一般来说,我们应该少向全局作用域中添加变量和函数,因为在多人参加的大型项目中,这样很容易导致命名冲突的错误。通过创建私有域,大家不必担心搞乱全局作用域,又可以愉快地使用自己的变量。
私有变量
任何在函数内部定义的变量,可以成为私有变量,因为在函数外部不能访问这些变量。
另外,我们把有权访问函数内部私有变量和私有方法的方法称为特权方法。
function myObject(){
// 私有变量和函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
利用私有和特权成员,可以隐藏那些不可以被直接修改的数据。
1、静态私有变量。
通过在私有作用域中定义私有变量和函数,同样可以创建特权方法:
(function(){
// 私有变量和函数
var privateVariable = 10;
function privateFunction(){
return false;
}
// 构造函数
MyObject = function(){ };
// 公有/特权方法
MyObject.prototype.publicMethod = function(){
privateVariable++;
return privateFunction();
};
})();
需要注意的是,在定义构造函数的时候,我们没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。
上面这种方式定义和在构造函数中定义特权方法的区别在于,私有变量和函数是所有对象共享的:
(function(){
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();
var person1 = new Person("Nicholas"); alert(person1.getName()); //"Nicholas" person1.setName("Greg"); alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"
我们可以发现了:当对象2修改了name变量,对象1也跟着受到影响。
2、模块模式
为单例创建私有变量和方法:(所谓单例,就是只有一个实例的对象)
var singleton = function(){
// 私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权/公有
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();
从本质上讲,这个对象字面量定义的是单例的公共接口。
3、增强的模块模式
单例必须是某种类型,并且还需要添加某些方法和属性的情况。
var singleton = function(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//创建对象
var object = new CustomType();
//添加特权/公有方法
object.publicProperty = true;
object.publicMethod = function(){
privateVariable++;
return privateFunction();
};
//
return object;
}();