sayHello(); // 结果是 "Hello"
sayHello("Alex"); // 结果是 "Hello, Alex"
sayHello("Alex", "cn"); // 结果是 " 你好,Alex"
arguments.callee
callee 是 arguments 对象的一个属性,其值是当前正在执行的 function 对象。它的作用是使得匿名 function 可以被递归调用。下面以一段计算斐波那契序列(Fibonacci sequence)中第 N 个数的值的代码来演示 arguments.callee 的使用,见代码清单 3。
function fibonacci(num) {
return (function(num) {
if (typeof num !== "number") return -1;
num = parseInt(num);
if (num < 1) return -1;
if (num == 1 || num == 2) return 1;
return arguments.callee(num - 1) + arguments.callee(num - 2);
})(num);
}
fibonacci(100);
prototype 与继承
JavaScript 中的每个对象都有一个 prototype 属性,指向另外一个对象。使用对象字面量创建的对象的 prototype 指向的是Object.prototype,如var obj = {"name" : "Alex"};中创建的对象obj的 prototype 指向的就是Object.prototype。而使用 new 操作符创建的对象的 prototype 指向的是其构造器的 prototype 。如var users = new Array();中创建的对象users的 prototype 指向的是Array.prototype。由于一个对象 A 的 prototype 指向的是另外一个对象 B,而对象 B 自己的 prototype 又指向另外一个对象 C,这样就形成了一个链条,称为 prototype 链。这个链条会不断继续,一直到Object.prototype。Object.prototype对象的 prototype 值为 null,从而使得该链条终止。图 1中给出了 prototype 链的示意图。
图 1. JavaScript prototype 链示意图
在图 1中,studentA是通过 new 操作符创建的,因此它的 prototype 指向其构造器Student的 prototype ;Student.prototype的值是通过 new 操作符创建的,其 prototype 指向构造器Person的 prototype 。studentA的 prototype 链在图 1中用虚线表示。
prototype 链在属性查找过程中会起作用。当在一个对象中查找某个特定名称的属性时,会首先检查该对象本身。如果找到的话,就返回该属性的值;如果找不到的话,会检查该对象的 prototype 指向的对象。如此下去直到找到该属性,或是当前对象的 prototype 为 null 。 prototype 链在设置属性的值时并不起作用。当设置一个对象中某个属性的值的时候,如果当前对象中存在这个属性,则更新其值;否则就在当前对象中创建该属性。
JavaScript 中并没有 Java 或 C++ 中类(class)的概念,而是通过 prototype 链来实现基于 prototype 的继承。在 Java 中,状态包含在对象实例中,方法包含在类中,继承只发生在结构和行为上。而在 JavaScript 中,状态和方法都包含在对象中,结构、行为和状态都是被继承的。这里需要注意的是 JavaScript 中的状态也是被继承的,也就是说,在构造器的 prototype 中的属性是被所有的实例共享的。如代码清单 4中所示。
清单 4. JavaScript 中状态被继承的示例
function Student(name) {
this.name = name;
}
Student.prototype.selectedCourses = [];
Student.prototype.addCourse = function(course) {
this.selectedCourses.push(course);
}
Student.prototype.outputCourses = function() {
alert(this.name + " 选修的课程是:" + this.selectedCourses.join(","));
}
var studentA = new Student("Alex");
var studentB = new Student("Bob");
studentA.addCourse(" 算法分析与设计 ");
studentB.addCourse(" 数据库原理 ");
studentA.outputCourses(); // 输出是“ Alex 选修的课程是算法分析与设计 , 数据库原理”
studentB.outputCourses(); // 输出同上
代码清单 4中的问题在于将selectedCourses作为 prototype 的属性之后,studentA和studentB两个实例共享了该属性,它们操作的实际是同样的数据。
this
JavaScript 中的 this 一直是容易让人误用的,尤其对于熟悉 Java 的程序员来说,因为 JavaScript 中的 this 与 Java 中的 this 有很大不同。在一个 function 的执行过程中,如果变量的前面加上了 this 作为前缀的话,如this.myVal,对此变量的求值就从 this 所表示的对象开始。
this 的值取决于 function 被调用的方式,一共有四种,具体如下:
如果一个 function 是一个对象的属性,该 funtion 被调用的时候,this 的值是这个对象。如果 function 调用的表达式包含句点(.)或是 [],this 的值是句点(.)或是 [] 之前的对象。如myObj.func和myObj["func"]中,func被调用时的 this 是myObj。
如果一个 function 不是作为一个对象的属性,那么该 function 被调用的时候,this 的值是全局对象。当一个 function 中包含内部 function 的时候,如果不理解 this 的正确含义,很容易造成错误。这是由于内部 function 的 this 值与它外部的 function 的 this 值是不一样的。代码清单 5中,在myObj的func中有个内部名为inner的 function,在inner被调用的时候,this 的值是全局对象,因此找不到名为myVal的变量。这个时候通常的解决办法是将外部 function 的 this 值保存在一个变量中(此处为self),在内部 function 中使用它来查找变量。
如果在一个 function 之前使用 new 的话,会创建一个新的对象,该 funtion 也会被调用,而 this 的值是新创建的那个对象。如function User(name) {this.name = name}; var user1 = new User("Alex");中,通过调用new User("Alex"),会创建一个新的对象,以user1来引用,User这个 function 也会被调用,会在user1这个对象中设置名为name的属性,其值是Alex。
可以通过 function 的 apply 和 call 方法来指定它被调用的时候的 this 的值。 apply 和 call 的第一个参数都是要指定的 this 的值,两者不同的是调用的实际参数在 apply 中是以数组的形式作为第二个参数传入的,而 call 中除了第一个参数之外的其它参数都是调用的实际参数。如func.apply(anotherObj, [arg1, arg2])中,func调用时候的 this 指的是anotherObj,两个参数分别是arg1和arg2。同样的功能用 call 来写则是func.call(anotherObj, arg1, arg2)。
清单 5. 内部 function 的 this 值
var myObj = {
myVal : "Hello World",
func : function() {
alert(typeof this.myVal); // 结果为 string
var self = this;
function inner() {
alert(typeof this.myVal); // 结果为 undefined
alert(typeof self.myVal); // 结果为 string
}
inner();
}
};
myObj.func();