ECMAScript学习笔记(八)——函数表达式

Posted by Csming on 2018-09-16

JavaScript高级程序设计的第五章: 函数表达式

递归

在ECMAScript中,通过名字进行的递归,会由于函数被赋值到其他变量上,而导致错误:

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

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial); // error

于是,应该这样解决:

1
2
3
4
5
6
7
function factorial(num) {
if(num <= 1) {
return 1;
} else {
return num * arguments.callee(num - 1);
}
}

函数的arguments的callee对象指向了正在执行的函数的指针。


但是在严格模式下,是不能使用callee对象的。于是,可以使用明明函数表达式来达成相同的结果:

1
2
3
4
5
6
7
var factorial = (function f(num) {
if(num <= 1) {
return 1;
} else {
return f(num - 1);
}
})

上述代码,创建了一个名为f()的明明函数表达式。然后将它赋值给了factorial。这样一来,就算把函数赋值给了另一个变量,函数的名字f依然有效。

闭包

闭包是指,有权访问另一个函数作用域中的变量的函数。

创建闭包的常用方式,就是在一个函数内部创建另一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createComparisonFunction(propertyName) {
return function(object1, object2) {

// 此处的两行代码,访问了外部函数的propertyName变量
var value1 = object1[propertyName];
var value2 = object2[propertyName];

if(value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
}

上述的:

1
2
var value1 = object1[propertyName];
var value2 = object2[propertyName];

这两行代码,访问来外部函数的propertyName变量。并且,即时这个内部函数被返回了,并且被其他地方调用了,它依旧可以访问propertyName变量。

这是因为,内部函数的作用域链中包含了createComparisonFunction()的作用域。

闭包与变量

闭包,只能取得包含函数中任何变量的最后一个值。意思就是说,闭包中获取的外部函数的变量的值,应该就它的最新的值。

1
2
3
4
5
6
7
8
9
10
11
function createFunction() {
var result = new Array();

for(var i = 0; i < 10; i++) {
result[i] = function() {
return i;
}
}

return result;
}

例如,以上代码中,每个函数都会返回10。因为,上述createFunction的活动对象引用的i是同一个变量。

我们可以这样做,来强制让闭包的行为符合预期:

1
2
3
4
5
6
7
8
9
10
11
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对象

this对象是在运行时基于函数的执行环境绑定的。

匿名函数的执行环境具有全局性。其this对象通常指向window。但有时候由于编写闭包的方式不同,可能会不明显:

1
2
3
4
5
6
7
8
9
10
11
12
13
var name = "The Window";

var object = {
name: "My Object",

getNamePunc: function() {
return function() {
return this.name;
};
}
};

alert(object.getNameFunc()); // "The Window"

以我的理解来看,object.getNameFunc()只是返回了内部的匿名函数,而这个匿名函数真正的调用,应该是在全局环境中,而非object下的。

不过,可以这样做,让闭包访问这个对象:将外部作用域中的this对象保存在一个闭包能够访问的变量里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var name = "The Window";

var object = {
name: "My Object",

getNamePunc: function() {
var that = this
return function() {
return that.name;
};
}
};

alert(object.getNameFunc()); // "The Window"

模仿块级作用域

js是没有块级作用域的。嗯。在块语句中定义的变量,实际上是包含在函数中而非语句中创建的。

可以用匿名函数模仿块级作用域:

1
2
3
(function () {
//
})();

以上代码定义并调用了一个匿名函数。

可以理解为:

1
2
3
4
var someFunction = function() {

};
someFunction();

这种方式,经常被用在函数外部,用于限制向全局作用域中添加过多的变量和函数。

私有变量

在函数中定义的变量,都可以认为是私有变量。

私有变量包括函数的参数、局部变量和函数内部定义的其他函数。

eg:

1
2
3
4
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}

上述代码包含了三个私有变量:num1、num2、sum;

可以在这个函数的内部创建一个闭包,这个闭包通过自己的作用域链也可以访问到这些变量。利用这点,就可以创建用于访问私有变量的公共方法了。

1
2
3
4
5
6
7
8
9
10
11
12
function MyObject() {
var privateVariable = 10;

function privateFunction() {
return false;
}

this.publicMethod = function() {
privateVariable ++;
return privateFunction();
}
}

这种有权访问私有变量和私有函数的公共方法被称为特权方法。

利用私有和特权成员,可以隐藏安歇不应该被直接修改的数据:

1
2
3
4
5
6
7
8
9
function Person(name) {
this.getName = function() {
return name;
};

this.setName = function(value) {
name = value;
};
}

静态私有变量

在私有作用域中定义私有变量或函数,同样可以创建特权方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(function(){
var privateVariable = 10;

function privateFunction() {
return false;
}

// 没有使用var,故MyObject为全局变量
MyObject = function() {

};

MyObject.prototype.publicMethod = function() {
privateVariable++;
return privateFunction();
}
})();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(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"

var person2 = new Person("Michael");
alert(person1.getName()); // "Michael"
alert(person2.getName()); // "Michael"

模块模式

模块模式,是为单例创建私有变量和特权方法的模式。

单例:指的是只有一个实例的对象。

eg: js通过对象字面量创建单例

1
2
3
4
5
6
var singleton = {
name: value,
method: function(){

}
};

模块模式为单例添加私有变量和特权方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var singleton = function() {
var privateVariable = 10;

function privateFunction() {
return false;
}

return {
publicProperty: true,

publicMethod: function() {
privateVariable++;
return privateFunction();
}
}
}();

增强的模块模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}