Function类及闭包_javascript技艺_脚本之家,JS特殊函
分类:pc28.am

Function类 定义 Function类可以表示开发者定义的任何函数,用Function类直接创建函数的语法如下: var function_name=new Function(agrument1,agrument2,...,argumentN,function_body); 每个argument都是一个参数,最后一个参数是函数主体。 示例: 复制代码 代码如下: function sayHi{ alert("Hello " sName "," sMessage); } 还可以如下定义它: var sayHi=new Function("sName","sMessage","alert("Hello" sName "," sMessage);"); 注:尽管可用Function构造函数创建函数,但最好不要使用它,因为用它定义函数比用传统方式要慢得多。不过,所有函数都应看作是Function类的实例。 属性和方法 因为函数是引用类型,所以它们也有属性和方法,ECMAScript定义的属性length声明了函数期望的参数个数,例如: 复制代码 代码如下: function doAdd; } function sayHi; } alert;//outpus 1 alert;//outpus 0 Function对象也有与所有对象共享的标准valueOf方法,这两个方法返回的都是函数的源代码,在调试时尤其有用。 例如: 复制代码 代码如下: function doAdd; } alert; 这段代码输出了doAdd()函数的文本。 闭包 定义 所谓闭包,是指词法表示包括不必计算的变量的函数,也就是说,该函数能使用函数外定义的变量。在ECMAScript中使用全局变量是一个简单的闭包实例。 示例: 复制代码 代码如下: var sMessage="Hello World"; function sayHelloWold; } sayHelloWorld(); 在一个函数中定义另一个函数会使闭包变得更复杂,如: 复制代码 代码如下: var iBaseNum=10; function addNumbers{ function doAddtion(){ return iNum1 iNum2 iBaseNum; } return doAddtion包括函数doAddtion。内部函数是个闭包,因为它将获取外部函数的参数iNum1和iNum2以及全局变量iBaseNum的值。addNumbers()的最后一步调用了内部函数,把两个参数和全局变量相加,并返回它们的和。这里要掌握的重要概念是doAddtion()函数根本不接受参数,它使用的是从执行环境中获取的。 可以看到,闭包是ECMAScript中非常强大多用的一部分,可以用于执行复杂的计算。就像使用任何高级函数一样,在使用闭包时要当心,因为它们可能会变得非常复杂。 本文示例代码 复制代码 代码如下:

ECMAScript关键字

函数定义
函数是由这样的方式进行声明的:关键字 function、函数名、一组参数,以及置于括号中的待执行代码。
函数的构造语法有这三种:
Js代码

《JavaScript高级程序设计》读书笔记之八:Function类及闭包

delete

复制代码 代码如下:

Length:

do

1.function functionName(arg0, arg1, ... argN) { statements }//function语句
2.var function_name = new Function(arg1, arg2, ..., argN, function_body);//Function()构造函数
3.var func = function(arg0, arg1, ... argN) { statements };//函数直接量

valueOf:

else

示例:
Js代码

闭包:

finally

复制代码 代码如下:

function

1.function f(x){return x*x};//function语句
2.var f = new Function("x","return x*x;");//Function()构造函数
3.var f = function(x){return x*x;};//函数直接量

in

如果函数无明确的返回值,或调用了没有参数的 return 语句,那么它真正返回的值是 undefined。

instanceof

Function()构造函数
函数实际上是功能完整的对象 。Function类可以表示开发者定义的任何函数。用Function类直接创建函数的语法如下:
var function_name = new function(arg1, arg2, ..., argN, function_body)
在上面的形式中,每个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码)。这些参数必须是字符串。
var sayHi = new Function("sName", "sMessage", "alert('Hello ' sName sMessage);");
sayHi("jzj,", "你好!");//Hello jzj,你好!
函数名只是指向函数的变量,那么可以把函数作为参数传递给另一个函数吗?答案是可以的,请看:
Js代码

this

复制代码 代码如下:

throw

function callAnotherFunc(fnFunction, vArgument) {
fnFunction(vArgument);
}
var doAdd = new Function("iNum", "alert(iNum 10)");
callAnotherFunc(doAdd, 10); //输出 "20"

try

注意:尽管可以使用 Function 构造函数创建函数,但最好不要使用它,因为用它定义函数比用传统方式要慢得多。不过,所有函数都应看作 Function 类的实例 。
如果你定义的函数没有参数,那么可以只需给构造函数传递一个字符串(即函数的主体)即可。
注意:传递给构造函数Function()的参数中没有一个用于说明它要创建的函数名。用Function()构造函数创建的未命名函数有时被称为“匿名函数”。
Function()函数允许我们动态地建立和编译一个函数,它不会将我们限制在function语句预编译的函数体中。
函数直接量
函数直接量是一个表达式,它可以定义匿名函数。函数直接量的语法和function语句非常相似,只不过它被用作表达式,而不是用作语句,而且也无需指定函数名。语法:
Js代码

typeof

复制代码 代码如下:

var

var func = function(arg0, arg1, ... argN) { statements };//函数直接量

with

虽然函数直接量创建的是未命名函数,但是它的语法也规定它可以指定函数名,这在编写调用自身的递归函数时非常有用,例如:
Js代码

保留字

复制代码 代码如下:

abstract

var f = function fact(x) {
if (x <= 1) {
return 1;
} else {
return x * fact(x - 1);
}
};

debugger

注:它并没有真正创建一个名为fact()函数,只是允许函数体用这个名字来引用自身。JavaScript1.5之前的版本中没有正确实现这种命名的函数直接量。
函数引用
函数名并没有什么实质意义,它不过是用来保存函数的变量名字,可以将这个函数赋给其他变量,它仍然会以相同方式起作用:
Js代码

extends

复制代码 代码如下:

final

function square(x){return x*x;}
var a = square;
var b = a(5);//b 为25

goto

这有点像C 中的函数指针了。
Function()构造函数和函数直接量差别
Function()构造函数和函数直接量之间的差别有一点就是:使用构造函数Function()创建的函数不使用词法作用域,相反的,它们总是被顶级函数来编译,如:
Js代码

implements

复制代码 代码如下:

native

var y = "global";
function constructFunction() {
var y = "local";
//Function()构造函数
return new Function("return y;");//不使用局部作用域
}
function constFunction() {
var y = "local";
//函数直接量
var f = function () {
return y;//使用局部作用域
};
return f;
}
//显示 global,因为Function()构造函数返回的函数并不使用局部作用域
alert(constructFunction()());
//显示 lobal,因为函数直接量返回的函数并使用局部作用域
alert(constFunction()());

package

函数是由这样的方式进行声明的:关键字 function、函数名、一组参数,以及置于括号中的待执行代码。 函数的构造语法有这三种...

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"

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

本文由pc28.am发布于pc28.am,转载请注明出处:Function类及闭包_javascript技艺_脚本之家,JS特殊函

上一篇:JavaScript高等程序设计,本地对象Array_javascript本事 下一篇:没有了
猜你喜欢
热门排行
精彩图文