原文:Javascript学习6 - 类、对象、继承

Javasciprt并不像C++一样支持真正的类,也不是用class关键字来定义类。Javascript定义类也是使用function关键字来完成的。
    在Javascript中,可以把对象(object)定义为“属性的无序集合”,每个属性存放一个原始值、对象或者函数。理解这一点非常重要。
ECMAScript定义:对象由特性(attribute)构成,特性可以是原始值,可以是引用值,如果特性存放的是函数,它将被看作对象的方法(Method),否则该特性被看作是属性(property).

     ★ 关于对象创建和撤销,有以下几个知识点:

       ● 对象的声明和实例化 : 对象是由关键字new后跟实例化的类的名字所创建,如 var obj = new Object()            ● 对象的引用,在ECMAScript中,不能访问对象的物理表示,只能访问对象的引用。每次创建对象,存储在变量中的都是对象的引用,而不是对象本身。                如上面的obj就是一个对象的引用。             ● 对象废除: ECMAScript有无用存储单元收集程序,当再没有对象的引用时,该对象就被废除(dereference),就相当于.net中的垃圾收集。每当函数执行完代码,无用存储单元收集程序都会运行,释放所有的局部变量。另外,也可以把对象的所有引用都设为null,可以强制废除对象。如果一个对象有两个或更多引用,必须将所有引用设置为null,才能正确废除对象。

★ 关于对象的早绑定(early binding)和晚绑定(late binding)

        ● 所谓绑定,就是把对象的接口与对象的实例结合在一起的方法             ● 早绑定: 在实例化对象前,类已经定义了它的特性和方法,这样编辑器或解释器就能提前转换机器代码。但ECMAScript不是强类型语言,所以不支持早绑定              ● 晚绑定: 指的是编辑器或解释器程序在运行前,不知道对象的类型。使用晚绑定,无需检查对象的类型,只需要检查对象是否支持特性和方法。ECMAScript支持晚绑定。

★ Javascript中的本地对象(native object)、内置对象(built-in object) 、宿主对象(host-object)

        ● 本地对象: 独立于宿主环境的ECMAScript实现并提供的一些对象。简单的说,就是ECMA-262标准所定义的类(引用类型),它们包括:                            Object,Function,Array,String,Boolean,Number,Date,RegExp,Error,EvalError,RangError                            ReferenceError,SyntaxError,TypeError,URIError              ● 内置对象: 由ECMAScript实现提供,独立于宿主环境的所有对象,在ECMAScript程序开始执行时出现。开发者不必明确实例化内置对象,它已经在程序开始时被实例化了。ECMA-262有两个内置对象: Global和Math。 每个内置对象也是本地对象                   需要注意的是Global对象是ECMAScript中最特别的一个对象,因为它实际上根本不存在。尝试编写如下代码将得到错误:      var pointer = Global;                  但是,它又隐含的在ECMAScript中体现,需要理解的一个概念是,在ECMAScript中,不存在独立的函数,那么我们平常使用的 isNaN(),parsetInt(),Eval()函数都属于哪个对象呢?答案就是Global对象。记住的一点是,Global不能实际引用,可以把ECMAScript环境看作就是一个全局的已经被实例化的Global对象。一些方法如isNaN等无需生成Global对象进行引用。                  另外,Global不止有方法,还有属性,这些属性包括undefined,NaN,Object,Array,Function….             ● 宿主对象: 所有的非本地对象都是宿主对象,所有自定义的,各浏览器支持的BOM,DOM对象都是宿主对象。下面主要讨论的就是如何构造宿主对象       

6.1 定义类或对象的三种方式

具体来说,定义类或对象包括了多种方式可以定义,分别是: 对象创建后动态定义,工厂方式,构造函数方式,原型方式,混合构造函数/原型方式,动态原型。下面具体来讨论一下 
       6.1.1 对象创建后动态定义
           在Javascipt中,因为对象的属性可以在对象创建后动态定义,所以一般不需要定义类,创建完对象后再定义它拥有的属性方法也是可行的。如下例所示:

