什么是好的最低限度Javascript继承方法?

什么是好的最低限度Javascript继承方法?,javascript,inheritance,oop,Javascript,Inheritance,Oop,我正在重写一个JavaScript项目,我希望能够使用面向对象的方法来组织当前代码的混乱。主要的问题是这个JavaScript应该作为第三方网站内的小部件运行,我不能让它与其他网站可能使用的其他JavaScript库冲突 因此,我正在寻找一种在JavaScript中编写“类式”继承的方法,该方法具有以下要求: 没有外部库或与外部库冲突的内容(禁止从外部库复制和粘贴) 极简主义-我不希望支持代码比几行代码大,也不希望开发人员每次定义新类或方法时都需要大量的模板 应该允许动态扩展父对象,以便子对象看

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

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

  • 没有外部库或与外部库冲突的内容(禁止从外部库复制和粘贴)
  • 极简主义-我不希望支持代码比几行代码大,也不希望开发人员每次定义新类或方法时都需要大量的模板
  • 应该允许动态扩展父对象,以便子对象看到更改(原型)
  • 应该考虑构造函数链接
  • 应允许
    super
    类型调用
  • 应该还是会觉得很不舒服
  • 最初,我尝试使用简单的原型链接:

    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不允许超级调用(新函数覆盖父函数),构造函数链接和动态扩展父类不会为子类提供新方法


    欢迎您提出任何建议。

    道格拉斯·克罗克福德(Douglas Crockford)在Javascript中提供了关于继承和继承的优秀文章,这应该是一个很好的起点。

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

    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);
    
    使用一些,您可以使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); // ...
        }
    });
    

    Crockford也受到了启发,但我在使用“构造函数”进行他所谓的“函数继承”方面有很好的经验。YMMV

    更新:很抱歉,我忘了:您仍然需要使用
    高级
    方法来扩充对象,以便更好地访问超级方法。可能不太适合你

    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;
    };
    

    好的,在JavaScript中复制类/实例样式系统的诀窍是,您只能在实例上使用原型继承。因此,您需要能够创建一个仅用于继承的“非实例”实例,并使一个initialiser方法与构造函数本身分离

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

    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= {};
    
    使用
    newfunction()
    可以避免对子类()形成不必要的闭包。如果愿意,可以用更漂亮的
    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); // ...
    };
    

    猴子修补一个内置(函数)有点可疑,但它使阅读起来很愉快,而且没有人可能想在函数上为…编写

    你可以使用Crockford在《JavaScript好的部分》一书中提出的函数模式。其思想是使用closore创建私有字段,并使用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
    

    我在研究这个问题时发现的最常见的模式在。我已经更新了他们的示例,包括对超类方法的调用,并在警报消息中显示日志:

    //形状-超类
    函数形状(){
    这个.x=0;
    这个。y=0;
    }
    //超类方法
    Shape.prototype.move=函数(x,y){
    这个.x+=x;
    这个.y+=y;
    日志+='形状已移动。\n';
    };
    //矩形子类
    函数矩形(){
    Shape.call(this);//调用超级构造函数。
    }
    //子类扩展了超类
    Rectangle.prototype=Object.create(Shape.prototype);
    Rectangle.prototype.constructor=矩形;
    //覆盖方法
    Rectangle.prototype.move=函数(x,y){
    Shape.prototype.move.call(this,x,y);//调用超类方法
    日志+='矩形已移动。\n';
    }
    var log=“”;
    var rect=新矩形();
    log+=('rect是矩形的实例吗?'+(rect instanceof Rectangle)+'\n');//真的
    log+=('rect是形状的实例吗?'+(rect instanceof Shape)+'\n');//真的
    直线移动(1,1);//输出“形状移动”
    
    警报(日志)uber
    beget
    的东西,结果看起来根本不像JavaScript——目的是让训练有素的JavaScript开发人员可以查看代码并立即了解发生了什么。不过,很难不使用一些锅炉板,尝试实现您想要的功能。和。。公平地说:每个受过培训的JavaScript开发人员都应该熟悉Crockford的工作:)您可以调用超级方法。如果要定义“绘制三角形”,可以通过Triangle.prototype.draw获得super draw。问题是“super”方法无法访问当前对象的字段-
    是绘制的原型,而不是
    。我可以做
    Square.prototype.draw.apply(这个,参数)
    ,但这很难,我通常不喜欢方法按名称调用它们的容器类(它们应该在任何地方都使用
    this
    ),那么这个.constructor.prototype.draw.apply(这个,参数)呢@艾尔达:woon不工作,因为覆盖原型会使
    构造函数不可用;它实际上也不算不那么响亮,Square是一个构造函数Square.proot