`
robinqu
  • 浏览: 90289 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

JavaScript Class模拟深入 - 继承、子类

阅读更多
Superclasses and Subclasses

之前的一篇文章讲解的类都是直接继承自Object的,这里会介绍如何在JS中实现继承自任意“类”

引用
Java, C++, and other class-based object-oriented languages have an explicit concept of the class hierarchy.

In JavaScript, the Object class is the most generic, and all other classes are specialized versions, or subclasses, of it. Another way to say this is that Object is the superclass of all the built-in classes, and all classes inherit a few basic methods from Object.

Remember that the prototype object is itself an object; it is created with the Object( ) constructor. This means the prototype object itself inherits properties from Object.prototype!

a chain of prototype objects is involved.


传统的OOP语言都有显示的继承,JavaScript没有这个特性。但是,JavaScript中所有对象实际上都是继承自Object的。换句话说,Object是所有其他类的“超类”。事实上,在JS中,的确其他对象都从Object中继承了一些方法和属性。

但是一个对象不是应该继承自它的Prototype对象么?答案是,Prototype对象本身也是对象啊!你不得不佩服JS的奇妙。

Prototype对象会从Object那里继承那些属性和方法。在复杂的继承树中,一个Prototype的继承链就出现了。

引用
Suppose you want to create a subclass of Rectangle in order to add fields and methods related to the position of the rectangle. To do this, simply make sure that the prototype object of the new class is itself an instance of Rectangle so that it inherits all the properties of Rectangle.prototype.


下面的例子中,演示了继承自Rectangle的一个例子。要这么做,你需要把新“类”的Prototype属性设置为一个Rectangle的实例。这样新的“类”就继承了所有Rectangle的属性和方法。
英文注释相当详细,我也会给出中文注释。

// Here is a simple Rectangle class.
// It has a width and height and can compute its own area
// 先定义一个原始的Rectangle
function Rectangle(w, h) {
    this.width = w;
    this.height = h;
}
Rectangle.prototype.area = function( ) { return this.width * this.height; }

// Here is how we might subclass it
// 从这里开始就子类的定义
function PositionedRectangle(x, y, w, h) {
    // First, invoke the superclass constructor on the new object
    // so that it can initialize the width and height.
    // We use the call method so that we invoke the constructor as a
    // method of the object to be initialized.
    // This is called constructor chaining.
    // 这里要调用“父类”的构造方法来初始化原有属性,这里一定要用call()
    // 因为要特别指明是在这个子类的对象上调用父类的构造方法,否则是没有意义的
    Rectangle.call(this, w, h);

    // Now store the position of the upper-left corner of the rectangle
    // 再来处理子类中独有的属性和方法
    this.x = x;
    this.y = y;
}

// If we use the default prototype object that is created when we
// define the PositionedRectangle( ) constructor, we get a subclass of Object.
// To subclass Rectangle, we must explicitly create our prototype object.
// 修改子类的Prototype属性的值,即为一个Rectangle实例,这样才是继承自Rectangle
// 而不是Object
PositionedRectangle.prototype = new Rectangle( );

// We create this prototype object for inheritance purposes, but we
// don't actually want to inherit the width and height properties that
// each Rectangle object has, so delete them from the prototype.
// 删掉Prototype对象中的Rectangle实例里面的width和height,这些都是是没用的
// 我们在子类的构造方法中已经处理了这些属性,不需要继承,而且由于上一步
// 执行Rectangle()时没有传递参数,这个对象的实例中的属性都是undefined
delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;

// Since the prototype object was created with the Rectangle( ) constructor,
// it has a constructor property that refers to that constructor.  But
// we want PositionedRectangle objects to have a different constructor
// property, so we've got to reassign this default constructor property.
// 改完了Prototype还不行,还要再把Prototype对象的Constructor属性改为指向子类的
// 构造函数。需要这一步是因为,prototype的值在是Rectangle对象,它的constructor还是
// Rectangle,我们在new的时候会出问题
PositionedRectangle.prototype.constructor = PositionedRectangle;

// Now that we've configured the prototype object for our subclass,
// we can add instance methods to it.
// 定义一个子类独有的方法
PositionedRectangle.prototype.contains = function(x,y) {
    return (x > this.x && x < this.x + this.width &&
            y > this.y && y < this.y + this.height);
}


引用
First, there is the issue of invoking the superclass constructor from the subclass constructor. Take care when you do this that the superclass constructor is invoked as a method of the newly created object. Next, there are the tricks required to set the prototype object of the subclass constructor. You must explicitly create this prototype object as an instance of the superclass, then explicitly set the constructor property of the prototype object.
  • Optionally, you may also want to delete any properties that the superclass constructor created in the prototype object because what's important are the properties that the prototype object inherits from its prototype.

  • 大致的步骤如下:
    1、调用父类的构造函数;确保实在当前对象上调用
    2、改变Prototype对象的值,以及Prototype对象的Constructor属性值;这些都需要你显示地给Prototype属性和Prototype.Constructor赋值,这样调用才能在new的时候调用的是子类的构造方法
    3、删掉一些父类构造方法在Prototype对象中创建的、不需要再继承的属性

    使用的示例:

    var r = new PositionedRectangle(2,2,2,2);
    print(r.contains(3,3));  // invoke an instance method
    print(r.area( ));         // invoke an inherited instance method
    
    // Use the instance fields of the class:
    print(r.x + ", " + r.y + ", " + r.width + ", " + r.height);
    
    // Our object is an instance of all 3 of these classes
    print(r instanceof PositionedRectangle &&
          r instanceof Rectangle &&
          r instanceof Object);
    



    Constructor Chaining
    构造函数的链接

    In the example just shown, the PositionedRectangle( ) constructor function needed to explicitly invoke the superclass constructor function. This is called constructor chaining and is quite common when creating subclasses. You can simplify the syntax for constructor chaining by adding a property named superclass to the prototype object of the subclass


    上面的例子里面,子类中显示的调用了父类的构造函数,这就是“Constructor Chaining”,我们可以创建一个新的标记来简化这个过程:

    // Store a reference to our superclass constructor.
    PositionedRectangle.prototype.superclass = Rectangle;
    
    function PositionedRectangle(x, y, w, h) {
        this.superclass(w,h);
        this.x = x;
        this.y = y;
    }


    引用
    Note that the superclass constructor function is explicitly invoked through the this object. This means that you no longer need to use call( ) or apply( ) to invoke the superclass constructor as a method of that object.


    这样的好处是不需要调用call()或是apply()方法,而是通过this.superclass()调用

    Invoking Overridden Methods
    调用被“重载”的方法
    引用

    When a subclass defines a method that has the same name as a method in the superclass, the subclass overrides that method. This is a relatively common thing to do when creating subclasses of existing classes.


    当定义子类时,实例方法如果和父类中的方法同名,那么你就自然“重载”它了。
    例如,下面这个这个示例中,Rectangle和它的子类PositionedRectangle都有toString():

    Rectangle.prototype.toString = function( ) {
        return "[" + this.width + "," + this.height + "]";
    }
    


    If you give Rectangle a toString( ) method, you really must override that method in PositionedRectangle so that instances of the subclass have a string representation that reflects all their properties, not just their width and height properties. PositionedRectangle is a simple enough class that its toString( ) method can just return the values of all properties. 


    PositionedRectangle中的toString()则可以通过调用父类Rectangle的toString()完成部分工作:

    PositionedRectangle.prototype.toString = function( ) {
        return "(" + this.x + "," + this.y + ") " +      // our fields
              Rectangle.prototype.toString.apply(this);  // chain to superclass
    }


    引用
    The superclass's implementation of toString( ) is a property of the superclass's prototype object. Note that you can't invoke it directly. Invoke it with apply( ) so that you can specify the object on which it should be called.


    父类的toString()并没有真正意义上被消除,依旧存在在父类的Prototype中,只是子类的的Prototype中的内容会被优先找到。
    你不能直接调用父类的方法,在调用时,你必须确保实在当前对象上调用。你可以使用apply()和call()方法来帮忙。

    如果你像之前那样定义了superclass属性,你也可以这样:

    PositionedRectangle.prototype.toString = function( ) {
        return "(" + this.x + "," + this.y + ") " +      // our fields
              this.superclass.prototype.toString.apply(this);
    }


    其实保证superclass在创建实例之前定义就可以了,构造函数中用this.superclass()的时候superclass还没有创建也没有关系。其实这个想想只有在new的时候才调用构造函数这一点就明白。

    Extending Without Inheriting
    不继承的扩展

    引用
    Since JavaScript functions are data values, you can simply copy (or "borrow") the functions from one class for use in another.

    由于JS的函数本身是一种数据,你可以轻易的拷贝到你想要的地方,例如这个函数:
    
     // Borrow methods from one class for use by another.
    // The arguments should be the constructor functions for the classes.
    // Methods of built-in types such as Object, Array, Date, and RegExp are
    // not enumerable and cannot be borrowed with this method.
    function borrowMethods(borrowFrom, addTo) {
        var from = borrowFrom.prototype;  // prototype object to borrow from
        var to = addTo.prototype;         // prototype object to extend
    
        for(m in from) {  // Loop through all properties of the prototye
            if (typeof from[m] != "function") continue; // ignore nonfunctions
            to[m] = from[m];  // borrow the method
        }
    }


    这个例子中的函数帮你把一个对象中的方法“借用“到另一个对象中。

    引用
    It is possible to write some methods generically so that they are suitable for use by any class, or by any class that defines certain properties.
    Classes like these that are designed for borrowing are called mixin classes or mixins.


    那么你可以歇一歇通用的方法,定义其他类的时候,你可以简单的“借用”它们,这些被借用的类叫做mixin class或者mixins。下面这个例子中,我们创建了两个通用的类,一个包含通用的toString(),还有一个包含比较对象的通用equal(),并演示了如何使用借用方法:

    // This class isn't good for much on its own. But it does define a
    // generic toString( ) method that may be of interest to other classes.
    function GenericToString( ) {}
    GenericToString.prototype.toString = function( ) {
        var props = [];
        for(var name in this) {
            if (!this.hasOwnProperty(name)) continue;
            var value = this[name];
            var s = name + ":"
            switch(typeof value) {
            case 'function':
                s += "function";
                break;
            case 'object':
                if (value instanceof Array) s += "array"
                else s += value.toString( );
                break;
            default:
                s += String(value);
                break;
            }
            props.push(s);
        }
        return "{" + props.join(", ") + "}";
    }
    
    // This mixin class defines an equals( ) method that can compare
    // simple objects for equality.
    function GenericEquals( ) {}
    GenericEquals.prototype.equals = function(that) {
        if (this == that) return true;
    
        // this and that are equal only if this has all the properties of
        // that and doesn't have any additional properties
        // Note that we don't do deep comparison.  Property values
        // must be === to each other.  So properties that refer to objects
        // must refer to the same object, not objects that are equals( )
        var propsInThat = 0;
        for(var name in that) {
            propsInThat++;
            if (this[name] !== that[name]) return false;
        }
    
        // Now make sure that this object doesn't have additional props
        var propsInThis = 0;
        for(name in this) propsInThis++;
    
        // If this has additional properties, then they are not equal
        if (propsInThis != propsInThat) return false;
        // The two objects appear to be equal.
        return true;
    }
    
    
    // Here is a simple Rectangle class.
    function Rectangle(x, y, w, h) {
        this.x = x;
        this.y = y;
        this.width = w;
        this.height = h;
    }
    Rectangle.prototype.area = function( ) { return this.width * this.height; }
    
    // Borrow some more methods for it
    borrowMethods(GenericEquals, Rectangle);
    borrowMethods(GenericToString, Rectangle);


    Borrow Constructor
    你甚至可以借用构造函数。这样你就像在用“多继承”一样……
    // This mixin has a method that depends on its constructor.  Both the
    // constructor and the method must be borrowed.
    function Colored(c) { this.color = c; }
    Colored.prototype.getColor = function( ) { return this.color; }
    
    // Define the constructor for a new class.
    function ColoredRectangle(x, y, w, h, c) {
        this.superclass(x, y, w, h);  // Invoke superclass constructor
        Colored.call(this, c);        // and borrow the Colored constructor
    }
    
    // Set up the prototype object to inherit methods from Rectangle
    ColoredRectangle.prototype = new Rectangle( );
    ColoredRectangle.prototype.constructor = ColoredRectangle;
    ColoredRectangle.prototype.superclass = Rectangle;
    
    // And borrow the methods of Colored for our new class
    borrowMethods(Colored, ColoredRectangle);


    引用
    Although any kind of strict analogy is impossible, you can think of this as a kind of multiple inheritance. Since the ColoredRectangle class borrows the methods of Colored, instances of ColoredRectangle can be considered instances of Colored as well.


    通过在子类的构造函数中调用另一个父类的构造函数,并且借用那个父类的所有方法,就实现了我们的目的
    分享到:
    评论

    相关推荐

      class-of-builtins.rar_class

      在这个压缩包中,10.1.5-1.js和class-of-builtins.js可能包含了上述概念的示例代码,学习和理解这些内容将有助于深入理解JavaScript中的类和内置类的使用。通过对这两个文件的学习,开发者可以更好地掌握JavaScript...

      在JavaScript中模拟类(class)及类的继承关系_.docx

      在JavaScript中,模拟继承通常通过修改或扩展原型链来实现。有多种实现方式,如使用`prototype`、`call`或`apply`、`Object.create`等。在上述的极简主义法中,如果要让一个类继承另一个类,只需在子类的`creatFn`...

      javascript的prototype继承

      2. **引用原型而不是复制**:在JavaScript中,原型继承是基于引用的,这意味着修改父类的原型会影响到所有子类实例。例如: ```javascript ClassB.prototype.a = 'changed!!'; ``` 这将改变所有`ClassB`实例的`a`...

      es5生成class支持构造传参继承函数复用多继承

      在JavaScript的世界里,ES5是ECMAScript第五版的简称,它是JavaScript的一种标准规范,而ES6(也称为ES2015)引入了`class`语法糖来更方便地处理对象的创建和继承。然而,在ES5时代,我们没有`class`关键字,但可以...

      javascript中类和继承(代码示例+prototype.js)

      本文将深入探讨JavaScript中的类和继承,并结合`prototype.js`文件中的示例进行解析。 ### 类的概念与模拟 在JavaScript中,我们通常使用函数来模拟类的行为。一个函数可以看作是一个类的定义,通过`new`关键字来...

      前端开源库-class-extend

      "class-extend"就是一个这样的开源库,专注于类的扩展功能,类似于JavaScript中的类继承机制。在这个库中,我们可以找到一系列用于处理和扩展类的实用工具,使我们能够更加灵活地构建和组织代码。 在JavaScript中,...

      在JavaScript中模拟类(class)及类的继承关系

      然而,JavaScript的设计允许我们通过其他方式模拟出类似类的行为,尤其是在ES6(ECMAScript 2015)引入了class关键字后,模拟类和继承变得更加直观。在深入探讨如何在JavaScript中模拟类和实现继承之前,我们首先...

      Javascript中3种实现继承的方法和代码实例_.docx

      由于JavaScript本身并不直接支持传统的继承概念,开发者通常通过模拟实现。本文将探讨三种主要的JavaScript继承实现方式:对象冒充、原型继承以及两者的混合。 ### 一、对象冒充 对象冒充是通过调用父类构造函数并...

      JavaScript ES6中CLASS的使用详解

      在此之前,JavaScript中模拟类主要通过原型链来实现,而ES6中的class为面向对象编程(OOP)提供了一种更简洁、更直观的语法。 在ES6之前,JavaScript本身没有类的概念,它是基于原型的语言。原型对象的属性可以被新...

      class-27-constrained-bodies

      在JavaScript中,由于它原生不支持类,我们通常使用原型链或ES6引入的class关键字来模拟类继承。例如,可以创建一个基础的“Body”类,然后定义一个“ConstrainedBody”子类,该子类继承自“Body”并添加特定的约束...

      JavaScript 继承详解(六)

      Prototype.js是一个早期的JavaScript库,它为JavaScript提供了类和继承的模拟,使得在JavaScript中实现面向对象编程变得更加方便。 在早期的Prototype.js版本中,继承的实现相对简单。`Class.create`函数被用来创建...

      javascript 经典面向对象设计

      - **类与对象**:JavaScript没有内置的类的概念,但在ES6中引入了`class`关键字来模拟类的行为。对象则是由类实例化出来的具体实体。 - **封装**:封装是指将数据和操作这些数据的方法绑定在一起,隐藏内部状态,只...

      javascript(课件)

      综上所述,本课件通过习题训练和阶段性的学习任务,帮助学习者深入理解JavaScript中的面向对象编程,包括封装、继承和多态等概念,以及异常处理和接口设计的实践。通过理论与实践相结合,提升编程能力。

      javascript面向对象包装类Class封装类库剖析.docx

      在JavaScript中,由于没有内置的Class关键字,开发者通常会通过函数或者类库来模拟类的行为。文档中提到了一个名为MY-CLASS的GitHub项目,这是一个用于创建和继承JavaScript类的封装库。 MY-CLASS的基本用法展示了...

      详解JavaScript基于面向对象之继承

      虽然`class`关键字在JavaScript中是语法糖,但提供了更接近传统面向对象编程的写法,支持`extends`关键字进行继承。 无论使用哪种方式,JavaScript的继承机制都允许开发者创建层次分明、可扩展的对象模型,提高了...

      JS继承笔记.7z

      下面,我们将深入探讨JavaScript继承的各个方面。 1. 原型链继承: JavaScript的继承基于原型链,每个对象都有一个`__proto__`属性,指向创建它的构造函数的原型。通过`Object.create()`方法可以创建一个新对象并...

      Javascript 面向对象的JavaScript进阶

      尽管JavaScript没有传统意义上的类继承(如Java),但它支持基于原型的继承。通过`__proto__`链或`Object.setPrototypeOf()`方法,子类可以访问和重写父类的方法。 **示例代码:** ```javascript function Animal...

      Javascript实现面向对象技术PPT 超牛 不看后悔

      3. 封装:JavaScript中,对象的属性和方法可以通过访问控制符(如`public`、`private`、`protected`,尽管JavaScript没有严格意义上的访问修饰符)进行封装,通常使用闭包或者属性和方法的前缀来模拟实现。...

      JavaScript面向对象编程指南

      在没有类的情况下,JavaScript使用原型式继承模拟面向对象概念。`Object.create`函数可以创建一个新的对象,该对象的原型是传入的第一个参数。 5. **构造函数、实例和`this`关键字** `this`在JavaScript中表示...

    Global site tag (gtag.js) - Google Analytics