js prototype

来源:互联网 发布:手机淘宝店铺背景音乐 编辑:程序博客网 时间:2024/05/16 06:10

许多人一直对JS的prototype继承机制不明了,到底在创建一个类的实例的时候,解析器为我们做了些什么呢?
首先,我们来看一个例子:

function class1() {  this.name = "my name";}function class2() {  this.age = 123;}class2.prototype = new class1(); var obj = new class2();alert(obj.name); alert(class2.prototype.name);  delete class2.prototype; alert(obj.name); alert(class2.prototype);alert(class2.prototype.name); 


结果将是
firefox "my name" "my name" "my name" "[object object]" undefined
IE       "my name" "my name" "my name" undefined 出错

从最后两个结果来看,Firefox下的prototype是可以删除的,但它却是一定会存在的,在做delete(class2.prototype)的时候,Firefox将prototype变成一个普通的Object。
而IE对此的处理方式有所不同,当删除class2.prototype的时候,它就真真正正被删掉了,所以会得到undefined,并且最后一行会出错。
可能就有人会有疑问了,我们都已经把class2.prototype给删除了,虽然Firefox和IE处理方式不同,但确确实实肯定不会存在class2.prototype.name这个变量了,但为什么在删除之后,class2的实例obj的name值会存在?这就是我们要说的重点之一了。

接着看下边几行代码,请使用Firefox来运行。

function class1() {  this.name = "my name";}function class2() {  this.age = 123;}class2.prototype = new class1(); var obj = new class2();alert(obj.name);alert(obj.__proto__.name);delete class2.prototype;alert(obj.name);alert(obj.__proto__.name);obj.name = "new name";alert(obj.name);alert(obj.__proto__.name);delete obj.namealert(obj.name);alert(obj.__proto__.name);
在我们指定的firefox下运行结果是: "my name" "my name" "my name" "my name" "new name" "my name" "my name" "my name"是不是觉得结果好奇怪?__proto__这又是什么变量呢?为什么在delete(obj.name)之后,原先这个值为"new name"的属性没消失,但却随着这行代码变成了"my name"稍加分析,其实这个__proto__对象,就是原先那个class2.prototype,而且尽管class2.prototype被清掉了,这个obj.__proto__对象却依然存在!我们立刻就可以想到,JS里一个类的实例在实例化完毕之后,就与原先的类的关系断开了。当它在本身找不着一个属性的时候,会尝试去__proto__里找,如果找着就把它当成本身的属性了。这是根据Firefox提供出来的这个__proto__对象推断出来的,那IE又是怎么样的呢?我们继续看下边简化了的代码:
function class1() {  this.name = "my name";}function class2() {  this.age = 123;}class2.prototype = new class1(); var obj = new class2();alert(obj.name);alert(obj.__proto__);delete class2.prototype;alert(obj.name);obj.name = "new name";alert(obj.name);delete obj.namealert(obj.name);




在看过Firefox下的运行结果后,我们可以很欣喜地看到IE其实处理方式应该是一致的,在删除obj.name之后,它的值确实像Firefox一样,"new name"变成了"my name",但它并没有给我们提供__proto__这样的对象。

于是,我很肯定但有点武断地得出了以下推论:

运行"new xxx()"的时候,解析器会创建一个空的Object,然后为它创建了一个名字可能为__proto__(为什么要用可能二字?因为IE不知道起的是什么名,但Firefox确实用了这个名字)的属性,然后将xxx类的prototype赋值给它。接下来,进入类本身,相当于xxx.call(obj)的方式把新的对象当成this一行一行执行下去,然后返回该对象。
从这里也可以看出JS里根本没有类这种东西,或者说没有真正的类,它纯属模拟出来的,这也是为什么类的定义跟函数的定义都用了function这个关键词,事实上类的定义就是一个函数,它负责将传进来的this对象根据函数的逻辑给它增加了属性以及方法,跟其它高级语言里的类的定义是完全不一样的。
----

 

 

 

 

Js所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。js函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也有原型。譬如普通函数:

function F(){   alert(F.prototype instanceof Object) //true; }




构造函数,也即构造对象。首先了解下通过构造函数实例化对象的过程。

 代码如下:
function A(x){   this.x=x; } var obj=new A(1);



实例化obj对象有三步:

1. 创建obj对象:obj=new A();

2. 将obj的内部__proto__指向构造他的函数A的prototype,同 时,obj.constructor===A.prototype.constructor(这个是永远成立的,即使A.prototype不再指向原来 的A原型,也就是说:类的实例对象的constructor属性永远指向"构造函数"的prototype.constructor),从而使得 obj.constructor.prototype指向 A.prototype(obj.constructor.prototype===A.prototype,当A.prototype改变时则不成立, 下文有遇到)。obj.constructor.prototype与的内部_proto_是两码事,实例化对象时用的是_proto_,obj是没有 prototype属性的,但是有内部的__proto__,通过__proto__来取得原型链上的原型属性和原型方法,FireFox公开了 __proto__,可以在FireFox中alert(obj.__proto__);

3. 将obj作为this去调用构造函数A,从而设置成员(即对象属性和对象方法)并初始化。

当这3步完成,这个obj对象就与构造函数A再无联系,这个时候即使构造函数A再加任何成员,都不再影响已经实例化的obj对象了。此时,obj对象具有了x属性,同时具有了构造函数A的原型对象的所有成员,当然,此时该原型对象是没有成员的。

原型对象初始是空的,也就是没有一个成员(即原型属性和原型方法)。可以通过如下方法验证原型对象具有多少成员。
复制代码代码如下:

var num=0;
for(o in A.prototype) {
  alert(o);//alert出原型属性名字
  num++;
}
alert("member: " + num);//alert出原型所有成员个数。



但是,一旦定义了原型属性或原型方法,则所有通过该构造函数实例化出来的所有对象,都继承了这些原型属性和原型方法,这是通过内部的_proto_链来实现的。

譬如

A.prototype.say=function(){alert("Hi")};

那所有的A的对象都具有了say方法,这个原型对象的say方法是唯一的副本给大家共享的,而不是每一个对象都有关于say方法的一个副本。

二. 原型与继承

首先,看个简单的继承实现。

代码如下:

1.function A(x){ 2.  this.x=x; 3.} 4.function B(x,y){ 5.  this.tmpObj=A; 6.  this.tmpObj(x); 7.  delete this.tmpObj; 8.  this.y=y; 9.}



第5、6、7行:创建临时属性tmpObj引用构造函数A,然后在B内部执行,执行完后删除。当在B内部执行了this.x=x后(这里的this是B的 对象),B当然就拥有了x属性,当然B的x属性和A的x属性两者是独立,所以并不能算严格的继承。第5、6、7行有更简单的实现,就是通过 call(apply)方法:A.call(this,x);

这两种方法都有将this传递到A的执行里,this指向的是B的对 象,这就是为什么不直接A(x)的原因。这种继承方式即是类继承(js没有类,这里只是指构造函数),虽然继承了A构造对象的所有属性方法,但是不能继承 A的原型对象的成员。而要实现这个目的,就是在此基础上再添加原型继承。


通过下面的例子,就能很深入地了解原型,以及原型参与实现的完美继承。(本文核心在此^_^)
 代码如下:

1.function A(x){ 2.  this.x = x; 3.} 4.A.prototype.a = "a"; 5.function B(x,y){ 6.  this.y = y; 7.  A.call(this,x); 8.} 9.B.prototype.b1 = function(){ 10.alert("b1"); 11.} 12.B.prototype = new A(); 13.B.prototype.b2 = function(){ 14.  alert("b2"); 15.} 16.B.prototype.constructor = B; 17.var obj = new B(1,3);


这个例子讲的就是B继承A。第7行类继承:A.call(this.x);上面已讲过。实现原型继承的是第12行:B.prototype = new A();

就是说把B的原型指向了A的1个实例对象,这个实例对象具有x属性,为undefined,还具有a属性,值为"a"。所以B原型也具有了这2个属性(或 者说,B和A建立了原型链,B是A的下级)。而因为方才的类继承,B的实例对象也具有了x属性,也就是说obj对象有2个同名的x属性,此时原型属性x要 让位于实例对象属性x,所以obj.x是1,而非undefined。第13行又定义了原型方法b2,所以B原型也具有了b2。虽然第9~11行设置了原 型方法b1,但是你会发现第12行执行后,B原型不再具有b1方法,也就是obj.b1是undefined。因为第12行使得B原型指向改变,原来具有 b1的原型对象被抛弃,自然就没有b1了。

第12行执行完后,B原型(B.prototype)指向了A的实例对象,而A的实例对象的构造器是构造函数A,所以B.prototype.constructor就是构造对象A了(换句话说,A构造了B的原型)。

alert(B.prototype.constructor) 出来后就是"function A(x){...}" 。同样地,obj.constructor也是A构造对象,alert(obj.constructor)出来后就是"function A(x){...}" ,也就是说B.prototype.constructor===obj.constructor(true),但是 B.prototype===obj.constructor.prototype(false),因为前者是B的原型,具有成员:x,a,b2,后者是 A的原型,具有成员:a。如何修正这个问题呢,就在第16行,将B原型的构造器重新指向了B构造函数,那么 B.prototype===obj.constructor.prototype(true),都具有成员:x,a,b2。

如果没有第16行,那是不是obj = new B(1,3)会去调用A构造函数实例化呢?答案是否定的,你会发现obj.y=3,所以仍然是调用的B构造函数实例化的。虽然 obj.constructor===A(true),但是对于new B()的行为来说,执行了上面所说的通过构造函数创建实例对象的3个步骤,第一步,创建空对象;第二步,obj.__proto__ === B.prototype,B.prototype是具有x,a,b2成员的,obj.constructor指向了 B.prototype.constructor,即构造函数A;第三步,调用的构造函数B去设置和初始化成员,具有了属性x,y。虽然不加16行不影响 obj的属性,但如上一段说,却影响obj.constructor和obj.constructor.prototype。所以在使用了原型继承后,要 进行修正的操作。

关于第12、16行,总言之,第12行使得B原型继承了A的原型对象的所有成员,但是也使得B的实例对象的构造器的原型指向了A原型,所以要通过第16行修正这个缺陷。

函数:原型

每一个构造函数都有一个属性叫做原型(prototype,下面都不再翻译,使用其原文)。这个属性非常有用:为一个特定类声明通用的变量或者函数。

prototype的定义

你不需要显式地声明一个prototype属性,因为在每一个构造函数中都有它的存在。你可以看看下面的例子:

Example PT1

CODE:
function Test()
{
}
alert(Test.prototype); // 输出 "Object"

给prototype添加属性

就如你在上面所看到的,prototype是一个对象,因此,你能够给它添加属性。你添加给prototype的属性将会成为使用这个构造函数创建的对象的通用属性。

例如,我下面有一个数据类型Fish,我想让所有的鱼都有这些属性:livesIn="water"和price=20;为了实现这个,我可以给构造函数Fish的prototype添加那些属性。

Example PT2

CODE:
function Fish(name, color)
{
this.name=name;
this.color=color;
}
Fish.prototype.livesIn="water";
Fish.prototype.price=20;

接下来让我们作几条鱼:

CODE:
var fish1=new Fish("mackarel", "gray");
var fish2=new Fish("goldfish", "orange");
var fish3=new Fish("salmon", "white");

再来看看鱼都有哪些属性:

CODE:
for (int i=1; i<=3; i++)
{
var fish=eval_r("fish"+i);   // 我只是取得指向这条鱼的指针
alert(fish.name+","+fish.color+","+fish.livesIn+","+fish.price);
}

输出应该是:

CODE:
"mackarel, gray, water, 20"
"goldfish, orange, water, 20"
"salmon, white water, 20"

你看到所有的鱼都有属性livesIn和price,我们甚至都没有为每一条不同的鱼特别声明这些属性。这时因为当一个对象被创建时,这个构造函数将会把它的属性prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找它的属性。

你也可以通过prototype来给所有对象添加共用的函数。这有一个好处:你不需要每次在构造一个对象的时候创建并初始化这个函数。为了解释这一点,让我们重新来看Example DT9并使用prototype来重写它:

用prototype给对象添加函数

Example PT3

CODE:

function Employee(name, salary)
{
this.name=name;               
this.salary=salary;
}
Employee.prototype.getSalary=function getSalaryFunction()
{
return this.salary;
}

Employee.prototype.addSalary=function addSalaryFunction(addition)
{
this.salary=this.salary+addition;
}

 

我们可以象通常那样创建对象:

CODE:
var boss1=new Employee("Joan", 200000);
var boss2=new Employee("Kim", 100000);
var boss3=new Employee("Sam", 150000);

并验证它:

CODE:
alert(boss1.getSalary());   // 输出 200000
alert(boss2.getSalary());   // 输出 100000
alert(boss3.getSalary());   // 输出 150000

这里有一个图示来说明prototype是如何工作的。这个对象的每一个实例(boss1, boss2, boss3)都有一个内部属性叫做__proto__,这个属性指向了它的构造器(Employee)的属性prototype。当你执行 getSalary或者addSalary的时候,这个对象会在它的__proto__找到并执行这个代码。注意这点:这里并没有代码的复制(和 Example DT8的图表作一下对比)。

js的Prototype属性 <wbr>解释及常用方法


 

原创粉丝点击