代码之家  ›  专栏  ›  技术社区  ›  Guss

什么是一个好的最低限度的javascript继承方法?

  •  6
  • Guss  · 技术社区  · 15 年前

    我正在重写一个JavaScript项目,我希望能够使用面向对象的方法来组织当前代码的混乱。主要的问题是,这个javascript应该作为第三方网站中的小部件运行,我不能让它与其他网站可能使用的其他javascript库冲突。

    因此,我正在寻找一种在javascript中编写“类式”继承的方法,它具有以下要求:

    1. 没有外部库或与外部库冲突的内容(排除从外部库复制和粘贴)。
    2. 最小化——我不希望支持代码比几行代码大,也不希望开发人员每次定义新的类或方法时都需要大量的样板。
    3. 应该允许动态扩展父对象,以便子对象看到更改(原型)。
    4. 应允许构造函数链接。
    5. 应该考虑 super 类型调用。
    6. 还是应该感觉到javascript的味道。

    最初我尝试使用简单的原型链接:

    function Shape(x,y) {
      this.x = x;
      this.y = y;
    
      this.draw = function() {
        throw new Error("Arbitrary shapes cannot be drawn");
      }
    }
    
    function Square(x,y,side) {
      this.x = x;
      this.y = y;
      this.side = side;
    
      this.draw = function() {
        gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); ...
      }
    }
    Square.prototype = new Shape();
    

    这解决了需求1、2和6,但ID不允许超级调用(新函数重写父函数),构造函数链接和动态扩展父类不向子类提供新方法。

    欢迎提出任何建议。

    6 回复  |  直到 9 年前
        1
  •  5
  •   Christoph    10 年前

    我建议使用以下模式 clone function 要从原型继承而不是从实例继承:

    function Shape(x, y) {
        this.x = x;
        this.y = y;
    }
    
    Shape.prototype.draw = function() {
        throw new Error('Arbitrary shapes cannot be drawn');
    };
    
    function Square(x,y,side) {
        Shape.call(this, x, y); // call super constructor
        this.side = side;
    }
    
    // inherit from `Shape.prototype` and *not* an actual instance:
    Square.prototype = clone(Shape.prototype);
    
    // override `draw()` method
    Square.prototype.draw = function() {
        gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
    };
    

    方法驻留在原型中是很重要的(因为性能原因,这无论如何都应该如此),因此可以通过调用超类的方法

    SuperClass.prototype.aMethod.call(this, arg1, arg2);
    

    与一些 syntactic sugar ,您可以使JS看起来像一种基于类的经典语言:

    var Shape = Class.extend({
        constructor : function(x, y) {
            this.x = x;
            this.y = y;
        },
        draw : function() {
            throw new Error('Arbitrary shapes cannot be drawn');
        }
    });
    
    var Square = Shape.extend({
        constructor : function(x, y, side) {
            Shape.call(this, x, y);
            this.side = side
        },
        draw : function() {
            gotoXY(this.x,this.y); lineTo(this.x+this.side, this.y); // ...
        }
    });
    
        2
  •  4
  •   Andrzej Doyle    15 年前

    道格拉斯·克罗克福德在这两方面都有很好的文章 classical prototypal 在javascript中继承,这应该是很好的起点。

        3
  •  1
  •   bobince    15 年前

    好的,在javascript中复制类/实例样式系统的诀窍是您只能对实例使用原型继承。因此,您需要能够创建一个只用于继承的__non-instance_实例,并且让一个初始化器方法与构造函数本身分离。

    这是我使用的最小系统(在添加虚饰之前),将一个特殊的一次性值传递给构造函数,让它在不初始化对象的情况下构造一个对象:

    Function.prototype.subclass= function() {
        var c= new Function(
            'if (!(this instanceof arguments.callee)) throw(\'Constructor called without "new"\'); '+
            'if (arguments[0]!==Function.prototype.subclass._FLAG && this._init) this._init.apply(this, arguments); '
        );
        if (this!==Object)
            c.prototype= new this(Function.prototype.subclass._FLAG);
        return c;
    };
    Function.prototype.subclass._FLAG= {};
    

    使用 new Function() 是一种避免在子类()上形成不必要的闭包的方法。你可以换个漂亮点的 function() {...} 表达,如果你愿意。

    用法相对简单,通常与Python样式的对象类似,只是语法稍显笨拙:

    var Shape= Object.subclass();
    Shape.prototype._init= function(x, y) {
        this.x= x;
        this.y= y;
    };
    Shape.prototype.draw= function() {
        throw new Error("Arbitrary shapes cannot be drawn");
    };
    
    var Square= Shape.subclass();
    Square.prototype._init= function(x, y, side) {
        Shape.prototype._init.call(this, x, y);
        this.side= side;
    };
    Square.prototype.draw= function() {
        gotoXY(this.x, this.y);
        lineTo(this.x+this.side, this.y); // ...
    };
    

    猴子修补一个内置的(功能)是有点可疑的,但它让人读起来很愉快,而且没有人愿意这样做。 for...in 在函数上。

        4
  •  1
  •   Don Kirkby    9 年前

    我在研究这个问题时发现的最常见的模式是 Mozilla Developer Network . 我更新了他们的示例,以包含对超类方法的调用,并在警报消息中显示日志:

    // Shape - superclass
    function Shape() {
      this.x = 0;
      this.y = 0;
    }
    
    // superclass method
    Shape.prototype.move = function(x, y) {
      this.x += x;
      this.y += y;
      log += 'Shape moved.\n';
    };
    
    // Rectangle - subclass
    function Rectangle() {
      Shape.call(this); // call super constructor.
    }
    
    // subclass extends superclass
    Rectangle.prototype = Object.create(Shape.prototype);
    Rectangle.prototype.constructor = Rectangle;
    
    // Override method
    Rectangle.prototype.move = function(x, y) {
      Shape.prototype.move.call(this, x, y); // call superclass method
      log += 'Rectangle moved.\n';
    }
    
    var log = "";
    var rect = new Rectangle();
    
    log += ('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle) + '\n'); // true
    log += ('Is rect an instance of Shape? ' + (rect instanceof Shape) + '\n'); // true
    rect.move(1, 1); // Outputs, 'Shape moved.'
    alert(log);
    1. 在您提出这个问题的五年中,似乎对继承的浏览器支持有所改善,所以我认为您不需要外部库。
    2. 这是我见过的最简单的技术,我不知道你是否认为这个样板太多了。
    3. 它根据需要使用原型,因此向父对象添加新方法也应该向子对象提供这些方法。
    4. 您可以在示例中看到构造函数链接。
    5. 示例中也有超类型调用。
    6. 我不确定它是否感觉像javascript,你必须自己决定。
        5
  •  0
  •   Hoa Nguyen    12 年前

    你可以使用Crockford在他的书“javascript的好部分”中提出的功能模式。其思想是使用closare创建私有字段,并使用previleged函数访问这些字段。以下是满足您6项要求的解决方案之一:

        var people = function (info) {
        var that = {};
        // private
        var name = info.name;
        var age = info.age;
        // getter and setter
        that.getName = function () {
            return name;
        };
        that.setName = function (aName) {
            name = aName;
        };
        that.getAge = function () {
            return age;
        };
        that.setAge = function (anAge) {
            age = anAge;
        };
        return that;
    };
    
    var student = function (info) {
        // super
        var that = people(info);
        // private
        var major = info.major;
        that.getMajor = function () {
            return major;
        };
        that.setMajor = function (aMajor) {
            major = aMajor;
        };
        return that;
    };
    
    var itStudent = function (info) {
        // super
        var that = student(info);
        var language = info.language;
        that.getLanguage = function () {
            return language;
        };
        that.setLanguage = function (aLanguage) {
            language = aLanguage;
        };
        return that;
    };
    
    var p = person({name : "Alex", age : 24});
    console.debug(p.age); // undefined
    console.debug(p.getAge()); // 24
    
    var s = student({name : "Alex", age : 24, major : "IT"});
    console.debug(s.getName()); // Alex
    console.debug(s.getMajor()); // IT
    
    var i = itStudent({name : "Alex", age : 24, major : "IT", language : "js"});
    console.debug(i.language); // Undefined
    console.debug(i.getName()); // Alex
    console.debug(i.getMajor()); // IT
    console.debug(i.getLanguage()); // js
    
        6
  •  -1
  •   Fabian    15 年前

    克罗克福德也启发了我,但我对他使用“构造函数函数”所说的“功能继承”有很好的经验。YMMV。

    更新 :对不起,我忘了:您仍然需要用 superior 方法以获得对超级方法的良好访问。可能不太适合你。

    var makeShape = function (x, y) {
        that = {};
        that.x = x;
        that.y = y;
        that.draw = function() {
            throw new Error("Arbitrary shapes cannot be drawn");
        }
        return that;
    };
    
    var makeSquare = function (x, y, side) {
        that = makeShape(x, y);
        that.side = side;
        that.draw = function() {
            gotoXY(that.x,that.y); lineTo(that.x+that.side, that.y); ...
        }
        return that;
    };