日期:2014-05-16  浏览次数:20397 次

JavaScript prototype学习笔记(转)
prototype源自法语,软件界的标准翻译为“原型”,代表事物的初始形态,也含有模型和样板的意义。学习了Java的面向对象思想,关于prototype的一些语法就很容易理解了,如属性、方法、继承、多态
JavaScript的所有function类型的对象都有一个prototype属性。这个prototype属性本身又是一个object类型的对象,因此我们也可以给这个prototype对象添加任意的属性和方法。既然prototype是对象的“原型”,那么由该函数构造出来的对象应该都会具有这个“原型”的特性。事实上,在构造函数的prototype上定义的所有属性和方法,都是可以通过其构造的对象直接访问和调用的。也可以这么说,prototype提供了一群同类对象共享属性和方法的机制。


//———————————————————
// 理解原型、构造、继承的示例
//———————————————————
function MyObject() {
this.v1 = ‘abc’;
}

function MyObject2() {
this.v2 = ‘def’;
}
MyObject2.prototype = new MyObject();

var obj1 = new MyObject();
var obj2 = new MyObject2();

alert(obj1.v1 + ” ” + obj2.v2 + ” ” + obj2.v1);

由alert可知道MyObject2具有了MyObject对象的属性

1). new()关键字的形式化代码
我们先来看“obj1 = new MyObject()”这行代码中的这个new关键字。
new关键字用于产生一个实例,但这个实例应当是从一个“原型的模板”复制过来的。这个用来作模板的原型对象,就是用“构造器
函数的prototype属性”所指向的那个对象。对于JavaScript“内置对象的构造器”来说,它指向内部的一个原型。

每一个函数,无论它是否用作构造器,都会有一个独一无二的原型对象。缺省时JavaScript用它构造出一个“空的初始对象实例(不是null)”。然而如果你给函数的这个prototype赋一个新的对象,那么构造过程将用这个新对象作为“模板”。

为了清楚地解释这个过程,我用代码形式化地描述一下这个过程:
//———————————————————
// new()关键字的形式化代码
//———————————————————
function new(aFunction) { // 如果有参数args
var _this = aFunction.prototype.clone(); // 从prototype中复制一个对象
aFunction.call(_this); // 调用构造函数完成初始化, (如果有,)传入args
return _this; // 返回对象
}

所以我们看到以下两点:
- 构造函数(aFunction)本身只是对传入的this实例做“初始化”处理,而不是构造一个对象实例。

- 构造的过程实际发生在new()关键字/运算符的内部。而且,构造函数(aFunction)本身并不需要操作prototype,也不需要回传this。

2). 由用户代码维护的原型(prototype)链
接下来我们更深入的讨论原型链与构造过程的问题。这就是:
- 原型链是用户代码创建的,new()关键字并不协助维护原型链

//———————————————————
// JS中“原型链表”的关键代码
//———————————————————
// 1. 构造器
function Animal() {};
function Mammal() {};
function Canine() {};
function Dog() {};

// 2. 原型链表
Mammal.prototype = new Animal();
Canine.prototype = new Mammal();
Dog.prototype = new Canine();

// 3. 示例函数
function isAnimal(obj) {
return obj instanceof Animal;
}

var dog = new Dog();
document.writeln(isAnimal(dog));

可以看到,在JS的用户代码中,“原型链表”的构建方法是一行代码:
“当前类的构造器函数”.prototype = “直接父类的实例”

3). 原型实例是如何被构造过程使用的
父类的构造过程仅仅发生在为原型(prototype属性)赋值的那一行代码上。其后,无论有多少个new MyObject2()发生,
MyObject()这个构造器都不会被使用。——这也意味着:
- 构造过程中,原型模板是一次性生成的;对这个原型实例的使用是不断复 制,而并不再调用原型的构造器。

我们先来看看下面的代码:

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”);   //创建BillGates对象
var SteveJobs = new Person(“Steve Jobs”);   //创建SteveJobs对象

BillGates.SayHello();   //通过BillGates对象直接调用到SayHello方法
SteveJobs.SayHello();   //通过SteveJobs对象直接调用到SayHello方法

alert(BillGates.SayHello == SteveJobs.SayHello); //因为两个对象是共享prototype的SayHello,所以显示:true

程序运行的结果表明,构造函数的prototype上定义的方法确实可以通过对象直接调用到,而且代码是共享的。显然,把方法设置到prototype的 写法显得优雅多了,尽管调用形式没有变,但逻辑上却体现了方法与类的关系,相对前面的写法,更容易理解和组织代码。

在JavaScript中,prototype不但能让对象共享自己财富,而且prototype还有寻根问祖的天性,从而使得先辈们的遗产可以代代相 传。当从一个对象那里读取属性或调用方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找;如果 prototype没有,又会去prototype自己关联的前辈prototype那里寻找,直到找到或追溯过程结束为止。

在JavaScript内部,对象的属性和方法追溯机制是通过所谓的prototype链来实现的。当用new操作符构造对象时,也会同时将构造函数的 prototype对象指派给新创建的对象,成为该对象内置的原型对象。对象内置的原型对象应该是对外不可见的,尽管有些浏览器(如Firefox)可以 让我们访问这个内置原型对象,但并不建议这样做。内置的原型对象本身也是对象,也有自己关联的原型对象,这样就形成了所谓的原型链。

在原型链的最末端,就是Object构造函数prototype属性指向的那一个原型对象。这个原型对象是所有对象的最老祖先,这个老祖宗实现了诸如 toString等所有对象天生就该具有的方法。其他内置构造函数,如Function, Boolean, String, Date和RegExp等的prototype都是从这个老祖宗传承下来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的那些 特征。