这一章我们讨论函数表达式。主要涉及函数表达式的特征、闭包和私有变量等内容。
我们知道创建一个函数有两种方法:
//函数声明
function function1(a,b,c){
......
}
//函数表达式
var function1 = funcition(a,b,c){
......
}
我们将第二种情况下创建的函数叫做匿名函数。
什么是闭包:闭包是有权访问另一个函数作用域中的变量的函数。
创建闭包的常见方式:在一个函数内部创建另一个函数。
function createComparisonFunction(propertyName){
return function(object1,object2){
var value1 = object1[propertyName]
var value2 = object2[propertyName]
if(value1 value2){
return 1;
}
else {
return 0;
}
}
}
在这个例子中,即使这个内部函数在其他的地方被调用,仍然可以访问属性
propertyName,因为内部函数的作用域中包含createComparsionFunction这个函数的作用域。
一般来说,当函数执行完毕后,函数内部的活动对象会被销毁,只留下全局作用域。但是当有闭包时,情况有所不同: 在外部函数执行结束后,他的作用域链会被销毁,但是他的活动对象仍然在内存中。只有匿名函数被销毁后,外部函数才会被销毁。
闭包的副作用:闭包只能取得外部函数中任何变量的最后一个值。
function createFunction(){
var result = new Array()
for(var i=0;i<10;i++){
result[i] = function(){
return i;
}
}
return result;
}
我们希望得到的是一个长度为10,数值分别为0-9的数组,但是实际上循环这么多个的内部函数,每个都是返回10。因为每个闭包函数作用域中都保存着外部函数的活动对象。他们引用的都是同一个i。外部函数返回时,i的值是10,所以每个内部函数的i的值也为10。
那么如何解决这个问题呢:
function createFunction(){
var result = new Array()
for(var i=0;i<10;i++){
result[i] = function(num){
return function(){
return num;
}
}(i)
}
return result;
}
我们又定义了一个匿名函数,并将立即执行该匿名函数的结果赋值给了数组。在这里为什么i是从0-9,因为我们是在最内部定义了一个闭包的匿名函数,用于访问num。而外面一层function(num)是按值传递的。会将i的当前值赋值给参数num。
闭包中this指向的问题:匿名函数的执行具有全局性,因此this对象通常指向window。但因为闭包的编写方式不同,可能会让人产生误解。
var name = 'hjh'
var object = {
name:'lym',
getNameFunc:function(){
return function(){
return this.name
}
}
}
alert(object.getNameFunc()()) //'hjh'
按道理说,我们在object对象中定义了name属性,同样是在object对象中定义的函数,this应该指向object中的name才对,为什么会指向window中的name属性呢?我们之前说过,每个函数调用时,都会取得两个特殊变量:this和argument。内部函数在搜索这两个变量时,只会搜索到活动对象为止。因此不会访问到外部函数中的这两个变量,也无法取到外部函数中的定义的变量值。
那么我们想要取到外部函数中的变量和属性怎么办呢:
var name = 'hjh'
var object = {
name:'lym',
getNameFunc:function(){
var that = this
return function(){
return that.name
}
}
}
alert(object.getNameFunc()()) //'lym'
注意:我们将this赋值给that,此时的this是在object中定义的,指向的也是object。所以我们此时访问的都是外部函数中的变量和属性。
我们之前提到,JavaScript没有块级作用域的概念。比如在函数中定义一个循环,变量i在函数内部任何地方都可以获取。那么有没有办法实现私有作用域呢?
function outputNumber(count){
(function(){
for(var i=0;i<count;i++){
alert(i)
}
})
}
alert(i) //报错
我们在for循环外插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束后被销毁。而在私有作用域中之所以能访问count,是因为这个匿名函数是一个闭包,他能访问外部函数作用域中的变量。
任何在函数中定义的变量,都可以认为是私有变量,因为不能再函数的外部访问这些变量。私有变量包括:函数的参数、局部变量、在函数内部定义的其他函数。
那么怎么在函数外部访问这些变量呢:我们可以创建一个闭包,闭包通过自己的作用域链可以访问这些变量。
我们把有权访问私有变量和私有函数的方法称为特权方法。有两种在对象上创建特权方法的方式:
1.构造函数中的特权方法
function Person(name){
this.getName = function(){
return name
};
this.setName = function(){
name = value
};
}
var person = new Person('nico');
alert(person.getName()) //'nico'
person.setName('lym')
alert(person.getName()) //‘lym'
这其实就是我们在第五章讲的构造函数的方法,缺点很明显**:针对每个实例都会创建一组新方法**。
2.静态私有变量
(function()){
var name = ''
Person = function(value){
name = value
}
Person.prototype.getName = function(){
return name
}
Person.prototype.setName = function(){
name = value
}
})();
var person1 = new Person('nico')
alert(person1.getName()) //'nico'
person1.setName('lym')
alert(person1.getName()) //'lym'
var person2 = new Person('hjh')
alert(person1.getName()) //'hjh'
alert(person2.getName()) //'hjh'
这个例子中,变量name变成一个静态的,由所有实例共享的属性。在一个实例上调用原型上的方法,所有的实例对象都会一起改变,正是和我们之前提到的原型模式相同。
我们刚刚提到的两种方法,都是用于为自定义类型创建私有变量和特权方法的。我们现在要说的模块模式,是为单例创建私有变量和特权方法。所谓单例指的就是只有一个实例的对象。
var singletion = function(){
//私有变量和函数
var privateNum = 10
function privateFunction(){
return false
}
//特权方法
return{
publicMethod:function(){
privateNum ++;
return privateFunction()
}
}
}
这种模式适用于什么情况呢:如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,就可以适用模块模式。