繁体   English   中英

作为JavaScript中对象本身的Mixin对象属性

[英]Mixin object properties that are objects itself in JavaScript

我已经阅读了很多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] );
    }
};

打印功能使用了一个我尚未声明的offset属性,因为它来自于我想混入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();

但是,由于myPoint2中使用的偏移量数组与myPoint1中使用的偏移量数组相同,因此两次都显示“ 6 7”。

那么,如何实现每个点都拥有自己的偏移数组,同时仍然源自mixin? 它也应该适用于除数组之外的每个对象,因为如果不是为了简短示例的简单起见,我将在这里使用向量类。

实际上, 这个答案可以满足我的要求,但是作为进一步的要求,我希望仍然能够使用对象文字语法来添加Point类的方法:

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

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

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

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类的对象文字语法,并且只需要从哪里混合就可以声明一次。

但是,性能可能是个问题,因为我不是混入类中,而是混入每个实例中,尽​​管我不知道那有多严重。

我欢迎任何确切的论点,为什么这可能是一个不好的解决方案。

回答您的第二条帖子; 不,除了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

在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]代替。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM