web前端之悟透JavaScript三:JavaScript真经(原型)
1.初看原型:
prototype在软件界翻译成“原型”,代表事物的初始形态,也含有模型和样板的意义。JavaScript的所有function类型的对象都有一个prototype属性。这个prototype属性本身也是一个object类型的对象,因此我们也可以为这个prototype对象添加任意的属性和方法。事实上,在构造函数的prototype上定义的所有属性和方法,都是可以通过其构造的对象直接访问和调用的。也可以这么说,prototype提供了一群同类对象共享属性和方法的机制。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>初看原型</title>
<script type="text/javascript">
function Person(name){
this.name=name;
};
Person.prototype.SayHello=function(){//给Person函数的prototype添加SayHello方法
alert("Hello,I'm "+this.name);
};
var BillGates=new Person("Bill Gates");
var SteveJobs=new Person("Steve Jobs");
BillGates.SayHello();
SteveJobs.SayHello();
alert(BillGates.SayHello==SteveJobs.SayHello);
</script>
</head>
<body>
</body>
</html>
构造函数的prototype上定义的方法确实可以通过对象直接调用到,而且代码是共享的。显然,把方法设置到prototype的写法显得优雅多了,尽管调用形式没有改变,但是逻辑上却体现了方法与类的关系,更容易理解
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title></title>
<script type="text/javascript">
function Person(name){
this.name=name;
};
Person.prototype.SayHello=function(){//给Person函数的prototype添加SayHello方法
alert("Hello,I'm "+this.name);
};
function Employee(name,salay){
Person.call(this,name);
this.salay=salay;
};
Employee.prototype=new Person();//建一个基类的对象作为子类原型的原型(原型继承)
Employee.prototype.ShowMeTheMoney=function(){
alert(this.name+" $ "+this.salay);
};
var BillGates=new Person("Bill Gates");
var SteveJobs=new Employee("Steve Jobs",1234);
BillGates.SayHello();
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
alert(BillGates.SayHello==SteveJobs.SayHello);
</script>
</head>
<body>
</body>
</html>
Employee.prototype=new Person(),构造了一个基类的对象,并将其设为子类的构造函数的prototype,这个是很有意思的。这样做的目的是为了后面,通过子类对象也可以直接调用基类prototype的方法。
在JavaScript中,prototype不但能让对象共享自己的财富,而且prototype还有寻根问主的天性,从而使得先辈们的遗产可以代代相传。当从一个对象那里读取属性或调用方法时,如果该对象自身不存在这样的属性和方法,就会去自己关联的prototype对象那里寻找;如果prototype没有,又会去prototype自己关联的前辈配prototype哪里寻找,直到找到或直到追溯结果结束。
在JavaScript内部,对象的属性和方法追溯机制使用过所谓的prototype链来实现的。当用new操作符构造对象时,也会同时将构造函数的prototype对象指派给新创建的对象,成为该对象内置的原型对象。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Document</title>
<script type="text/javascript">
function Person(name){
this.name=name;
};
Person.prototype.company="Microsoft";//原型的属性
Person.prototype.SayHello=function(){
alert("Hello I'm "+this.name+" of "+this.company);
};
var BillGates=new Person("Bill Gates");
BillGates.SayHello();
var SteveJobs=new Person("Steve Jobs");
SteveJobs.company="Apple";
SteveJobs.SayHello=function(){
alert("Hi,"+this.name+" like "+this.company+".ha ha");
};
SteveJobs.SayHello();
BillGates.SayHello();
</script>
</head>
<body>
</body>
</html>
对象可以覆盖原型对象的那些属性和方法,一个构造函数原型对象也可以掩盖上层构造函数原型对象既有的属性和方法。这种掩盖其实只是在对象自己身上创建了新的属性和方法,只不过这些属性和方法与原型对象的那些同名而已。JavaScript就是用这个简单的掩盖机制来实现对象的“多态性”,与静态对象语言的虚函数和重载概念不谋而合。
然而,比静态对象语言更神奇的是,我们可以随时给原型对象动态添加新的属性和方法,从而动态地扩展基类的功能特性,现在让我们看下面这个代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title></title>
<script type="text/javascript">
function Person(name){
this.name=name;
};
Person.prototype.SayHello=function(){
alert("Hello I'm "+this.name);
};
var BillGates=new Person("Bill Gates");
BillGates.SayHello();
Person.prototype.Retire=function(){
alert("Poor "+this.name+" .bye bye!");
};
BillGates.Retire();
</script>
</head>
<body>
</body>
</html>
2.原型扩展:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>模拟闭包机制</title>
<script type="text/javascript">
function Person(firstName,lastName,age){
//私有变量
var _firstName=firstName;
var _lastName=lastName;
//公共变量
this.age=age;
//方法
this.getName=function(){
return(firstName+" "+lastName);
};
this.SayHello=function(){
alert("Hello,I'm "+firstName+" "+lastName);
};
};
var BillGates=new Person("Bill","Gates",53);
var SteveJobs=new Person("Steve","Jobs",51);
BillGates.SayHello();
SteveJobs.SayHello();
alert(BillGates.getName()+" "+BillGates.age);
alert(BillGates._firstName);//这里不能访问私有变量
</script>
</head>
<body>
</body>
</html>
所谓的“闭包”,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标对象在生存期间内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内始终能引用到该变量的值,而且该值只能通过这个方法来访问。即使再次调用相同的构造函数,只会生成新对象和方法,新的临时变量只是对应新的值,和上次那次调用的是各自独立的。
原型函数需要一个构造函数来到定义对象的成员,而方法却依附在构造函数的原型上。大致的写法如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>Document</title>
<script type="text/javascript">
function Person(name){
this.name=name;
};
Person.prototype.SayHello=function(){
alert("Hello ,I'm "+this.name);
};
//子类构造函数
function Employee(name,salay){
Person.call(this,name);//调用上层构造函数
this.salay=salay;
};
//在自雷构造函数首先需要用上层构造函数来建立prototype对象,实现继承的概念
Employee.prototype=new Person();
Employee.prototype.ShowMeTheMoney=function(){
alert(this.name+" $ "+this.salay);
};
var BillGates=new Person("Bill Gates");
BillGates.SayHello();
var SteveJobs=new Employee("Steve Jobs",1234);
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();
</script>
</head>
<body>
</body>
</html>
原型类模型虽然不能模拟真正的私有变量,但是也要分两部分来定义类,显得不优雅。但是对象间的方法是共享的,不会遇到垃圾回收的问题,而且性能优于“闭包”模型。