Mixin对象属性,它们是JavaScript

时间:2016-02-04 16:40:30

标签: javascript constructor prototype mixins composition

我已经阅读了很多JavaScript mixin设计模式,但没有找到适合我需求的模式。我所看到的大部分内容都使用了一个扩展功能,它将一个对象的方法复制到另一个对象的原型中。对于值属性,这与方法一样,但对于属于对象的属性则失败,因为只是复制它们意味着我的类的每个实例都将指向该属性的同一对象。

我用简单的点类创建了一个代码示例:

var Point = function ( x, y ) {
    this.x = x;
    this.y = y;
};

Point.prototype = {
    constructor: Point,
    print: function () {
        console.log( this.x + this.offset[0], this.y + this.offset[1] );
    }
};

print函数使用了一个我尚未声明的偏移属性,因为它源自我想要混合到我的Point类中的Transformable类:

var Transformable = {
    offset: [ 0, 0 ],
    setOffset: function ( x, y ) {
        this.offset[ 0 ] = x;
        this.offset[ 1 ] = y;
    }
};

为了制作mixin,我使用了一些像这样的函数:

var extent = function ( target, mixin ) {
    for ( var property in mixin ) {
        if ( !target.prototype[ property ] ) {
            target.prototype[ property ] = mixin[ property ];
        }
    }
};
extent( Point, Transformable );

现在代码编译并可以使用:

var myPoint1 = new Point( 1, 2 );
myPoint1.setOffset( 5, 5 );
var myPoint2 = new Point( 1, 2 );
myPoint1.print();
myPoint2.print();

但它同时打印“6 7”,因为myPoint2中使用的偏移数组与myPoint1中的相同。

那么我怎样才能实现每个点都有自己的偏移数组,同时仍然来自mixin?它应该适用于除了数组之外的每个对象,因为当不是为了简单的例子时,我会在这里使用向量类。

实际上this answer完成了我想要的,但作为进一步的要求,我希望仍然能够使用对象文字语法来添加Point类的方法:

Point.prototype = {
    constructor: Point,
    print: function () {...},
    ...
};

但是我对mixin对象的语法看起来很灵活。

3 个答案:

答案 0 :(得分:1)

我有一个有效的解决方案,但我自己会认为它有争议。基本上每当我混音时,我都会使用我的mixin类的新实例进行复制。所以我无法实际扩展我的Point类,而是在构造函数中进行mixin。 (使用与问题中相同的范围功能。)

var Transformable = function () {
    this.offset = [ 0, 0 ];
    this.setOffset = function ( x, y ) {
        this.offset[ 0 ] = x;
        this.offset[ 1 ] = y;
    };
};

var Point = function ( x, y ) {
    this.x = x;
    this.y = y;
    extent( this, new Transformable() );
};

Point.prototype = {
    constructor: Point,
    print: function () {
        console.log( this.x + this.offset[0], this.y + this.offset[1] );
    }
};

var myPoint1 = new Point( 1, 2 );
myPoint1.setOffset( 5, 5 );
var myPoint2 = new Point( 1, 2 );

myPoint1.print();
myPoint2.print();

与我在我的问题中链接的多重继承答案相比,我认为它更具可读性,因为我可以使用Point类的对象文字语法,我只需要在哪里调用mixin。

然而,表演可能会成为一个问题,因为我并没有融入课堂,而是融入每一个实例,尽管我不知道这是多么严重。

我欢迎任何准确的论据,为什么这可能是一个糟糕的解决方案。

答案 1 :(得分:1)

回答你的第二篇文章;不,除了Bergi已经提到的内容之外,这个解决方案没什么不好 - 基于函数的Mixin永远不应该被实例化,而是始终通过callapply应用于对象。 性能不会成为一个问题。 JS处理对象,函数委托和处理闭包很快。 你的胜利是关注点的分离,代码重用,仍然坚持ES3语言核心(不需要第三方库),能够引入额外的状态,同时控制如何暴露或隐藏这样的附加状态。

原始帖子提供的重构示例:

var Transformable = function () {       // a function based mixin approach
    var offset = [0, 0];                // is library agnostic and already
                                        // at ES3 language core level enables
    this.setOffset = function (x, y) {  // composition as well as creation of
        offset[0] = x;                  // and/or passsing around additional
        offset[1] = y;                  // state.
    };
    this.getOffsetX = function () {
        return offset[0];
    };
    this.getOffsetY = function () {
        return offset[1];
    };
};

var Point = function (x, y) {

    this.x = x;
    this.y = y;
                                // applying the `Transformable` mixin
    Transformable.call(this);   // onto the new `Point` instance.
};
Point.prototype = {
    constructor: Point,
    print: function () {
        console.log(this.x + this.getOffsetX(), this.y + this.getOffsetY());
    }
};


var myPoint1 = new Point( 1, 2 );
myPoint1.setOffset( 5, 5 );

var myPoint2 = new Point( 1, 2 );

myPoint1.print(); // 6 7
myPoint2.print(); // 1 2

仍然把OP的例子作为起点,对于Point原型print方法,下一个方法可能更清晰。不对任何既不属于该方法的方法做出任何假设constructor也不是prototype ...

var Transformable = function () {
    var offset = [0, 0];

    this.setOffset = function (x, y) {
        offset[0] = x;
        offset[1] = y;
    };
    this.getOffsetX = function () {
        return offset[0];
    };
    this.getOffsetY = function () {
        return offset[1];
    };

    return this;
};


var Point = function (x, y) {

    this.x = x;
    this.y = y;

    return this;
};
Point.prototype.print = function () {

    console.log(this.x, this.y);
};


var TransformablePoint = function () {

    return Transformable.call(Point.apply(this, arguments));
};
TransformablePoint.prototype.print = function () {

    console.log(this.x + this.getOffsetX(), this.y + this.getOffsetY());
};


var myPoint1 = new TransformablePoint( 1, 2 );
myPoint1.setOffset( 5, 5 );

var myPoint2 = new TransformablePoint( 1, 2 );

myPoint1.print(); // 6 7
myPoint2.print(); // 1 2

答案 2 :(得分:0)

在ECMAScript6中,我会使用符号来存储内部数据,并将公共属性定义为使用该数据的访问者:

var Transformable = (function() {
  var defaultOffset = [0, 0],
      offset = Symbol('offset');
  return {
    get offset() {
      if(!this[offset]) this[offset] = Object.create(defaultOffset);
      return this[offset];
    },
    setOffset: function ( x, y ) {
      this.offset[0] = x;
      this.offset[1] = y;
    }
  };
})();

为了复制描述符,您需要更复杂的extend

var extent = function(target, mixin) {
  Object.getOwnPropertyNames(mixin).forEach(function(prop) {
    var desc = Object.getOwnPropertyDescriptor(mixin, prop);
    Object.defineProperty(target, prop, desc);
  });
};
extent(Point.prototype, Transformable);

此时您还可以考虑删除setOffset并为offset定义一个setter,以便您可以使用myPoint1.offset = [5, 5]