ECMAScript

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"

TIY

给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"

------------------------------------

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,716评论 2 17
  • 本人是android开发的,由于最近React Native的火热,再加上自己完全不懂JS的语法,俗话说的好"落后...
    Bui_vlee阅读 277评论 0 0
  • 我基本從來不寫工作的事兒。 因為工作實在沒啥好寫的,不就是工作唄。 然後今天打算稍微寫一點,就寫JS吧。 我一直相...
    LostAbaddon阅读 1,429评论 22 21
  • 一、let 和 constlet:变量声明, const:只读常量声明(声明的时候赋值)。 let 与 var 的...
    dadage456阅读 755评论 0 0
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4