ECMAScript关键字
delete
do
else
finally
function
in
instanceof
this
throw
try
typeof
var
with
保留字
abstract
debugger
extends
final
goto
implements
native
package
synchronized
throws
transient
volatile
ECMAScript有5种原始类型(primitive type),即Undefined、Null、Boolean、Number和String。ECMA-262把术语类型(type)定义为值的一个集合,每种原始类型定义了它包含的值的范围及其字面量表示形式。
原始值
存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
引用值
存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存处。
typeof运算符
typeof运算符有一个参数,即要检查的变量或值。例如:
var sTemp = "test string";
alert (typeof sTemp);//输出"string"
alert (typeof 86);//输出"number"
对变量或值调用typeof运算符将返回下列值之一:
undefined -如果变量是Undefined类型的
boolean -如果变量是Boolean类型的
number -如果变量是Number类型的
string -如果变量是String类型的
object -如果变量是一种引用类型或Null类型的
Object对象具有下列属性:
constructor
对创建对象的函数的引用(指针)。对于Object对象,该指针指向原始的Object()函数。
Prototype
对该对象的对象原型的引用。对于所有的对象,它默认返回Object对象的一个实例。
Object对象还具有几个方法:
hasOwnProperty(property)
判断对象是否有某个特定的属性。必须用字符串指定该属性。(例如,o.hasOwnProperty("name"))
IsPrototypeOf(object)
判断该对象是否为另一个对象的原型。
PropertyIsEnumerable
判断给定的属性是否可以用for...in语句进行枚举。
ToString()
返回对象的原始字符串表示。对于Object对象,ECMA-262没有定义这个值,所以不同的ECMAScript实现具有不同的值。
ValueOf()
返回最适合该对象的原始值。对于许多对象,该方法返回的值都与ToString()的返回值相同。
注释:上面列出的每种属性和方法都会被其他对象覆盖。
instanceof运算符
在使用typeof运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回"object"。ECMAScript引入了另一个Java运算符instanceof来解决这个问题。
instanceof运算符与typeof运算符相似,用于识别正在处理的对象的类型。与typeof方法不同的是,instanceof方法要求开发者明确地确认对象为某特定类型。例如:
var oStringObject = new String("hello world");
alert(oStringObject instanceof String);//输出"true"
这段代码问的是“变量oStringObject是否为String对象的实例?”oStringObject的确是String对象的实例,因此结果是"true"。尽管不像typeof方法那样灵活,但是在typeof方法返回"object"的情况下,instanceof方法还是很有用的。
全等号由三个等号表示(===),只有在无需类型转换运算数就相等的情况下,才返回true。
例如:
var sNum = "66";
var iNum = 66;
alert(sNum == iNum);//输出"true"
alert(sNum === iNum);//输出"false"
var sNum = "66";
var iNum = 66;
alert(sNum != iNum);//输出"false"
alert(sNum !== iNum);//输出"true"
for (sProp in window) {
alert(sProp);
}
start : i = 5;
在这个例子中,标签start可以被之后的break或continue语句引用。
with语句用于设置代码在特定对象中的作用域。
它的语法:
with (expression)statement
例如:
var sMessage = "hello";
with(sMessage) {
alert(toUpperCase());//输出"HELLO"
}
arguments对象
在函数代码中,使用特殊对象arguments,开发者无需明确指出参数名,就能访问它们。
function sayHi() {
if (arguments[0] == "bye") {
return;
}
alert(arguments[0]);
}
检测参数个数
还可以用arguments对象检测函数的参数个数,引用属性arguments.length即可。
下面的代码将输出每次调用函数使用的参数个数:
function howManyArgs() {
alert(arguments.length);
}
howManyArgs("string", 45);
howManyArgs();
howManyArgs(12);
上面这段代码将依次显示"2"、"0"和"1"。
模拟函数重载
用arguments对象判断传递给函数的参数个数,即可模拟函数重载:
function doAdd() {
if(arguments.length == 1) {
alert(arguments[0] + 5);
} else if(arguments.length == 2) {
alert(arguments[0] + arguments[1]);
}
}
doAdd(10);//输出"15"
doAdd(40, 20);//输出"60"
Function对象(类)
ECMAScript最令人感兴趣的可能莫过于函数实际上是功能完整的对象。
Function类可以表示开发者定义的任何函数。
用Function类直接创建函数的语法如下:
var function_name = new function(arg1,arg2, ...,argN,function_body)
function sayHi(sName, sMessage) {
alert("Hello " + sName + sMessage);
}
还可以这样定义它:
var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");
function doAdd(iNum) {
alert(iNum + 20);
}
function doAdd(iNum) {
alert(iNum + 10);
}
doAdd(10);//输出"20"
如你所知,第二个函数重载了第一个函数,使doAdd(10)输出了"20",而不是"30"。
如果以下面的形式重写该代码块,这个概念就清楚了:
var doAdd = new Function("iNum", "alert(iNum + 20)");
var doAdd = new Function("iNum", "alert(iNum + 10)");
doAdd(10);
请观察这段代码,很显然,doAdd的值被改成了指向不同对象的指针。函数名只是指向函数对象的引用值,行为就像其他对象一样。甚至可以使两个变量指向同一个函数:
var doAdd = new Function("iNum", "alert(iNum + 10)");
var alsodoAdd = doAdd;
doAdd(10);//输出"20"
alsodoAdd(10);//输出"20"
function callAnotherFunc(fnFunction, vArgument) {
fnFunction(vArgument);
}
var doAdd = new Function("iNum", "alert(iNum + 10)");
callAnotherFunc(doAdd, 10);//输出"20"
Function对象的length属性
如前所述,函数属于引用类型,所以它们也有属性和方法。
ECMAScript定义的属性length声明了函数期望的参数个数。例如:
function doAdd(iNum) {
alert(iNum + 10);
}
function sayHi() {
alert("Hi");
}
alert(doAdd.length);//输出"1"
alert(sayHi.length);//输出"0"
函数doAdd()定义了一个参数,因此它的length是1;sayHi()没有定义参数,所以length是0。
记住,无论定义了几个参数,ECMAScript可以接受任意多个参数(最多25个),这一点在《函数概述》这一章中讲解过。属性length只是为查看默认情况下预期的参数个数提供了一种简便方式。
Function对象的方法
Function对象也有与所有对象共享的valueOf()方法和toString()方法。这两个方法返回的都是函数的源代码,在调试时尤其有用。例如:
function doAdd(iNum) {
alert(iNum + 10);
}
document.write(doAdd.toString());//function doAdd(iNum) { alert(iNum + 10); }
闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
简单的闭包实例
在ECMAScript中使用全局变量是一个简单的闭包实例。请思考下面这段代码:
var sMessage = "hello world";
function sayHelloWorld() {
alert(sMessage);
}
sayHelloWorld();
复杂的闭包实例
在一个函数中定义另一个会使闭包变得更加复杂。例如:
var iBaseNum = 10;
function addNum(iNum1, iNum2) {
function doAdd() {
return iNum1 + iNum2 + iBaseNum;
}
return doAdd();
}
面向对象语言的要求
一种面向对象语言需要向开发者提供四种基本能力:
封装-把相关的信息(无论数据或方法)存储在对象中的能力
聚集-把一个对象存储在另一个对象内的能力
继承-由另一个类(或多个类)得来类的属性和方法的能力
多态-编写能以多种方法运行的函数或方法的能力
对象的构成
在ECMAScript中,对象由特性(attribute)构成,特性可以是原始值,也可以是引用值。如果特性存放的是函数,它将被看作对象的方法(method),否则该特性被看作对象的属性(property)。
声明和实例化
对象的创建方式是用关键字new后面跟上实例化的类的名字:
var oObject = new Object();
var oStringObject = new String();
第一行代码创建了Object类的一个实例,并把它存储到变量oObject中。第二行代码创建了String类的一个实例,把它存储在变量oStringObject中。如果构造函数无参数,括号则不是必需的。因此可以采用下面的形式重写上面的两行代码:
var oObject = new Object;
var oStringObject = new String;
对象废除
ECMAScript拥有无用存储单元收集程序(garbage collection routine),意味着不必专门销毁对象来释放内存。当再没有对对象的引用时,称该对象被废除(dereference)了。运行无用存储单元收集程序时,所有废除的对象都被销毁。每当函数执行完它的代码,无用存储单元收集程序都会运行,释放所有的局部变量,还有在一些其他不可预知的情况下,无用存储单元收集程序也会运行。
把对象的所有引用都设置为null,可以强制性地废除对象。例如:
var oObject = new Object;
// do something with the object here
oObject = null;
早绑定和晚绑定
所谓绑定(binding),即把对象的接口与对象实例结合在一起的方法。
早绑定(early binding)是指在实例化对象之前定义它的属性和方法,这样编译器或解释程序就能够提前转换机器代码。在Java和Visual Basic这样的语言中,有了早绑定,就可以在开发环境中使用IntelliSense(即给开发者提供对象中属性和方法列表的功能)。ECMAScript不是强类型语言,所以不支持早绑定。
另一方面,晚绑定(late binding)指的是编译器或解释程序在运行前,不知道对象的类型。使用晚绑定,无需检查对象的类型,只需检查对象是否支持属性和方法即可。ECMAScript中的所有变量都采用晚绑定方法。这样就允许执行大量的对象操作,而无任何惩罚。
--------------------------------
一般来说,可以创建并使用的对象有三种:本地对象、内置对象和宿主对象。
本地对象
ECMA-262把本地对象(native object)定义为“独立于宿主环境的ECMAScript实现提供的对象”。简单来说,本地对象就是ECMA-262定义的类(引用类型)。它们包括:
Object
Function
Array
String
Boolean
Number
Date
RegExp
Error
EvalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
--------------
内置对象
ECMA-262把内置对象(built-in object)定义为“由ECMAScript实现提供的、独立于宿主环境的所有对象,在ECMAScript程序开始执行时出现”。这意味着开发者不必明确实例化内置对象,它已被实例化了。ECMA-262只定义了两个内置对象,即Global和Math(它们也是本地对象,根据定义,每个内置对象都是本地对象)。
相关页面
JavaScript参考手册:Global对象
JavaScript参考手册:Math对象
--------------------
宿主对象
所有非本地对象都是宿主对象(host object),即由ECMAScript实现的宿主环境提供的对象。
所有BOM和DOM对象都是宿主对象。
相关页面
JavaScript高级教程:JavaScript实现
W3School参考手册:JavaScript参考手册
W3School教程:HTML DOM教程
-------------------------------
作用域指的是变量的适用范围。
公用、私有和受保护作用域
概念
在传统的面向对象程序设计中,主要关注于公用和私有作用域。公用作用域中的对象属性可以从对象外部访问,即开发者创建对象的实例后,就可使用它的公用属性。而私有作用域中的属性只能在对象内部访问,即对于外部世界来说,这些属性并不存在。这意味着如果类定义了私有属性和方法,则它的子类也不能访问这些属性和方法。
受保护作用域也是用于定义私有的属性和方法,只是这些属性和方法还能被其子类访问。
ECMAScript只有公用作用域
对ECMAScript讨论上面这些作用域几乎毫无意义,因为ECMAScript中只存在一种作用域-公用作用域。ECMAScript中的所有对象的所有属性和方法都是公用的。因此,定义自己的类和对象时,必须格外小心。记住,所有属性和方法默认都是公用的!
建议性的解决方法
许多开发者都在网上提出了有效的属性作用域模式,解决了ECMAScript的这种问题。
由于缺少私有作用域,开发者确定了一个规约,说明哪些属性和方法应该被看做私有的。这种规约规定在属性前后加下划线:
obj._color_ = "blue";
这段代码中,属性color是私有的。注意,下划线并不改变属性是公用属性的事实,它只是告诉其他开发者,应该把该属性看作私有的。
有些开发者还喜欢用单下划线说明私有成员,例如:obj._color。
静态作用域
静态作用域定义的属性和方法任何时候都能从同一位置访问。在Java中,类可具有属性和方法,无需实例化该类的对象,即可访问这些属性和方法,例如java.net.URLEncoder类,它的函数encode()就是静态方法。
ECMAScript没有静态作用域
严格来说,ECMAScript并没有静态作用域。不过,它可以给构造函数提供属性和方法。还记得吗,构造函数只是函数。函数是对象,对象可以有属性和方法。例如:
function sayHello() {
alert("hello");
}
sayHello.alternate = function() {
alert("hi");
}
sayHello();//输出"hello"
sayHello.alternate();//输出"hi"
-------------------------------------
this的功能
在ECMAScript中,要掌握的最重要的概念之一是关键字this的用法,它用在对象的方法中。关键字this总是指向调用该方法的对象,例如:
var oCar = new Object;
oCar.color = "red";
oCar.showColor = function() {
alert(this.color);
};
oCar.showColor();//输出"red"
---------------------------------
使用this的原因
为什么使用this呢?因为在实例化对象时,总是不能确定开发者会使用什么样的变量名。使用this,即可在任何多个地方重用同一个函数。请思考下面的例子:
function showColor() {
alert(this.color);
};
var oCar1 = new Object;
oCar1.color = "red";
oCar1.showColor = showColor;
var oCar2 = new Object;
oCar2.color = "blue";
oCar2.showColor = showColor;
oCar1.showColor();//输出"red"
oCar2.showColor();//输出"blue"
例如,函数createCar()可用于封装前面列出的创建car对象的操作:
function createCar() {
var oTempCar = new Object;
oTempCar.color = "blue";
oTempCar.doors = 4;
oTempCar.mpg = 25;
oTempCar.showColor = function() {
alert(this.color);
};
return oTempCar;
}
var oCar1 = createCar();
var oCar2 = createCar();
------------------------------------
为函数传递参数
我们还可以修改createCar()函数,给它传递各个属性的默认值,而不是简单地赋予属性默认值:
function createCar(sColor,iDoors,iMpg) {
var oTempCar = new Object;
oTempCar.color = sColor;
oTempCar.doors = iDoors;
oTempCar.mpg = iMpg;
oTempCar.showColor = function() {
alert(this.color);
};
return oTempCar;
}
var oCar1 = createCar("red",4,23);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();//输出"red"
oCar2.showColor();//输出"blue"
给createCar()函数加上参数,即可为要创建的car对象的color、doors和mpg属性赋值。这使两个对象具有相同的属性,却有不同的属性值。
在工厂函数外定义对象的方法
虽然ECMAScript越来越正式化,但创建对象的方法却被置之不理,且其规范化至今还遭人反对。一部分是语义上的原因(它看起来不像使用带有构造函数new运算符那么正规),一部分是功能上的原因。功能原因在于用这种方式必须创建对象的方法。前面的例子中,每次调用函数createCar(),都要创建新函数showColor(),意味着每个对象都有自己的showColor()版本。而事实上,每个对象都共享同一个函数。
有些开发者在工厂函数外定义对象的方法,然后通过属性指向该方法,从而避免这个问题:
function showColor() {
alert(this.color);
}
function createCar(sColor,iDoors,iMpg) {
var oTempCar = new Object;
oTempCar.color = sColor;
oTempCar.doors = iDoors;
oTempCar.mpg = iMpg;
oTempCar.showColor = showColor;
return oTempCar;
}
var oCar1 = createCar("red",4,23);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();//输出"red"
oCar2.showColor();//输出"blue"
-------------------------------------------
构造函数方式
创建构造函数就像创建工厂函数一样容易。第一步选择类名,即构造函数的名字。根据惯例,这个名字的首字母大写,以使它与首字母通常是小写的变量名分开。除了这点不同,构造函数看起来很像工厂函数。请考虑下面的例子:
function Car(sColor,iDoors,iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.showColor = function() {
alert(this.color);
};
}
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
下面为您解释上面的代码与工厂方式的差别。首先在构造函数内没有创建对象,而是使用this关键字。使用new运算符构造函数时,在执行第一行代码前先创建一个对象,只有用this才能访问该对象。然后可以直接赋予this属性,默认情况下是构造函数的返回值(不必明确使用return运算符)。
现在,用new运算符和类名Car创建对象,就更像ECMAScript中一般对象的创建方式了。
你也许会问,这种方式在管理函数方面是否存在于前一种方式相同的问题呢?是的。
-------------------------------------------
原型方式
该方式利用了对象的prototype属性,可以把它看成创建新对象所依赖的原型。
这里,首先用空构造函数来设置类名。然后所有的属性和方法都被直接赋予prototype属性。我们重写了前面的例子,代码如下:
function Car() {
}
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();
alert(oCar1 instanceof Car);//输出"true"
-------------------------------------
原型方式的问题
原型方式看起来是个不错的解决方案。遗憾的是,它并不尽如人意。
首先,这个构造函数没有参数。使用原型方式,不能通过给构造函数传递参数来初始化属性的值,因为Car1和Car2的color属性都等于"blue",doors属性都等于4,mpg属性都等于25。这意味着必须在对象创建后才能改变属性的默认值,这点很令人讨厌,但还没完。真正的问题出现在属性指向的是对象,而不是函数时。函数共享不会造成问题,但对象却很少被多个实例共享。请思考下面的例子:
function Car() {
}
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.drivers = new Array("Mike","John");
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();
oCar1.drivers.push("Bill");
alert(oCar1.drivers);//输出"Mike,John,Bill"
alert(oCar2.drivers);//输出"Mike,John,Bill"
-------------------------------------------
混合的构造函数/原型方式
联合使用构造函数和原型方式,就可像用其他程序设计语言一样创建对象。这种概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。
我们重写了前面的例子,代码如下:
function Car(sColor,iDoors,iMpg) {
this.color = sColor;
this.doors = iDoors;
this.mpg = iMpg;
this.drivers = new Array("Mike","John");
}
Car.prototype.showColor = function() {
alert(this.color);
};
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
oCar1.drivers.push("Bill");
alert(oCar1.drivers);//输出"Mike,John,Bill"
alert(oCar2.drivers);//输出"Mike,John"
--------------------------------
function StringBuffer () {
this._strings_ = new Array();
}
StringBuffer.prototype.append = function(str) {
this._strings_.push(str);
};
StringBuffer.prototype.toString = function() {
return this._strings_.join("");
};
这段代码首先要注意的是strings属性,本意是私有属性。它只有两个方法,即append()和toString()方法。append()方法有一个参数,它把该参数附加到字符串数组中,toString()方法调用数组的join方法,返回真正连接成的字符串。要用StringBuffer对象连接一组字符串,可以用下面的代码:
var buffer = new StringBuffer ();
buffer.append("hello ");
buffer.append("world");
var result = buffer.toString();
-----------------------------
通过使用ECMAScript,不仅可以创建对象,还可以修改已有对象的行为。
prototype属性不仅可以定义构造函数的属性和方法,还可以为本地对象添加属性和方法。
创建新方法
通过已有的方法创建新方法
可以用prototype属性为任何已有的类定义新方法,就像处理自己的类一样。例如,还记得Number类的toString()方法吗?如果给它传递参数16,它将输出十六进制的字符串。如果这个方法的参数是2,那么它将输出二进制的字符串。我们可以创建一个方法,可以把数字对象直接转换为十六进制字符串。创建这个方法非常简单:
Number.prototype.toHexString = function() {
return this.toString(16);
};
在此环境中,关键字this指向Number的实例,因此可完全访问Number的所有方法。有了这段代码,可实现下面的操作:
var iNum = 15;
alert(iNum.toHexString());//输出"F"
---------------------------
重命名已有方法
我们还可以为已有的方法命名更易懂的名称。例如,可以给Array类添加两个方法enqueue()和dequeue(),只让它们反复调用已有的push()和shift()方法即可:
Array.prototype.enqueue = function(vItem) {
this.push(vItem);
};
Array.prototype.dequeue = function() {
return this.shift();
};
----------------------------
添加与已有方法无关的方法
当然,还可以添加与已有方法无关的方法。例如,假设要判断某个项在数组中的位置,没有本地方法可以做这种事情。我们可以轻松地创建下面的方法:
Array.prototype.indexOf = function (vItem) {
for (var i=0; i
if (vItem == this[i]) {
return i;
}
}
return -1;
}
var aColors = new Array("red","green","blue");
alert(aColors.indexOf("green"));//输出"1"
---------------------------
为本地对象添加新方法
最后,如果想给ECMAScript中每个本地对象添加新方法,必须在Object对象的prototype属性上定义它。前面的章节我们讲过,所有本地对象都继承了Object对象,所以对Object对象做任何改变,都会反应在所有本地对象上。例如,如果想添加一个用警告输出对象的当前值的方法,可以采用下面的代码:
Object.prototype.showValue = function () {
alert(this.valueOf());
};
var str = "hello";
var iNum = 25;
str.showValue();//输出"hello"
iNum.showValue();//输出"25"
-----------------------------
重定义已有方法
就像能给已有的类定义新方法一样,也可重定义已有的方法。如前面的章节所述,函数名只是指向函数的指针,因此可以轻松地指向其他函数。如果修改了本地方法,如toString(),会出现什么情况呢?
Function.prototype.toString = function() {
return "Function code hidden";
}
前面的代码完全合法,运行结果完全符合预期:
function sayHi() {
alert("hi");
}
alert(sayHi.toString());//输出"Function code hidden"
--------------------------------
Function.prototype.originalToString = Function.prototype.toString;
Function.prototype.toString = function() {
if (this.originalToString().length > 100) {
return "Function too long to display.";
} else {
return this.originalToString();
}
};
function sayHi() {
alert("hi");
}
document.write(sayHi.toString());
//
function sayHi() { alert("hi"); }
---------------------------------
其原理如下:构造函数使用this关键字给所有属性和方法赋值(即采用类声明的构造函数方式)。因为构造函数只是一个函数,所以可使ClassA构造函数成为ClassB的方法,然后调用它。ClassB就会收到ClassA的构造函数中定义的属性和方法。例如,用下面的方式定义ClassA和ClassB:
function ClassA(sColor) {
this.color = sColor;
this.sayColor = function () {
alert(this.color);
};
}
function ClassB(sColor) {
}
还记得吗?关键字this引用的是构造函数当前创建的对象。不过在这个方法中,this指向的所属的对象。这个原理是把ClassA作为常规函数来建立继承机制,而不是作为构造函数。如下使用构造函数ClassB可以实现继承机制:
function ClassB(sColor) {
this.newMethod = ClassA;
this.newMethod(sColor);
delete this.newMethod;
}
在这段代码中,为ClassA赋予了方法newMethod(请记住,函数名只是指向它的指针)。然后调用该方法,传递给它的是ClassB构造函数的参数sColor。最后一行代码删除了对ClassA的引用,这样以后就不能再调用它。
所有新属性和新方法都必须在删除了新方法的代码行后定义。否则,可能会覆盖超类的相关属性和方法:
function ClassB(sColor, sName) {
this.newMethod = ClassA;
this.newMethod(sColor);
delete this.newMethod;
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
为证明前面的代码有效,可以运行下面的例子:
var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();//输出"blue"
objB.sayColor();//输出"red"
objB.sayName();//输出"John"
------------------------
对象冒充可以实现多重继承
有趣的是,对象冒充可以支持多重继承。也就是说,一个类可以继承多个超类。用UML表示的多重继承机制如下图所示:
例如,如果存在两个类ClassX和ClassY,ClassZ想继承这两个类,可以使用下面的代码:
function ClassZ() {
this.newMethod = ClassX;
this.newMethod();
delete this.newMethod;
this.newMethod = ClassY;
this.newMethod();
delete this.newMethod;
}
----------------------------------
call()方法
call()方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作this的对象。其他参数都直接传递给函数自身。例如:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.call(obj, "The color is ", "a very nice color indeed.");
在这个例子中,函数sayColor()在对象外定义,即使它不属于任何对象,也可以引用关键字this。对象obj的color属性等于blue。调用call()方法时,第一个参数是obj,说明应该赋予sayColor()函数中的this关键字值是obj。第二个和第三个参数是字符串。它们与sayColor()函数中的参数sPrefix和sSuffix匹配,最后生成的消息"The color is blue, a very nice color indeed."将被显示出来。
要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.call(this, sColor);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
------------------------
apply()方法
apply()方法有两个参数,用作this的对象和要传递给函数的参数的数组。例如:
function sayColor(sPrefix,sSuffix) {
alert(sPrefix + this.color + sSuffix);
};
var obj = new Object();
obj.color = "blue";
sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
这个例子与前面的例子相同,只是现在调用的是apply()方法。调用apply()方法时,第一个参数仍是obj,说明应该赋予sayColor()函数中的this关键字值是obj。第二个参数是由两个字符串构成的数组,与sayColor()函数中的参数sPrefix和sSuffix匹配,最后生成的消息仍是"The color is blue, a very nice color indeed.",将被显示出来。
该方法也用于替换前三行的赋值、调用和删除新方法的代码:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.apply(this, new Array(sColor));
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
同样的,第一个参数仍是this,第二个参数是只有一个值color的数组。可以把ClassB的整个arguments对象作为第二个参数传递给apply()方法:
function ClassB(sColor, sName) {
//this.newMethod = ClassA;
//this.newMethod(color);
//delete this.newMethod;
ClassA.apply(this, arguments);
this.name = sName;
this.sayName = function () {
alert(this.name);
};
}
---------------------------
在上一章学过,prototype对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。
如果用原型方式重定义前面例子中的类,它们将变为下列形式:
function ClassA() {
}
ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB() {
}
ClassB.prototype = new ClassA();
原型方式的神奇之处在于突出显示的蓝色代码行。这里,把ClassB的prototype属性设置成ClassA的实例。这很有意思,因为想要ClassA的所有属性和方法,但又不想逐个将它们ClassB的prototype属性。还有比把ClassA的实例赋予prototype属性更好的方法吗?
注意:调用ClassA的构造函数,没有给它传递参数。这在原型链中是标准做法。要确保构造函数没有任何参数。
与对象冒充相似,子类的所有属性和方法都必须出现在prototype属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为prototype属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为ClassB类添加name属性和sayName()方法的代码如下:
function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
alert(this.name);
};
可通过运行下面的例子测试这段代码:
var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();
此外,在原型链中,instanceof运算符的运行方式也很独特。对ClassB的所有实例,instanceof为ClassA和ClassB都返回true。例如:
var objB = new ClassB();
alert(objB instanceof ClassA);//输出"true"
alert(objB instanceof ClassB);//输出"true"
----------------------
混合方式
这种继承方式使用构造函数定义类,并非使用任何原型。对象冒充的主要问题是必须使用构造函数方式,这不是最好的选择。不过如果使用原型链,就无法使用带参数的构造函数了。开发者如何选择呢?答案很简单,两者都用。
在前一章,我们曾经讲解过创建类的最好方式是用构造函数定义属性,用原型定义方法。这种方式同样适用于继承机制,用对象冒充继承构造函数的属性,用原型链继承prototype对象的方法。用这两种方式重写前面的例子,代码如下:
function ClassA(sColor) {
this.color = sColor;
}
ClassA.prototype.sayColor = function () {
alert(this.color);
};
function ClassB(sColor, sName) {
ClassA.call(this, sColor);
this.name = sName;
}
ClassB.prototype = new ClassA();
ClassB.prototype.sayName = function () {
alert(this.name);
};
在此例子中,继承机制由两行突出显示的蓝色代码实现。在第一行突出显示的代码中,在ClassB构造函数中,用对象冒充继承ClassA类的sColor属性。在第二行突出显示的代码中,用原型链继承ClassA类的方法。由于这种混合方式使用了原型链,所以instanceof运算符仍能正确运行。
下面的例子测试了这段代码:
var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();//输出"blue"
objB.sayColor();//输出"red"
objB.sayName();//输出"John"
------------------------------------