JavaScript中的Function函数对象.docx
《JavaScript中的Function函数对象.docx》由会员分享,可在线阅读,更多相关《JavaScript中的Function函数对象.docx(30页珍藏版)》请在冰豆网上搜索。
JavaScript中的Function函数对象
JavaScript中的Function(函数)对象
JavaScript中的Function对象是函数,函数的用途分为3类:
1.作为普通逻辑代码容器;
2.作为对象方法;
3.作为构造函数。
1.作为普通逻辑代码容器
functionmultiply(x,y){
returnx*y;
}
函数multiply封装了两位数的乘法运算公式:
varproduct=multiply(128,128);//product=16384
创建函数实例的方式有3种。
第一种是声明式,即像声明变量一样,将通过function(){}标识符创建的匿名函数直接赋值给变量,以该变量作为调用时的函数名称:
varmultiply=function(x,y){
returnx*y;
}
第二种是定义式,即以function关键字后跟函数名称及(){}来直接定义命名函数,前面第一个multiply函数就是通过定义式创建的。
第三种是构造函数式,即通过new运算符调用构造函数Function来创建函数。
这种方式极不常用,因此就不作介绍了。
在创建函数的3种方式中,声明式和定义式还存在细微的差别。
比如下列代码中的函数采用声明式:
varexample=function(){
return1;
}
example();
varexample=function(){
return2;
}
example();
执行结果如下:
1
2
而如果采用定义式,即:
functionexample(){
return1;
}
example();
functionexample(){
return2;
}
example();
那么会得到另一种结果:
2
2
即,在采用定义式创建同名函数时,后创建的函数会覆盖先创建的函数。
这种差别是由于JavaScript解释引擎的工作机制所导致的。
JavaScript解释引擎在执行任何函数调用之前,首先会在全局作用域中注册以定义式创建的函数,然后再依次执行函数调用。
由于注册函数时,后定义的函数重写了先定义的函数,因此无论调用语句位于何处,执行的都是后定义的函数。
相反,对于声明式创建的函数,JavaScript解释引擎会像对待任何声明的变量一样,等到执行调用该变量的代码时才会对变量求值。
由于JavaScript代码是从上到下顺序执行的,因此当执行第一个example()调用时,example函数的代码就是首先定义代码;而当执行第二个example()调用时,example函数的代码又变成了后来定义的代码。
2.作为对象方法
JavaScript在解析代码时,会为声明或定义的函数指定调用对象。
所谓调用对象,就是函数的执行环境。
如果函数体内有以关键字this声明的变量,则this引用的就是调用对象。
事实上,在普通的函数中,也存在调用对象,只不过这个调用对象是默认的全局window对象而已。
例如:
varproduct=window.multiply(128,128);//product=16384
这说明,默认情况下,在全局作用域中定义或声明的函数的调用对象就是window。
在面向对象编程中,通常将作为对象成员的函数称为方法。
例如:
vardog={};
dog.name=“heibao”;
dog.age=“3months”;
dog.shout=function(){
return“Hello,Mynameis“+this.name+”andIam”+this.age+”old!
”;
}
dog.shout();//“Hello,MynameisheibaoandIam3monthsold!
”
有意思的是,对象也可以借用其他对象的方法:
varcat={};
cat.name=“xiaohua”;
cat.age=“2years”;
cat.greet=dog.shout;
cat.greet();//“Hello,MynameisxiaohuaandIam2yearsold!
”
另外,使用函数对象的call和apply方法,还可以动态指定函数或方法的调用对象:
dog.shout.call(cat);//“Hello,MynameisxiaohuaandIam2yearsold!
”
或者
dog.shout.apply(cat);//“Hello,MynameisxiaohuaandIam2yearsold!
”
3.作为构造函数
JavaScript是通过构造函数来模拟面向对象语言中的类的。
例如:
functionAnimal(sort,character){
this.sort=sort;
this.character=character;
}
以Animal作为构造函数,就可以像下面这样创建一个新对象:
vardog=newAnimal(”mammal”,”fourlegs”);
创建dog的对象的过程如下:
首先,new运算符创建一个空对象({}),然后以这个空对象为调用对象调用函数Animal,为这个空对象添加两个属性sort和character,接着,再将这个空对象的默认constructor属性修改为构造函数的名称(即Animal;空对象创建时默认的constructor属性值是Object),并且将空对象的__proto__属性设置为指向Animal.prototype——这就是所谓的对象初始化。
最后,返回初始化完毕的对象。
这里将返回的新对象赋值给了变量dog。
dog.sort;//mammal
dog.character;//fourlegs
dog.constructor;//Animal
聪明的读者结合前面介绍的内容,可能会认为使用new运算符调用构造函数创建对象的过程也可以像下面这样来实现:
vardog={};
Animal.call(dog,“mammal”,”fourlegs”);
表面上看,这两行代码与vardog=newAnimal(”mammal”,”fourlegs”);是等价的,其实却不是。
虽然通过指定函数的执行环境能够部分达到初始化对象的目的,例如空对象dog确实获得了sort和character这两个属性:
dog.sort;//mammal
dog.character;//fourlegs
dog.constructor;//Object——注意,没有修改dog对象默认的constructor属性
但是,最关键的是新创建的dog对象失去了通过Animal.prototype属性继承其他对象的能力。
只要与前面采用new运算符调用构造函数创建对象的过程对比一下,就会发现,new运算符在初始化新对象期间,除了为新对象添加显式声明的属性外,还会对新对象进行了一番“暗箱操作”——即将新对象的constructor属性重写为Animal,将新对象的__proto__属性设置为指向Animal.prototype。
虽然手工“初始化对象”也可以将dog.constructor重写为Animal,但根据ECMA262规范,对象的__proto__属性对开发人员是只读的,对它的设置只能在通过new运算符创建对象时由JavaScript解释引擎替我们完成。
JavaScript是基于原型继承的,如果不能正确设置对象的__proto__属性,那么就意味着默认的继承机制会失效:
Animal.prototype.greet=“Hi,goodlucky!
”;
dog.greet;//undefined
事实上,在Firefox中,__proto__属性也是可写的:
Animal.prototype.greet=“Hi,goodlucky!
”;
dog.__proto__=Animal.prototype;
dog.greet;//Hi,goodlucky!
但这样做只能在Firefox中行得通。
考虑到在兼容多浏览器,必须依赖于new运算符,才能实现基于原型的继承。
JavaScript
使用面向对象的技术创建高级Web应用程序
本文讨论:
▪JavaScript是基于原型的语言
▪用JavaScript进行面向对象的编程
▪JavaScript编码技巧
▪JavaScript的未来
本文使用了以下技术:
JavaScript
目录
JavaScript对象是词典
JavaScript函数是最棒的
构造函数而不是类
原型
静态属性和方法
闭包
模拟私有属性
从类继承
模拟命名空间
应当这样编写JavaScript代码吗?
展望
最近,我面试了一个有五年Web应用程序开发经验的软件开发人员。
四年半来她一直在从事JavaScript相关的工作,她自认为JavaScript技能非常好,但在不久之后我就发现实际上她对JavaScript知之甚少。
话虽这样说,但我确实没有责备她的意思。
JavaScript真的是很有趣。
很多人(包括我自己,直到最近!
)都认为自己很擅长JavaScript语言,因为他们都知道C/C++/C#,或者有一些以前的编程经验。
在某种程度上,这种假设并不是完全没有根据的。
用JavaScript很容易做些简单的事情。
入门的门槛很低,该语言很宽松,它不需要您知道很多细节就可以开始用它进行编码。
甚至非编程人员也可能用它在几个小时内为主页编写一些有用的脚本。
的确,直到最近,仅仅凭借MSDN® DHTML参考资料和我的C++/C#经验,我也总能勉强利用这点JavaScript知识完成一些任务。
只是当我开始编写真实的AJAX应用程序时,我才意识到实际上我的JavaScript知识还非常不够。
这个新一代的Web应用程序的复杂性和交互性需要程序员以完全不同的方法来编写JavaScript代码。
它们是真正的JavaScript应用程序!
我们在编写一次性脚本时一直采用的方法已完全不再有效。
面向对象编程(OOP)是一种流行的编程方法,很多JavaScript库中都使用这种方法,以便更好地管理和维护基本代码。
JavaScript支持OOP,但与诸如C++、C#或VisualBasic® 等流行的Microsoft® .NETFramework兼容语言相比,它支持OOP的方式非常不同,因此主要使用这些语言的开发人员开始可能会觉得在JavaScript中使用OOP很奇怪而且不直观。
我写本文就是为了深入讨论JavaScript语言实际上如何支持面向对象编程,以及您如何使用这一支持在JavaScript中高效地进行面向对象开发。
下面首先讨论对象(还能先讨论其他别的什么呢?
)。
JavaScript对象是词典
在C++或C#中,在谈论对象时,是指类或结构的实例。
对象有不同的属性和方法,具体取决于将它们实例化的模板(即类)。
而JavaScript对象却不是这样。
在JavaScript中,对象只是一组名称/值对,就是说,将JavaScript对象视为包含字符串关键字的词典。
我们可以使用熟悉的“.”(点)运算符或“[]”运算符,来获得和设置对象的属性,这是在处理词典时通常采用的方法。
以下代码段
varuserObject=newObject();
userObject.lastLoginTime=newDate();
alert(userObject.lastLoginTime);
的功能与下面的代码段完全相同:
varuserObject={};//equivalenttonewObject()
userObject[“lastLoginTime”]=newDate();
alert(userObject[“lastLoginTime”]);
我们还可以直接在userObject的定义中定义lastLoginTime属性,如下所示:
varuserObject={“lastLoginTime”:
newDate()};
alert(userObject.lastLoginTime);
注意,它与C#3.0对象初始值非常相似。
而且,熟悉Python的人会发现在第二和第三个代码段中实例化userObject的方法与在Python中指定词典的方法完全相同。
唯一的差异是JavaScript对象/词典只接受字符串关键字,而不是像Python词典那样接受可哈希化的对象。
这些示例还显示JavaScript对象比C++或C#对象具有更大的可延展性。
您不必预先声明属性lastLoginTime—如果userObject没有该名称的属性,该属性将被直接添加到userObject。
如果记住JavaScript对象是词典,您就不会对此感到吃惊了,毕竟,我们一直在向词典添加新关键字(和其各自的值)。
这样,我们就有了对象属性。
对象方法呢?
同样,JavaScript与C++/C#不同。
若要理解对象方法,首先需要仔细了解一下JavaScript函数。
JavaScript函数是最棒的
在很多编程语言中,函数和对象通常被视为两样不同的东西。
在JavaScript中,其差别很模糊—JavaScript函数实际上是具有与它关联的可执行代码的对象。
请如此看待普通函数:
functionfunc(x){
alert(x);
}
func(“blah”);
这就是通常在JavaScript中定义函数的方法。
但是,还可以按以下方法定义该函数,您在此创建匿名函数对象,并将它赋给变量func
varfunc=function(x){
alert(x);
};
func(“blah2”);
甚至也可以像下面这样,使用Function构造函数:
varfunc=newFunction(“x”,“alert(x);”);
func(“blah3”);
此示例表明函数实际上只是支持函数调用操作的对象。
最后一个使用Function构造函数来定义函数的方法并不常用,但它展示的可能性非常有趣,因为您可能注意到,该函数的主体正是Function构造函数的String参数。
这意味着,您可以在运行时构造任意函数。
为了进一步演示函数是对象,您可以像对其他任何JavaScript对象一样,在函数中设置或添加属性:
functionsayHi(x){
alert(“Hi,“+x+“!
”);
}
sayHi.text=“HelloWorld!
”;
sayHi[“text2”]=“HelloWorld...again.”;
alert(sayHi[“text”]);//displays“HelloWorld!
”
alert(sayHi.text2);//displays“HelloWorld...again.”
作为对象,函数还可以赋给变量、作为参数传递给其他函数、作为其他函数的值返回,并可以作为对象的属性或数组的元素进行存储等等。
图1 提供了这样一个示例。
Figure 1 JavaScript中的函数是最棒的
//assignananonymousfunctiontoavariable
vargreet=function(x){
alert(“Hello,“+x);
};
greet(“MSDNreaders”);
//passingafunctionasanargumenttoanother
functionsquare(x){
returnx*x;
}
functionoperateOn(num,func){
returnfunc(num);
}
//displays256
alert(operateOn(16,square));
//functionsasreturnvalues
functionmakeIncrementer(){
returnfunction(x){returnx+1;};
}
varinc=makeIncrementer();
//displays8
alert(inc(7));
//functionsstoredasarrayelements
vararr=[];
arr[0]=function(x){returnx*x;};
arr[1]=arr[0]
(2);
arr[2]=arr[0](arr[1]);
arr[3]=arr[0](arr[2]);
//displays256
alert(arr[3]);
//functionsasobjectproperties
varobj={“toString”:
function(){return“Thisisanobject.”;}};
//callsobj.toString()
alert(obj);
记住这一点后,向对象添加方法将是很容易的事情:
只需选择名称,然后将函数赋给该名称。
因此,我通过将匿名函数分别赋给相应的方法名称,在对象中定义了三个方法:
varmyDog={
“name”:
“Spot”,
“bark”:
function(){alert(“Woof!
”);},
“displayFullName”:
function(){
alert(this.name+“TheAlphaDog”);
},
“chaseMrPostman”:
function(){
//implementationbeyondthescopeofthisarticle
}
};
myDog.displayFullName();
myDog.bark();//Woof!
C++/C#开发人员应当很熟悉displayFullName函数中使用的“this”关键字—它引用一个对象,通过对象调用方法(使用VisualBasic的开发人员也应当很熟悉它,它在VisualBasic中叫做“Me”)。
因此在上面的示例中,displayFullName中的“this”的值是myDog对象。
但是,“this”的值不是静态的。
通过不同对象调用“this”时,它的值也会更改以便指向相应的对象,如图2 所示。
Figure 2 “this”随对象更改而更改
functiondisplayQuote(){
//thevalueof“this”willchange;dependson
//whichobjectitiscalledthrough
alert(this.memorableQuote);
}
varwilliamShakespeare={
“memorableQuote”:
“Itisawisefatherthatknowshisownchild.”,
“sayIt”:
displayQuote
};
varmarkTwain={
“memorableQuote”:
“Golfisagoodwalkspoiled.”,
“sayIt”:
displayQuote
};
varoscarWilde={
“memorableQuote”:
“Truefriendsstabyouinthefront.”
//wecancallthefunctiondisplayQuote
//asamethodofoscarWildewithoutassigningit
//asoscarWilde’smethod.
//”sayIt”:
displayQuote
};
williamShakespeare.sayIt();//true,true
markTwain.sayIt();//hedidn’tknowwheretoplaygolf
//watchthis,eachfunctionhasamethodcall()
//thatallowsthefunctiontobecalledasa
//methodoftheobjectpassedtocall()asan
//argument.
//thislinebelowisequivalenttoassigning
//displayQuotetosayIt,andcallingoscarWilde.sayIt().
displayQuote.call(oscarWilde);//ouch!
图2 中的最后一行表示的是将函数作为对象的方法进行调用的另一种方式。
请记住,JavaScript中的函数是对象。
每个函数对象都有一个名为call的方法,它将函数作为第一个参数的方法进行调用。
就是说,作为函数第一个参数传递给call的任何对象都将在函数调用中成为“this”的值。
这一技术对于调用基类构造函数来说非常有用,稍后将对此进行介绍。
有一点需要记住,绝不要调用包含“this”(却没有所属对象)的函数。
否则,将违反全局命名空间,因为在该调用中,“this”将引用全局对象,而这必然会给您的应用程序带来灾难。
例如,下面的脚本将更改JavaScript的全局函数isNaN的行为。
一定不要这样做!
alert(“NaNisNaN:
“+isNaN(NaN));
functionx(){
this.isNaN=function(){
return“notanymore!
”;
};
}
//alert!
!
!
tramplingtheGlobalobject!
!
!
x();
alert(“NaNisNaN:
“+isNaN(NaN));
到这里,我们已经介绍了如何创建对象,包括它的属性和方法。
但如果注意上面的所有代码段,您会发现属性和方法是在对象定义本身中进行硬编码的。
但如果需要更好地控制对象的创建,该怎么做呢?
例如,您可能需要根据某些参数来计算对象的属性值。
或者,可能需要将对象的属性初始化为仅在运行时才能获得的值。
也可能需要创建对象的多个实例(此要求非常常见)。
在C#中,我们使用类来实例化对象实例。
但JavaScript与此不同,因为它没有类。
您将在下一节中看到,您可以充分利用这一情况:
函数在与“new”运算符一起使用时,函数将充当构造函数。