0%

高程笔记3-函数表达式

高程笔记3

函数表达式

1
2
3
4
5
6
函数表达式与其他表达式一样,在使用前必须先赋值。以下代码会导致错误。
sayHi(); //错误:函数还不存在
var sayHi = function(){ //匿名函数
alert("Hi!");
};

递归函数是在一个函数通过名字调用自身的情况下构成的,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * factorial(num-1);
}
}

arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数
的递归调用,例如
function factorial(num){
if (num <= 1){
return 1;
} else {
return num * arguments.callee(num-1);
}
}

闭包

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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;
}
};
}

/*
作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最
后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰地说
明这个问题。
*/
function createFunctions(){
var result = new Array();
for (var i=0; i < 10; i++){
result[i] = function(){
return i;
};
}
return result;
}
/*
这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置 0 的函数
返回 0,位置 1 的函数返回 1,以此类推。但实际上,每个函数都返回 10。因为每个函数的作用域链中
都保存着 createFunctions() 函数的活动对象,所以它们引用的都是同一个变量 i 。 当
createFunctions()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量
对象,所以在每个函数内部 i 的值都是 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;
}

每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。

1
2
3
4
5
6
7
8
9
10
11
12
把外部作用域中的 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"

内存泄漏

1
2
3
4
5
6
7
8
9
10
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;

element.onclick = function(){
alert(id);
};

element = null;
}

模仿块级作用域

1
2
3
4
5
6
7
8
9
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();

alert(i); //导致一个错误!
}

私有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function MyObject(){
//私有变量和私有函数
var privateVariable = 10;
function privateFunction(){
return false;
}
//特权方法
this.publicMethod = function (){
privateVariable++;
return privateFunction();
};
}
这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员
的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的
所有变量和函数。对这个例子而言,变量 privateVariable 和函数 privateFunction()只能通过特
权方法 publicMethod()来访问。在创建 MyObject 的实例后,除了使用 publicMethod()这一个途
径外,没有任何办法可以直接访问 privateVariable 和 privateFunction()。

利用私有和特权成员,可以隐藏那些不应该被直接修改的数据,例如:
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"

静态私有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(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"

模块模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:
var singleton = function(){

//私有变量和私有函数
var privateVariable = 10;

function privateFunction(){
return false;
}
//特权/公有方法和属性
return {
publicProperty: true,
publicMethod : function(){
privateVariable++;
return privateFunction();
}
};
}();

/*
增强的模块模式
*/
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;
}();

总结

在 JavaScript 编程中,函数表达式是一种非常有用的技术。使用函数表达式可以无须对函数命名,
从而实现动态编程。匿名函数,也称为拉姆达函数,是一种使用 JavaScript 函数的强大方式。以下总结
了函数表达式的特点。
 函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表
达式也叫做匿名函数。
 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂;
 递归函数应该始终使用 arguments.callee 来递归地调用自身,不要使用函数名——函数名可
能会发生变化。
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理
如下。
 在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。
 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
 但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。
使用闭包可以在 JavaScript 中模仿块级作用域(JavaScript 本身没有块级作用域的概念),要点如下。
 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
 结果就是函数内部的所有变量都会被立即销毁——除非将某些变量赋值给了包含作用域(即外
部作用域)中的变量。
闭包还可以用于在对象中创建私有变量,相关概念和要点如下。
 即使 JavaScript 中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公
有方法可以访问在包含作用域中定义的变量。
 有权访问私有变量的公有方法叫做特权方法。
 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强
的模块模式来实现单例的特权方法。
JavaScript 中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。不过,因为
创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。