JavaScript面向对象的支持(4)

发表于:2007-07-01来源:作者:点击数: 标签:
八、JavaScript 面向对象 的支持 ~~~~~~~~~~~~~~~~~~ (续) 3. 构造、析构与原型问题 -------- 我们已经知道一个对象是需要通过构造器函数来产生的。我们先记住几点: - 构造器是一个普通的函数 - 原型是一个对象实例 - 构造器有原型属性,对象实例没有 - (如

八、JavaScript面向对象的支持
~~~~~~~~~~~~~~~~~~
(续)

3. 构造、析构与原型问题
--------
 我们已经知道一个对象是需要通过构造器函数来产生的。我们先记住几点:
   - 构造器是一个普通的函数
   - 原型是一个对象实例
   - 构造器有原型属性,对象实例没有
   - (如果正常地实现继承模型,)对象实例的constructor属性指向构造器
   - 从三、四条推出:obj.constructor.prototype指向该对象的原

 好,我们接下来分析一个例子,来说明JavaScript的“继承原型”声明,以及构造过程。
//---------------------------------------------------------
// 理解原型、构造、继承的示例
//---------------------------------------------------------
function MyObject() {  this.v1 = @#abc@#;
}

function MyObject2() {
  this.v2 = @#def@#;
}
MyObject2.prototype = new MyObject();

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

 1). new()关键字的形式化代码
 ------
 我们先来看“obj1 = new MyObject()”这行代码中的这个new关键字。

new关键字用于产生一个实例(说到这里补充一下,我习惯于把保留字叫关键字。另外,在JavaScript中new关键字同时也是一个运算符),但这个实例应当是从一个“原型的模板”复制过来的。这个用来作模板的原型对象,就是用“构造器函数的prototype属性”所指向的那个对象。对于JavaScript“内置对象的构造器”来说,它指向内部的一个原型。

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

接下来,构造过程将调用MyObject()来完成初始化。——注意,这里只是“初始化”。

为了清楚地解释这个过程,我用代码形式化地描述一下这个过程:
//---------------------------------------------------------
// 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()关键字并不协助维护原型链

以Delphi代码为例,我们在声明继承关系的时候,可以用这样的代码:
//---------------------------------------------------------
// delphi中使用的“类”类型声明
//---------------------------------------------------------
type
  TAnimal = class(TObject); // 动物
  TMammal = class(TAnimal); // 哺乳动物
  TCanine = class(TMammal); // 犬科的哺乳动物
  TDog = class(TCanine);    // 狗

这时,Delphi的编译器会通过编译技术来维护一个继承关系链表。我们可以通过类似以下的代码来查询这个链表:
//---------------------------------------------------------
// delphi中使用继关系链表的关键代码
//---------------------------------------------------------
function isAnimal(obj: TObject): boolean;
begin
  Result := obj is TAnimal;
end;

var
  dog := TDog;

// ...
dog := TDog.Create();
writeln(isAnimal(dog));

可以看到,在Delphi的用户代码中,不需要直接继护继承关系的链表。这是因为Delphi是强类型语言,在处理用class()关键字声明类型时,delphi的编译器已经为用户构造了这个继承关系链。——注意,这个过程是声明,而不是执行代码。

而在JavaScript中,如果需要获知对象“是否是某个基类的子类对象”,那么你需要手工的来维护(与delphi这个例子类似的)一个链表。当然,这个链有不叫类型继承树,而叫“(对象的)原型链表”。——在JS中,没有“类”类型。

参考前面的JS和Delphi代码,一个类同的例子是这样:
//---------------------------------------------------------
// 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 = "直接父类的实例"

这与Delphi一类的语言不同:维护原型链的实质是在执行代码,而非声明。

那么,“是执行而非声明”到底有什么意义呢?

JavaScript是会有编译过程的。这个过程主要处理的是“语法检错”、“语法声明”和“条件编译指令”。而这里的“语法声明”,主要处理的就是函数声明。——这也是我说“函数是第一类的,而对象不是”的一个原因。

[1]    

原文转自:http://www.ltesting.net