在JavaScript中作为对象本身的Mixin对象属性
我已经阅读了很多JavaScript混合设计模式,但没有找到一个适合我的需要。我所看到的大部分内容都使用某种程度的函数,将一个对象的方法复制到另一个对象的原型。对于值属性,它的工作方式与方法类似,但对于本身就是对象的属性,它会失败,因为复制它们意味着类的每个实例都将指向该属性的同一对象 我用一个简单的point类制作了一个代码示例:在JavaScript中作为对象本身的Mixin对象属性,javascript,constructor,prototype,mixins,composition,Javascript,Constructor,Prototype,Mixins,Composition,我已经阅读了很多JavaScript混合设计模式,但没有找到一个适合我的需要。我所看到的大部分内容都使用某种程度的函数,将一个对象的方法复制到另一个对象的原型。对于值属性,它的工作方式与方法类似,但对于本身就是对象的属性,它会失败,因为复制它们意味着类的每个实例都将指向该属性的同一对象 我用一个简单的point类制作了一个代码示例: var Point = function ( x, y ) { this.x = x; this.y = y; }; Point.prototyp
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] );
}
};
var Transformable = {
offset: [ 0, 0 ],
setOffset: function ( x, y ) {
this.offset[ 0 ] = x;
this.offset[ 1 ] = y;
}
};
Point.prototype = {
constructor: Point,
print: function () {...},
...
};
print函数使用了一个我还没有声明的offset属性,因为它源自一个我想混入Point类的可转换类:
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] );
}
};
var Transformable = {
offset: [ 0, 0 ],
setOffset: function ( x, y ) {
this.offset[ 0 ] = x;
this.offset[ 1 ] = y;
}
};
Point.prototype = {
constructor: Point,
print: function () {...},
...
};
要制作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开始的情况下实现每个点都有自己的偏移数组呢?除了数组之外,它还应该适用于每个对象,因为如果不是为了简单起见,我会在这里使用向量类
实际上实现了我想要的功能,但作为进一步的要求,我希望仍然能够使用object literal语法来添加Point类的方法:
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] );
}
};
var Transformable = {
offset: [ 0, 0 ],
setOffset: function ( x, y ) {
this.offset[ 0 ] = x;
this.offset[ 1 ] = y;
}
};
Point.prototype = {
constructor: Point,
print: function () {...},
...
};
不过,对于mixin对象的语法可能是什么样子,我还是很灵活的。在ECMAScript 6中,我会使用符号来存储内部数据,并将公共属性定义为使用该数据的访问器:
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;
}
};
})();
为了复制描述符,您需要更复杂的扩展:
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);
这一点,你也可以考虑去掉<代码> SETOBSE/<代码>,并定义一个用于<代码>偏移的设置器,这样你就可以使用<代码> MyPooTiT1.Office=[5, 5 ] < /Cord>。
< P>我有一个工作解决方案,但我自己会认为这是有争议的。基本上每次混合时,我都会使用mixin类的一个新实例进行复制。所以我不能扩展我的Point类,而是在构造函数中进行mixin。(使用与问题中相同的范围函数。)
与我在问题中链接到的多重继承答案相比,我认为它更具可读性,因为我可以使用Point类的对象文字语法,并且只需声明一次从何处混合
然而,性能可能是一个问题,因为我并没有混入到类中,而是混入到每个实例中,尽管我不知道这有多严重
我欢迎任何关于为什么这可能是一个糟糕的解决方案的精确论据。回答你的第二篇文章;不,这个解决方案没有什么不好的地方,除了Bergi已经提到的——基于函数的Mixin永远不应该被实例化,而是总是通过call
或apply
应用于对象。
性能不会成为问题。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的示例为起点,下一种方法可能更简洁,因为点
s原型打印
方法不会对既不属于构造函数
也不属于原型
的方法进行任何假设
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
需要实例初始化的mixin也需要在构造函数中混合。你可能想看看@Bergi谢谢,也许我应该从一开始就寻找decorator而不是mixin。同时,在构造函数中混合也是我一直认为的。顺便说一句,它是extend
而不是extent
:-)如果你的混合像类一样(构造函数),我宁愿使用Transformable.call(this)
而不是extend(this,new Transformable)
。但是,当将其设置为用作mixin而不是其自身的构造函数时,请使该函数可以被调用为可转换的(这一点)代码>。在适用的情况下,您仍然应该在原型中添加和混合方法。就像一个普通的班级一样,你们的混音应该同时做到这两个方面。这就像我需要它一样有效。虽然我需要更多的时间来理解它,因为我还不习惯ECMAScript 6。我不太愿意采用这种方式,因为我还在一些配置上使用Chromium 37,因为它是Ubuntu Precise Pangolin中仍然受支持的版本,缺乏对Symbol的支持。你的第二种方式非常类似于标准Javascript继承设计模式。我很少使用它,因为对于每个函数,都必须从Namespace.Class.prototype.functionName=
开始。我猜大多数代码都会发生在TransformablePoint中。关于第一种方法,我认为在Point
中使用混合方法和属性并不坏,因为构造函数中声明了一些功能插入到这个类中。这比web上的大多数mixin设计模式要好得多,在web上,只有在两个对象都布置好之后,才能进行混合,事先访问所有mixin功能,没有任何后悔。这只是因为最初提供的示例代码看起来如此<代码>可转换点
不会从点
继承任何内容。忽略整个原型链。相反,它直接使用基于函数的mixin组合,将点
构造器升级为一个mixin,正如打算使用的可转换的
一样。唯一发生的继承是为print
(两次),而Point
甚至不需要它。如果我知道你的架构应该是什么样子,我可以