在JavaScript中,构造函数拥有原型,实例对象通过prototype关键字可以访问原型,实现JavaScript原型继承机制。
定义原型
原型实际上就是一个数据集合,即普通对象,继承与Object类,由JavaScript自动创建并依附于每个构造函数。
说明:JavaScript暂不支持类的概念,所谓的类就是构造函数。Function和Object都是两个不同类型的构造函数,使用new运算符可以创建不同类型的实例对象。
通过function.prototype方式定义原型。
function p(x){ //构造函数
this.x=x; //声明本地属性,并初始化为参数x
}
p.prototype.x=1; //添加原型属性x,赋值为1
var p1=new p(10); //实例化对象,并设置参数为10
alert(p1.x); //10
alert(p.prototype.x); //1
p.prototype.x=p1.x; //设置原型属性值为本地属性值
alert(p.prototype.x); //10
比较原型属性和本地属性
定义一个构造函数并为实例对象定义本地属性。
function f(){ // 声明一个构造函数
this.a=2; //本地属性
this.b=function(){ //本地方法
return this.a;
};
}
var e=new f(); //实例化构造函数
alert(e.a); //2
alert(e.b()); //2
e.a=22;
alert(e.a); //22
alert(e.b()); //22
实例化对象继承了构造函数的本地属性,可以在本地修改实例对象的属性和方法。
如果构造函数定义了与原型属性同名的本地属性,则本地属性会覆盖原有的属性值。
如果使用delete运算符删除本都属性,则原型属性会被访问。
本地属性可以在实例对象中修改,但是不同的实例对象之间不会相互干扰。
function f(){ // 声明一个构造函数
this.a=2; //本地属性
this.b=function(){ //本地方法
return this.a;
};
}
var e=new f(); //实例化构造函数
var g=new f(); //实例化构造函数
alert(e.a); //2
alert(e.b()); //2
e.a=22;
alert(e.a); //22
alert(e.b()); //22
alert(g.a); //2
alert(g.b()); //2
原型属性将会影响所有实例对象,修改任何原型属性值,则该构造函数的所有实例都会看到这种变化。
function f(){
this.a=11;
f.prototype.b=9;
this.c=function(){
return this.a;
};
}
var f1=new f();
var f2=new f();
alert(f1.b); //9
alert(f2.b); //9
f.prototype.b=55;
alert(f1.b); //55
alert(f2.b); //5
原型属性和原型方法可以在构造函数结构体内定义。
function f(){
f.prototype.a=33;
f.prototype.b=function(){
return f.prototype.a;
};
}
var f1=new f();
alert(f1.a); //33
prototype属性属于构造函数,所以必须使用构造函数通过点语法来调用prototype属性,再通过prototype属性来访问原型对象。
应用原型
利用原型为对象设置默认值。当原型属性与本地属性同名时,它们之间可以出现交流现象。可以利用这种现象为对象初始化默认值。
function f(x){
if(x){
this.x=x;
}
}
f.prototype.x=22;
var f1=new f();
alert(f1.x); //22
var f2=new f(34);
alert(f2.x); //34
利用原型间接实现本地数据备份。把本地对象的数据完全赋值给原型对象,相当于为该对象定义一个副本,通俗地说就是备份对象。这样当对象属性被修改时,可以通过原型对象来恢复本地对象的初始值。
function f(x){
this.x=x;
}
f.prototype.backup=function(){
for(var i in this){
f.prototype[i]=this[i];
}
}
var f1=new f(2);
alert(f1.x); //2
f1.backup();
alert(f1.x); //2
f1.x=55;
alert(f1.x); //55
f1=f.prototype;
alert(f1.x); //2
利用原型还可以为对象属性设置“只读”特性,这在一定程度上可以避免对象内部数据被任意修改的尴尬。
利用原型进行批量复制。
function f(x){
this.x=x;
}
var a=[];
function temp(){}
temp.prototype=new f(10);
for(var i=0;i<100;i++){
a[i]=new temp();
}
原型域和原型域链
在JavaScript中,实例对象在读取属性时,总是先检查自身域属性,如果存在,则会返回本地属性值,否则就会往上检索prototype原型域,如果找到同名属性,则返回prototype原型域中的原型属性。
prototype原型域可以允许原型属性引用任何类型的对象。如果在prototype原型域中没有找到指定的属性,则JavaScript将会跟腱炎引用关系,继续向外查找prototype原型域所指向对象的prototype原型域,直到对象的prototype域为它自己,或者出现循环为止。
原型域链能够帮助用户更清楚地认识JavaScript面向对象的本质。每个对象实例都有属性成员用于指向它的构造函数的原型(prototype),可以把这种层层指向父原型的关系称为原型域链(prototype chain)。
在JavaScript中,一切都是对象,函数是第一型。Function和Object都是函数的实例。构造函数的父原型指向Function的原型
Function.prototype.a=function(){ //Function原型方法
alert("Function");
}
Object.prototype.a=function(){ // Object原型方法
alert("Object");
}
function f(){ //构造函数f
this.a="a";
}
f.prototype={ //构造函数f的原型方法
w:function(){
alert("w");
}
}
alert(f instanceof Function); //true,说明f是Function的实例
alert(f.prototype instanceof Object); //true,说明f原型也是对象
alert(Function instanceof Object); //true,说明Function是Object的实例
alert(Function.prototype instanceof Object); //true,说明Function的原型是Object的实例
alert(Object instanceof Function); //true,说明Object是Function的实例
alert(Object.prototype instanceof Function); //false,说明Object.prototype是所有父原型的顶层
原型继承
原型继承是一种简化的继承机制,也是JavaScript主要支持的一种继承方式。在原型继承中,类和实例化概念淡化了,一切都从对象的角度来考虑。原型继承不再需要使用类来定义对象的结果,直接定义对象,并被其他对象引用,这样就形成了一种继承关系,其中引用对象被称为原型对象(Property Object)。JavaScript能够根据原型链来查找对象之间的这种继承关系。
使用原型继承父方法设计类型继承。
function A(X){ //A类
this.x1=x;
this.get1=function(){
return this.x1;
}
}
function B(x){ //B类
this.x2=x;
this.get2=function(){
return this.x2+this.x2;
}
}
B.prototype=new A(1);//原型对象继承A的实例
function C(x){ //C类
this.x3=x;
this.get3=function(){
return this.x2*this.x2;
}
}
C.prototype=new B(2); //原型对象继承B的实例
var f1=new B(2);
var f2=new C(3);
alert(f1.x1); //1
alert(f2.x1); //1
alert(f1.get2()); //4
alert(f2.get3()); //9
alert(f2.get2()); //4
prototype的最大特点是能够允许对象实例共享原型对象的成员。因此如果把某个对象作为一个类型的原型,那么说这个类型的实例以这个对象为原型。
基于原型的编程是面向对象编程的一种特定形式。在这种编程中,不需要声明静态类,而是通过复制已经存在的原型对象来实现继承关系的。
原型继承缺点:
- 每个类型只有一个原型,所以它不直接支持多重继承。
- 它不能很好地支持多参数或者动态参数的父类,也许在原型继承阶段,用户还不能决定以什么参数来实例化构造函数。
- 使用不够灵活。用户需要在原型声明阶段实例化父类对象,并把它作为当前类型的原型, 这限制了父类实例化的灵活性,很多时候无法确定父类对象实例化的时机和场所。
- prototype属性固有的副作用。
扩展原型方法
JavaScript允许为基本数据类型定义方法。通过Object.prototype添加原型方法,可以使得该方法对所有的对象可用。这样的方式对函数、数组、字符串、数字、正则表达式和布尔值都适用。
Function.prototype.method=function(name,func){
this.prototype[name]=func;
return this;
};
Number.method('integer',function(){
return Math[this<0?'ceil':'floor'](this);
});
document.write((-10/3).integer()); //-3
String.method('trim',function(){
return this.replace(/^\s+|\s+$/g,'');
});
document.write('"'+" abc ".trim()+'"'); //"abc"
通过为基本类型扩展方法,可以大大提高语言的表现力。由于JavaScript原型继承的本质,所有原型方法立即被赋予到所有的实例,即使该实例在原型方法被创建之前就创建好了。
基本类型的原型是公共结构,所以在扩展基类时务必小心,避免覆盖掉基类的原生方法。