var oCar = new Object();     oCar.color = "red";     oCar.doors = ;     oCar.mpg =;     oCar.showColor = function() { alert(this.color); };   //注意最后的分号不要忘记     

执行以上代码后,就拥有一个个对象oCar, 这个对象拥有color,doors,mpg属性,拥有showColor方法。但是最大的问题是,不能使用创建多个实例【当然,如果说把代码复制下来,对象名变一变,我也无话可说。】

         6.1.2 工厂方法
            这个方法可以通过调用函数来创建对象的实例。就像一个工厂方法一样,调用一下函数,一个对象就出来了。

function createCar(sColor,iDoors,iMpg)     {          var oTempCar = new Object;         oTempCar.color = sColor;         oTempCar.doors = iDoors;         oTempCar.mpg = iMpg;         oTempCar.showColor = function() { alert(this.color); };  //注意最后的分号不要忘记         return oTempCar;  // 返回一个创建的对象。     }     var oCar1  = createCar("red",,);     var oCar1  = createCar("blue",,);     oCar1.showColor();      oCar2.showColor();     

以上,对象可以成批的被创建,但是语义上有问题,1.不像使用new关键字构造对象那么正规,2.每次创建对象,showColor是引用的一个函数对象,该函数引用对于每个对象都有一份实例,即每个对象的showColor都指向自己Function引用。而函数是可以被共享的。
           一个重要的知识点为:一个类中,一般来说,属性是每个对象所私有的,而方法可以作为引用被每个对象所共享(因为方法只是一段代码,并不存放实体数据。)

         6.1.3 构造函数方式
               构造函数第一步选择类名,根据惯例,类名采用首字母大写。变量名小写,私有变量名以_开头。

// Define the constructor.      // Note how it initializes the object referred to by "this".      function Rectangle(w, h) {           this._width = w;           this._height = h;           this.area = function( ) { return this._width * this._height; }  //每个对象都维持一份Copy     // Note: no return statement here        }      var rect1 = new Rectangle(, );    // rect1 = { width:2, height:4 };

以上,就创建一个构造函数,与工厂方法的区别是,在构造函数内不创建对象,而是使用this关键字。然后,使用new运算符进行构造对象。
          像工厂函数一样,使用构造函数方式,会重复生成函数,为每个对象都创建独立的函数版本。
 
        6.1.4 原型方式
           原型方式利用了对象的prototype属性,声明类的步骤与构造函数方式相似,不同的是,所有成员加上prototype修饰

function Rectangle() {  }   // 构造函数为空     Rectangle.prototype._width=;     Rectangle.prototype._height=;     Rectangle.prototype.area=function() {return this._widht*this._height;};     //以上,prototype定义在构造函数外

以上,解决了每个对象都会重复生成函数的问题,但是,又带来一个新的问题,就是私有变量_width,_height也被各个对象所共享了,类的一个对象修改了变量,另一个对象也会被反映出来。所以,最好的生成对象方式是使用构造+原型方式

        6.1.5 混合构造函数/原型方式
             使用这种方式,概念比较简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是每个对象都具有自己的对象属性实例,且所有的函数也只被创建一次。

function Rectangle(w, h) {            this.width = w;            this.height = h;  }             // The prototype object holds methods and other properties that      // should be shared by each instance.      Rectangle.prototype.area = function( ) { return this.width * this.height; }      

var r = new Rectangle(, );      r.hasOwnProperty("width");   // true: width is a direct property of r      r.hasOwnProperty("area");    // false: area is an inherited property of r      "area" in r;                 // true: "area" is a property of r      

6.1.6 动态原型
           使用混合构造/原型方式的唯一缺点就是在类外面也定义了相应的原型方法,不能把这些方法定义在构造函数中,作为一个定义整体。
           使用动态原型可以做到这一点,但需要额外写点代码:

function Rectangle(){         this._width=;         this._height=;         if( typeof Rectangle._initialized==”undefined”) {   //判断是否已经被构造            this.prototype.area=function() {return this._widht*this._height;};            Rectangle._initialized = true;                           // 设定相应的原型函数已经被构造         }     }  

6.2  究竟采用哪一种方式进行构造

在6.1节中,使用了6种方式进行对象的构造,但是,究竟采用哪一种比较好呢?目前最广泛使用的是混合构造函数/原型方式,此外,动态原型也很流行,功能上也与混合构造函数/原型等价,可以采用两种方式中的任何一种
        但是,不要采用单独的构造函数或原型方式,这样会带来问题


6.3 扩展内建的类型 (修改对象)

用户定义的类可以增加原型对象,另外,像String和Date这样内建的类,JS语言已经定义了一些原型对象,如ChatAt,left等。也可以自定义为他们扩展相应的原型对象,如

 // Returns true if the last character is c       String.prototype.endsWith = function(c) {           return (c == this.charAt(this.length-))       }       var message = "hello world";       message.endsWith('h')  // Returns false       message.endsWith('d')  // Returns true       

注:绝对不能为Object.prototype添加属性。因为所有添加的任何属性和方法都可以用一个for/in循环来枚举,但一个空的对象{}(空的对象也属性Object)应该没有可枚举的属性。

6.4 实例方法和this.

在C++/Java面向对象的语言中,在类中访问自身的属性和方法,无需显式调用this.
        但是,在Javascript语言中,必须为这些属性显式指定this关键字。 
        在对象的方法中,关键字this总是指向调用该方法的对象。

6.5 类方法

类方法有点像C++/Java中的静态方法,无需实例化对象即可以进行调用。如Javascript调用Date.Parse()方法。
        类方法是和一个类而不是类的一个实例相关的方法,通过类自身来调用,而不是通过类的一个具体实例来调用。
        类方法通过一个构造函数来调用的,this关键字并不引用类的任何具体实例,相反,它引用的是构造函数自身。通常,一个类方法根本不使用this.
        和类属性一样,类方法也是全局的。
        请参考以下代码,定义了类方法:Circle.max()

// We begin with the constructor      function Circle(radius) {            // r is an instance property, defined and initialized in the constructor.            this.r = radius;        }             // Circle.PI is a class propertyit is a property of the constructor function.      Circle.PI = 3.14159;             // Here is an instance method that computes a circle's area.      // 函数原型      Circle.prototype.area = function( ) { return Circle.PI * this.r * this.r; }             // This class method takes two Circle objects and returns the      // one that has the larger radius.      // 类方法      Circle.max = function(a,b) {            if (a.r > b.r) return a;            else return b;        }             // Here is some code that uses each of these fields:      var c = new Circle(1.0);      // Create an instance of the Circle class      c.r = 2.2;                    // Set the r instance property      var a = c.area( );             // Invoke the area( ) instance method      var x = Math.exp(Circle.PI);  // Use the PI class property in our own computation      var d = new Circle(1.2);      // Create another Circle instance      var bigger = Circle.max(c,d); // Use the max( ) class method            

6.6 通用对象模型

在自定义类的时候,以下几种方法应该考虑定义。
        ①toString()方法
            每个对象都有自己特定的字符串表示,在定义一个类时,应该为它定义一个toString()方法,以便这个类的实例能够为有意义的字符串。
            另个,应该考虑为类添加一个静态parse()方法,用来把toString()方法的字符串输出解析回对象的形式。

Circle.prototype.toString = function ( ) {             return "[Circle of radius " + this.r + ", centered at ("                 + this.x + ", " + this.y + ").]";        }

②valueOf()方法
            与toString()方法相似,是在JS需要把一个对象转换成基本类型的时候才调用的,一般要转换成一个数字而不是一个字符串时,有可能使用到这个函数的地方。函数返回一个基本类型数值。
        ③compareTo()方法
            默认的,JS对于对象是按照运算符的地址来比较对象,而不是按值。给定两个对象引用,它会去查看两个对象是否都引用同一个对象。
            它不会去检查两个不同的对象具有相同的属性名和值。所以可以实现一个compareTo方法来进行自定义对象的比较:

Complex.prototype.compareTo = function(that) {            // If we aren't given an argument, or are passed a value that            // does not have a magnitude( ) method, throw an exception            // An alternative would be to return -1 or 1 in this case to say            // that all Complex objects are always less than or greater than            // any other values.            if (!that || !that.magnitude || typeof that.magnitude != "function")                throw new Error("bad argument to Complex.compareTo( )");                     // This subtraction trick returns a value less than, equal to, or            // greater than zero.  It is useful in many compareTo( ) methods.            return this.magnitude( ) - that.magnitude( );        }      

6.7 确定对象类型

Javascript是一个松散类型的语言,对象也是松散的类型,有以下几种方法可以用来确定JS中一个任意值的类型。
        ①typeof方法
            使用typeof有几点要注意:
                typeof null 是 "object"
                typeof undefined 是 "undefined"
                任何数组类型都是"object"
                任何函数类型都是"function",尽管函数也是对象!
        ②instanceof方法
            一旦确定一个值是对象而不是基本类型或者函数,就可以使用instanceof运算符来详细了解它。如果x是一个数组,如下的表达式为true
            x instanceof Array // return true;
            对象是自己的类的一个实例,也是任何超类的一个实例 。所以,以下方法也为true
            x instanceof Object // return true;
        ③构造函数方法
            如果要测试一个对象是一个具体的类的一个实例(而不是超类),可以查看对象的constructor属性,如下代码:

var d = new Date( );                     // A Date object; Date extends Object      var isobject = d instanceof Object;     // evaluates to true      var realobject = d.constructor==Object; // evaluates to false

④Object.toString()方法测试
            instancesof和constructor测试的方法一个缺点是,需要根据已经知道的类来进行测试,无法检测未知的对象。
            Object定义了一个默认的toString()方法,任何没有定义自己的toString()方法的类,都会继承这个默认的实现。
            默认的toString()方法总是返回以下形式的一个字符串:
                [object class]
            class是对象的内部类型,通常和该对象的构造函数名字相对应。例如,数组的class是Array,函数的class是Function...
        以上,要查看更具体的介绍,请参见《Javascript权威指南》 9.7节。

6.8 Javascript中类的继承

和定义类的功能一样,ECMAScript中实现继承的方式不止一种,这是因为Javascript中的继承机制并不是明确规定的,而是通过模仿来实现的。
     下面来讨论几种实现继承的方式:
     6.8.1 对象冒充

<script type="text/javascript" language="javascript">    function ClassA(sColor) {        this.color = sColor;        this.sayColor = function() { alert(this.color); };    }    function ClassB(sColor, sName) {        this.newMethods = ClassA;  // ClassA的构造函数成为ClassB的方法        this.newMethods(sColor);    // 商用ClassA的构造函数,ClassB会收到ClassA的构造函数中定义的属性和方法        delete this.newMethods;      // 删除ClassA的引用        this.name = sName;        this.sayName = function() { alert(this.name); };    }    var objB = new ClassB("blue", "Nicholas");    objB.sayColor();    objB.sayName();    alert(objB instanceof ClassA);  // 返回false     alert(objB instanceof ClassB);  // 返回true</script> 

以上,ClassA为构造函数,其实也只是一个函数,所以ClassA的构造子数成为了ClassB的一个方法,然后调用该方法,在ClassB中引入了ClassA()构造函数中定义的属性和方法。最后删除对ClassA的引用。这样一来,相当于ClassA构造函数中定义的属性和方法成为了ClassB中定义的属性及方法。
        对于instanceof方法,在使用对象冒充继承时,如果使用instanceof <基类名>是返回false的。如果使用了原型链继承,则会返回true.原型链继承在6.8.3节中介绍。
        需要指出的是,对象冒充方法可以支持多重继承。

function ClassZ() {    this.newMethod = ClassX;    this.newMethod();    delete this.newMethod;    this.newMethod = ClassY;    this.newMethod();    delete this.newMethod;} 

如果ClassX和ClassY具有同名的属性或方法,ClassY具有高优先级(后面定义的会覆盖前面定义的)
       另外,有继承类中的新属性和新方法必须在删除了新方法的代码行后定义,否则,同名的属性和函数会被基类所覆盖。
       需要强调的是:这样的冒充方法只适合所有的属性及方法在构造函数中进行定义的类。对于原型方法,不适用。对于原型继承,使用原型链方法,后面谈到。

    6.8.2 call()和apply()方法
       ● call方法与经典的对象冒充方法最相似,它第一个参数用作this的对象,表示需要被继承的对象,其它参数都直接传递给基类构造函数的。

function ClassB(sColor, sName) {    //this.newMethod = ClassA;    //this.newMethod(sColor);    //delete this.newMethod;    ClassA.call(this, sColor);  // 很简便,一行相当于上面三行。    this.name = sName;    this.sayName = function () { alert(this.name); };}// 使用直接定义对象方法。从相应的对象中继承。function sayColor(sPrefix, sSuffix) {    alert(sPrefix + this.color + sSuffix);};var obj = new Object();obj.color = “red”;sayColor.call(obj, “The color is “, “, a very nice color indeed. “);  // 表示obj对象继承自sayColor. 

● apply方法有两个参数,分别为this对象(即需要被继承的对象)和要传递给基类构造函数参数的数组。例如:

function ClassB(sColor, sName) {    //this.newMethod = ClassA;    //this.newMethod(sColor);    //delete this.newMethod;    ClassA.apply(this, new Array(sColor));  // 与call不同的是,第二个参数是一个参数数组.    this.name = sName;    this.sayName = function () { alert(this.name); };} 

6.8.3 原型链继承
       前面提到过,prototype对象是个模板,实例化的对象都是以这个模板为基础,进行对象的构造。那么,基类中使用prototype定义的属性和方法怎么被子类继承呢?

function ClassA(sColor) {    this.color = sColor;}ClassA.prototype.sayColor = function() { alert(this.color); };  // 原型方法 

如果需要继承ClassA中的原型方法,需要在子类中使用以下语句: ClassB.prototype = new ClassA();

function ClassB(sColor, sName) {    ClassA.call(this, sColor);   // 继承了ClassA中的构造方法    this.name = sName;    this.sayName = function() { alert(this.name); };}ClassB.prototype = new ClassA();  // 继承了ClassB中的原型方法var objB = new ClassB("blue", "Nicholas");objB.sayColor();objB.sayName();alert(objB instanceof ClassA);  // 返回true alert(objB instanceof ClassB);  // 返回true

在原型链继承中,有一点与对象冒充不同,就是使用<对象名> instanceof <基类>运算,是返回True
       还有一点:原型链不支持多重继承,因为,原型链会用另一个类型的对象重写类的prototype属性。

   6.8.4 关于继承的方式
       一般来说,在Javascript中,继承一个类,对象冒充及原型链方式都会被混合在一起使用,如6.8.3中的例子所示。使用对象冒充继承构造函数的属性,使用原型链继承prototype对象的方法。

对象的构造和继承就讲到这儿,这样的继承方式是在JS语言中很通用的做法,当然,可以使用其它的JS库来实现对象构造和继承。使用JS库可以解决JS中继承机制的一些缺陷,如支持namespace,能简单访问父类方法,支持接口…
       在ASP.NET AJAX中,会讲到ASP.NET中Microsoft Javascript Library怎么样来实现对象,继承,接口等操作的。ASP.NET AJAX详见:

Javascript学习6 - 类、对象、继承的更多相关文章

  1. JavaScript学习08 Cookie对象

    JavaScript学习08 Cookie对象 JavaScript Cookie Cookie对象: Cookie是一种以文件的形式保存在客户端硬盘的Cookies文件夹中的用户数据信息(Cooki ...

  2. JavaScript里的类和继承

    JavaScript与大部分客户端语言有几点明显的不同: JS是 动态解释性语言,没有编译过程,它在程序运行过程中被逐行解释执行JS是 弱类型语言,它的变量没有严格类型限制JS是面向对象语言,但 没有 ...

  3. JavaScript中的类式继承和原型式继承

    最近在看<JavaScript设计模式>这本书,虽然内容比较晦涩,但是细品才发现此书内容的强大.刚看完第四章--继承,来做下笔记. 书中介绍了三种继承方式,类式继承.原型式继承和掺元类继承 ...

  4. JavaScript学习总结 之对象

    JavaScript学习总结(二) ---- 对象 在JavaScript中,几乎用到的每个js都离不开它的对象.下面我们深入了解一下js对象. js中对象的分类跟之前我们学过的语言中函数的分类一样, ...

  5. python中的面向对象学习以及类的继承和继承顺序

    继承 首先编写一串关于类的代码行: __author__ = "Yanfeixu" # class People: 经典类不用加(object) class People(obje ...

  6. [Java学习]面向对象-类的继承;方法覆盖

    一.类的继承 实现方法: public Class SubClass extends SuperClass{ } 继承最基本作用: 代码重用. 继承最重要的作用: 方法可以重写. 关于类的继承: 子类 ...

  7. JavaScript:学习笔记(8)——对象扩展运算符

    JavaScript:学习笔记(8)——扩展运算符 对象的扩展运算符 扩展运算符是三个点(...).用于取出参数对象的所有可遍历属性,然后拷贝到当前对象之中. 如上图所示,新建了一个对象a,然后通过扩 ...

  8. C#图解教程学习笔记——类和继承

    一.屏蔽基类的成员所有类都派生自object类.虽然类只能直接继承一个基类,但继承的层次没有限制.虽然派生类不能删除它继承的任何成员,但可以用与基类同名的成员来屏蔽(mask)基类成员.1. 要屏蔽一 ...

  9. JavaScript学习笔记之对象

    目录 1.自定义对象 2.Array 3.Boolean 4.Date 5.Math 6.Number 7.String 8.RegExp 9.Function 10.Event 在 JavaScri ...

随机推荐

  1. 自己实现的Boost库中的lexical_cast随意类型转换

    知道了C++的I/O设施之后.这些就变的非常easy了. 假设你常常使用,时间长了就会有感觉.这个事情是多此一举吗?就当是练习吧,知道原理之后,你会认为用起来更舒畅,更喜欢C++了. #include ...

  2. Tomcat启动错误,端口占用

    错误信息: Several ports (8080, 8009) required by Tomcat v7.0 Server at localhost are already in use. The ...

  3. Apache与Tomcat整合(转)

    一 Apache与Tomcat比较联系 apache支持静态页,tomcat支持动态的,比如servlet等. 一般使用apache+tomcat的话,apache只是作为一个转发,对jsp的处理是由 ...

  4. 在基于阿里云serverCentOS6.5下安装Subversion 1.6.5服务

    近期阿里云搞了个1元免费提供云server的活动,偶心痒痒就申请了一个. 正好能够作为团队的SVNserver了,以下就来部署SVN服务吧. 一.安装基础环境 apr-1.5.0.tar.gz apr ...

  5. Sails.js中文文档

    Sails.js中文文档   http://sailsdoc.swift.ren/ Sails.js是一个Web框架,可以于轻松构建自定义,企业级Node.js Apps.它在设计上类似于像Ruby ...

  6. Linux 环境下 Lua 安装(转)

    系统环境:CentOS-6.2-x86_64. Lua 是嵌入式脚本语言,应用场景很广泛. 引自官网:Lua is used in many products and projects around ...

  7. base 64 编解码器

    base 64 编解码 1. base64的编码都是按字符串长度,以每3个8bit的字符为一组, 2. 然后针对每组.首先获取每一个字符的ASCII编码. 3. 然后将ASCII编码转换成8bit的二 ...

  8. 任意长度的正小数的加法(YT新人之巅峰大决战05)

    Problem Description 话说,经过了漫长的一个多月,小明已经成长了许多,所以他改了一个名字叫"大明". 这时他已经不是那个只会做100以内加法的那个"小明 ...

  9. HYSBZ 2243 染色 (树链拆分)

    主题链接~~> 做题情绪:这题思路好想.调试代码调试了好久.第一次写线段树区间合并. 解题思路: 树链剖分 + 线段树区间合并 线段树的端点记录左右区间的颜色.颜色数目.合并的时候就用区间合并的 ...

  10. 非常棒的Visual Studo调试插件:OzCode 2.0 下载地址

    最新版下载地址 http://download.csdn.net/detail/simadi/8925511 如果你是一名C#开发者,那么,你则需要OzCode.它将可视化调试的概念上升到了一个新的高 ...