文章探索:   分类:    关键字:  
  + 栏目导航
  + 相关文章
document 对象
Window.Open详解
JS replace 方法
JScript 属性
JScript 对象
JScript 方法
关于window.opener的用法
JavaScript语法——style.display 属..
不被拦截的弹出窗口代码
showModalDialog和showModelessDialog..
showModelessDialog()使用详解
IE中非模式对话框(showModelessDialog..
JS eval()函数
Preferences 指南
JS中的setTimeout和setInterval的区别
JavaScript对象与数组参考大全
javascript动态增加、删除、填充表格..
用Java实现几种常见的排序算法
JavaScript 日期函数
JavaScript 使用字符串函数
如何用Javascript获得TextArea中的光..
Document 对象方法
在input中只能输入数字
selection.createRange() 用法例子
获取网页各种宽高的值
JavaScript方法 - indexOf方法
substring函数详解
40种网页常用小技巧(javascript)
event.X和event.clientX有什么区别
clientX, clientY,offsetX, offsetY,..


技术教程 -> JavaScript教程 ->  
JavaScript面向对象的支持(4)
来源:转载   人气:730   录入时间:2007-11-8
    
   Qomolangma OpenProject v0.9
   
   
   类别 :Rich Web Client
   关键词 :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component,
    DOM,DTHML,CSS,JavaScript,JScript
   
   项目发起:aimingoo (aim@263.net)
   项目团队:aimingoo, leon(pfzhou@gmail.com)
   有贡献者:JingYu(zjy@cnpack.org)
   =====================================================================
   
   八、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是会有编译过程的。这个过程主要处理的是“语法检错”、“语
   法声明”和“条件编译指令”。而这里的“语法声明”,主要处理的就是函
   数声明。——这也是我说“函数是第一类的,而对象不是”的一个原因。
   
   如下例:
   //---------------------------------------------------------
   // 函数声明与执行语句的关系(firefox 兼容)
   //---------------------------------------------------------
   // 1. 输出1234
   testFoo(1234);
   
   // 2. 尝试输出obj1
   // 3. 尝试输出obj2
   testFoo(obj1);
   try {
    testFoo(obj2);
   }
   catch(e) {
    document.writeln('Exception: ', e.description, '<BR>');
   }
   
   // 声明testFoo()
   function testFoo(v) {
    document.writeln(v, '<BR>');
   }
   
   // 声明object
   var obj1 = {};
   obj2 = {
    toString: function() {return 'hi, object.'}
   }
   
   // 4. 输出obj1
   // 5. 输出obj2
   testFoo(obj1);
   testFoo(obj2);
   
   这个示例代码在JS环境中执行的结果是:
   ------------------------------------
    1234
    undefined
    Exception: 'obj2' 未定义
    [object Object]
    hi, obj
   ------------------------------------
   问题是,testFoo()是在它被声明之前被执行的;而同样用“直接声明”的
   形式定义的object变量,却不能在声明之前引用。——例子中,第二、三
   个输入是不正确的。
   
   函数可以在声明之前引用,而其它类型的数值必须在声明之后才能被使用。
   这说明“声明”与“执行期引用”在JavaScript中是两个过程。
   
   另外我们也可以发现,使用"var"来声明的时候,编译器会先确认有该变量
   存在,但变量的值会是“undefined”。——因此“testFoo(obj1)”不会发
   生异常。但是,只有等到关于obj1的赋值语句被执行过,才会有正常的输出。
   请对照第二、三与第四、五行输出的差异。
   
   由于JavaScript对原型链的维护是“执行”而不是“声明”,这说明“原型
   链是由用户代码来维护的,而不是编译器维护的。
   
   由这个推论,我们来看下面这个例子:
   //---------------------------------------------------------
   // 示例:错误的原型链
   //---------------------------------------------------------
   // 1. 构造器
   function Animal() {}; // 动物
   function Mammal() {}; // 哺乳动物
   function Canine() {}; // 犬科的哺乳动物
   
   // 2. 构造原型链
   var instance = new Mammal();
   Mammal.prototype = new Animal();
   Canine.prototype = instance;
   
   // 3. 测试输出
   var obj = new Canine();
   document.writeln(obj instanceof Animal);
   
   这个输出结果,使我们看到一个错误的原型链导致的结果“犬科的哺乳动
   物‘不是’一种动物”。
   
   根源在于“2. 构造原型链”下面的几行代码是解释执行的,而不是象var和
   function那样是“声明”并在编译期被理解的。解决问题的方法是修改那三
   行代码,使得它的“执行过程”符合逻辑:
   //---------------------------------------------------------
   // 上例的修正代码(部分)
   //---------------------------------------------------------
   // 2. 构造原型链
   Mammal.prototype = new Animal();
   var instance = new Mammal();
   Canine.prototype = instance;
   
   
    3). 原型实例是如何被构造过程使用的
    ------
    仍以Delphi为例。构造过程中,delphi中会首先创建一个指定实例大小的
   “空的对象”,然后逐一给属性赋值,以及调用构造过程中的方法、触发事
   件等。这个过程跟JavaScript中的行为是一致的:
   //---------------------------------------------------------
   // JS中的构造过程(形式代码)
   //---------------------------------------------------------
   function MyObject2() {
    this.prop = 3;
    this.method = a_method_function;
   
    if (you_want) {
    this.method();
    this.fire_OnCreate();
    }
   }
   MyObject2.prototype = new MyObject(); // MyObject()的声明略
   
   var obj = new MyObject2();
   
   如果以单个类为参考对象的,这个构造过程中JavaScript可以拥有与Delphi
   一样丰富的行为。然而,由于Delphi中的构造过程是“动态的”,因此事实上
   Delphi还会调用父类(MyObject)的构造过程,以及触发父类的OnCreate()事件。
   
   JavaScript没有这样的特性。父类的构造过程仅仅发生在为原型(prototype
   属性)赋值的那一行代码上。其后,无论有多少个new MyObject2()发生,
   MyObject()这个构造器都不会被使用。——这也意味着:
    - 构造过程中,原型模板是一次性生成的;对这个原型实例的使用是不断复
    制,而并不再调用原型的构造器。
   
   由于不再调用父类的构造器,因此Delphi中的一些特性无法在JavaScript中实现。
   这主要影响到构造阶段的一些事件和行为。——无法把一些“对象构造过程中”
   的代码写到父类的构造器中。因为无论子类构造多少次,这次对象的构造过程根
   本不会激活父类构造器中的代码。
   
   所以再一次请大家看清楚new()关键字的形式代码中的这一行:
   //---------------------------------------------------------
   // new()关键字的形式化代码
   //---------------------------------------------------------
   function new(aFunction) { // 如果有参数args
    var _this = aFunction.prototype.clone(); // 从prototype中复制一个对象
    // ...
   }
   
   这个过程中,JavaScript做的是“prototype.clone()”,而Delphi等其它语言做
   的是“Inherited Create()”。
   
   




Copyright(C)2007-2024 广州市佳沛数码科技有限公司 版权所有
公司地址: 广州市荔湾区东漖北路560号511室
电话:020-81803473 传真:020-81544987