JS中的 prototype的含义


 搜了两 个认为好的讲解 Prototype 属性的文章 , 希望会对大伙有一点帮助 .

我们知道 JScript 中对象的 prototype 属性 , 是用来 返回对象类型原型的引用 的。我们使用 prototype 属性提供对象的类的一组基本功能。并且对象的新实例会 " 继承 " 赋予该对象原型的操作。但是这个 prototype 到底是怎么实现和被管理的呢?

对于对象的 prototype 属 性的说明, JScript 手册 上如是说:所有 JScript 内部 对象都有只读的 prototype 属 性。 可以向其原型中动态添加功能 ( 属性和方法 ) ,但该对象不能被赋予不同的原型。 然 而,用户定义的对象可以被赋给新的原型。

下面我们看三个经典的 prototype 属 性的使用示例。

1
、为脚本环境内建对象添加方法:


程序代码
Array .prototype.max = function()
{
var i, max =
this [0];
for (i = 1; i < this.length; i++)
{
if (max < this[i])
max = this[i];
}
return max;
};


2
、为用户自定义类 添加方法

程序代码
function TestObject(name)
{
this.m_Name = name;
}

TestObject.prototype.ShowName = function()
{
alert(this.m_Name);
};


3
、更新自定义类的 prototype


程序代码
function TestObjectA()
{
this.MethodA = function()
{
alert('
TestObjectA.MethodA() ');  // 显示这一段文字而已
}
}

function TestObjectB()
{
this.MethodB = function()
{
alert('TestObjectB.MethodB()');
}
}

TestObjectB.prototype = new TestObjectA();


第三个很眼熟吧?对啊,它就是我们前面介绍的原型继承法呀 ~~ 不过今天我们不是研究 " 继承 " ,之所以可以这样来实现一种继承,只是利用了 prototype 属性的一个副作用而已。

prototype 还 有一个默认的属性: constructor , 是用来表示创建对象的函数的 ( 即我 们 OOP 里说 的构造函数 ) constructor 属性是所有具有 prototype 属性的对象的成员。它们包括除 Global Math 对象以外的所有 JScript 内部对象。 constructor 属性保存了对构造特定对象实例的函数的引用。

弄清楚了 JScript prototype 属性怎么使用后,下面我们再来深入的研究它。
上面的文章中我罗列了一下 prototype 属 性在 JScript 中的 各种用法,但是 prototype 这个 东西却不是 JScript 创造 出来的, JScript 实际 上是使用了我们设计模式中 prototype pattern 的 一种衍生形式。下面我先简单的说一下 prototype pattern , 然后再来看到底 JScript 中的 prototype 是怎么回事 ?!

What's prototype pattern?

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节, 工作原理是 :通过将一个原型对象传给那个 要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

继续了解到底什么是 prototype pattern ,可以参看 ' 设 计模式之 Prototype( 原型 )' 这篇文章,即使不懂 Java 也没有关系,把它的代码都当 C# 看就行了。

搞清楚什么是原型了吧?反正记着一点, prototype pattern 是的实现是依赖于 clone 这个操作的,当然要 shallow copy 还是 deep copy clone 看自己的需要了。

下面我们继续说 JScript 里 的 prototype ,为 什么我们说它和 prototype pattern 里的 prototype 不一样呢 ?! 这个不是我说就说出来的,也不是我吹出来的,看看这个示例,你就能大概糊 涂:

程序代码
<script language="javascript">
function RP()
{
 RP.PropertyA = 1;   
// 相当于静态
 RP.MethodA = function()     
// 相当于静态
 {
 alert("RP.MethodA ");
 };

 this.PropertyA = 100;
 this.MethodA = function()
 {
 alert("this.MethodA");
 };
}

RP.prototype.PropertyA = 10;
RP.prototype.MethodA = function()
{
alert("RP.prototype.MethodA");
};
</script>

不要着急,还没有开始做示例,只是给出了我们用来演示的一个类。 RP 是什么? rpwt 吗?当然不是了, RP ResearchPrototype 了。 好了不废话了,看示例及结果分析。


程序代码
<script language="javascript">
rp = new RP();
alert(RP.PropertyA);
RP.MethodA();
alert(rp.PropertyA);
rp.MethodA();
</script>


运行结果闪亮登场:
1
RP.MethodA
100
this.MethodA
这个 %$@#^$%&^... , 不要着急,继续看哦!


程序代码
<script language="javascript">
rp = new RP();
delete RP.PropertyA;    // 竟然还有删除属性
alert(RP.PropertyA);
delete RP.MethodA;
RP.MethodA();
delete rp.PropertyA;
alert(rp.PropertyA);
delete rp.MethodA;
rp.MethodA();
</script>
运行结果再次登场:

程序代码
undefined
A Runtime Error has occurred.
Do you wish to Debug?
Line: 32
Error: Object doesn't support this property or method
10
RP.prototype.MethodA


好玩吧,看出来什么名堂了吗? 这里的 RP.PropertyA RP.MethodA 只是用来做参照的,可是怎么把 this.PropertyA this.MethodA delete 了,还能出来结果 ,而且还是 prototype 导入的属性和方法呢?

这就是 JScript prototype prototype pattern prototype 最大的不同了, JScript 中的这个所谓的 prototype 属性其实是个语言本身支持的特性,这里没有发生任何的 copy ,不管 shallow 还是 deep 的。 对于 JScript 的解释引擎,它在处理 "." "[keyName]" 引用的对象的属性和方法时,先在对象本身的实例 (this) 中查找,如果找到就返回或执行。如果没有查找到,就查找对象的 prototype(this.constructor.prototype) 里是否定义了被查找的对象和 方法,如果找到就返回或执行,如果没有查找到,就返回 undefined( 对于属性 ) runtime error( 对于方法 )

正因为 prototype 导 入类实例的属性或方法是动态查找的,所以我们才能对系统内部对象添加 prototype 属 性和方法,比如 String 对象添加 trim 方法:


程序代码
<script lanuage="javascript">
String.prototype.trim()
{
return this.replace(/(^"s+)|("s+$)/g, "");
}
</scritp>

显然 JScript 中 的这种用法也是 prototype pattern 中的 prototype 不能解释和支持的。

这下对于 JScript OOP 中 原型继承法的理解因该没有任何的障碍了吧?同时也应该明白为什么原型继承法有那么大的天生缺陷了吧?当然如果有任何问题,欢迎继续讨论。

附演示示例源代码:


程序代码
<html>
<head>
<meta name="author" content="birdshome@
博客园 ">
<title>JScript Prototype Research</title>
</head>
<body>
<script language="javascript">
function RP()
{
RP.PropertyA = 1;
RP.MethodA = function()
{
alert("RP.MethodA ");
};

this.PropertyA = 100;
this.MethodA = function()
{
alert("this.MethodA");
};
}

RP.prototype.PropertyA = 10;
RP.prototype.MethodA = function()
{
alert("RP.prototype.MethodA");
};
</script>
<script language="javascript">
rp = new RP();
delete RP.PropertyA;
alert(RP.PropertyA);
delete RP.MethodA;
RP.MethodA();
delete rp.PropertyA;
alert(rp.PropertyA);
delete rp.MethodA;
rp.MethodA();
</script>
</body>
</html>

本文着重 解析 javascript 类继承机制,让你从底层了解 javascript 是怎样实现 继承 这一概念的。

                                                                转载自 jimichan

   目前 javascript 的实现继承方式并不是通 过 “extend” 关键字来实现的,而是通过 constructor functionprototype 属性来实现继承。首先我们创建一个 animal

js 代码

 

  1.  var animal = function(){  // 这就是 constructor function  了  
  2.    this.name = 'pipi';    
  3.    this.age = 10;      
  4.    this.height = 0;      
  5. }      
  6.  // 建立一个动物的实例      
  7.  var a1 =  new animal ();   


构造函数与其他普通函数区别在于, 1. 构造函数里有 this 关键字, 2. 调用构造函数是使用的 new 关键字。通过 new 运算符调用构造函数 animal 后,系统就会返回一个对 象,这个对象就相当于

js 代码

  1. var  a1 = { name:'pipi' ,age:10,height:0 }  
  2.   
  3. // 或者   
  4. var  a1 = new Object();  
  5. a1.name='pipi';  
  6. a1.age = 10;  
  7. a1.height = 0; 

等同这样的方式来产生 js 对象。

到这里我们知道如何在 js 中定义一个类了,接下来我们展示如何写 一个 cat

js 代码

 

  1. var  cat = function(){    
  2. this .play = function(){     
  3.  alert('cat play')    
  4.  }    
  5.  }    
  6.  cat .prototype = new animal ();    
  7.  //prototype  属性指向一个对象  
  8. var c1 = new cat();


到这里, cat 就继承了 animal 对象,类 cat 的一个实例对象 c1 拥有属性 name,age,height, 和方法 play 了。
那么 prototype 起到了一个什么样的作用呢?
prototype
就 好比一个指针,它指向一个 object ,这个 object 就称为子类对象的原型。当 cat 的对象被创建的时候,由于 cat 的构造函数拥有 prototype 属性,那么 cat 的实例就会间接指向这个原型对象了(说成间接的是因为每个 object 都有一个 constructor 属性指向它的构造函数)。
那么问题来了, 当我们修改对象 c1 name 属性的时候,会不会修改它 prototypename 属性值呢? ,答案是否定的。
接下来详细解析:
1.
访问 name 属性: 首先当我们第一次访问 c1.name 的属性的时候,我们会得到值 “pipi” ,这个和我们预料中的一样。但是计算过程你未必 知道。
它计算的过程是这样 的:第一步:检查 c1 对象中是否有 name 属性 , 找 到的话就返回值,没有就跳到第二步,显然没有找到,因为 cat 的构造函数中没有定义。第二步:当第一步没有找时,去间接访问 prototype 对象所指向的 object ,如果在 prototype 对象中找到的 name 属性的话,就返回找到的属性值。如果还是没有找到 的话,再去递归地寻找 prototype 对象的 prototype 对象(去找它的爷爷),一直到找到 name 属性或者没有 prototype 对象为止。如果到最后还是没有找到 name 属性的话就返回 undefined

2.
设定 name 属性:当我们设定 c1 对象的 name 属性时,及调用 c1.name= ' new name';  这个 过程就简单多了。首先检查是否对象已有该属性,若已存在则修改当前值,若不存在则为该对象新增一个属性并设定当前值。值得一提的是,在设定值的过程中没有 去访问 prototype 属性。

为了加深理解,我们再 看一个 read-write-read 的过程,第一次 read 的时候,由于自己的对象没有 name 属性,那么就会返回的原型对象的 name 属性的值。第二步,写入 name 的值,同样没发现本身对象有 name 属性 , 那么就在本身对象上新建一个 name 属性,然后赋值。第三步,再次读取 name 属性,由于在第二步中已经新建了 name 属性,此时就返回在第二步中设定的值。值得一提的 是,在这三步中没有改变原型对象的值。

好了,到此详细分析了 javascript 对象是如果实现继承的,其实和其他的面向对象语 言不一样的是, javascript 的继承机制是对象的原型继承而不是类型继承。

呵呵,欢迎看完,有不 对的地方欢迎大家讨论!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值