什么是一个很好的简约Javascript继承方法?

时间:2009-09-10 10:47:22

标签: javascript inheritance oop

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

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

  1. 没有外部库或与外部库冲突的东西(阻止从外部库复制和粘贴)。
  2. 简约 - 我不希望支持代码比几行代码更大,我不希望开发人员每次定义新的类或方法时都需要大量的样板。
  3. 应该允许动态扩展父对象,以便子对象看到更改(原型)。
  4. 应该允许构造函数链接。
  5. 应允许super类型的来电。
  6. 还应该感受到JavaScript-ish。
  7. 最初我尝试使用简单的原型链接:

    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 个答案:

答案 0 :(得分:5)

我建议使用以下模式,使用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); // ...
    }
});

答案 1 :(得分:4)

道格拉斯·克罗克福德在Javascript中有关于classicalprototypal继承的好文章,这应该是一个很好的起点。

答案 2 :(得分:1)

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

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

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函数。

答案 3 :(得分:1)

我在研究这个问题时发现的最常见的模式在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,你必须自己决定。

答案 4 :(得分:0)

你可以使用Crockford在他的书“JavaScript the good parts”中提出的功能模式。 Idea是使用closore来创建私有字段,并使用普通函数来访问这些字段。以下是满足您的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

答案 5 :(得分:-1)

同样受Crockford启发,但我使用“构造函数”对他所谓的“功能继承”有很好的体验。 YMMV。

更新:抱歉,我忘记了:您仍需要使用superior方法扩充Object,以便更好地访问超级方法。不太可能适合你。

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