JavaScript 构造函数模式
JavaScript constructor patterns
我正在寻找一个理智的解决方案来解决JavaScript只有一个构造函数的问题。因此,假设我们有一个类Point
并且我们希望允许从坐标创建对象。
我将忽略所有这些示例中的类型检查。
function Point(x, y) {
this.x = x;
this.y = y;
}
容易。从其他点创建点怎么样?
function Point(x, y) {
if (!y /* && x instanceof Point */) {
y = x.y;
x = x.x;
}
this.x = x;
this.y = y;
}
这很快就会变成一场噩梦。所以我想要的是一种将这两个构造函数解耦(或者将一个构造函数拆分为两个(的设计模式。Objective-C对此有一个很好的模式。ObjC人用一些东西创造对象。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.withPoint = function(point) {
return new Point(point.x, point.y);
};
到目前为止,我非常喜欢这个。但是现在我们有两种不同的语法。
var a = new Point(4, 2);
var b = Point.withPoint(a);
好吧,这很容易,不是吗?只需添加Point.withCoordinates
.但是构造函数呢?隐藏它?我不知道。我想这就是你进来的地方。
这是我决定采用的:
var Point = {
withCoordinates: function(x, y) {
if (typeof x == 'number' && typeof y == 'number') {
this.x = x;
this.y = y;
return this;
}
throw TypeError('expected two numbers');
},
withPoint: function(point) {
if (typeof point.x == 'number' && typeof point.y == 'number') {
this.withCoordinates(point.x, point.y);
return this;
}
throw TypeError('expected a point');
}
};
var a = Object.create(Point).withCoordinates(0, 0);
var b = Object.create(Point).withPoint(a);
优点:
- 无样板
- 描述性语法/API
- 扩展性好
- 功能的
- 易于测试
缺点:
- 实例不知道它们是否已初始化
- 不能只向类添加属性(比较
Number.MAX_SAFE_INTEGER
(
请注意 Point.withPoint
中的类型检查。它允许鸭子类型的点,如点击事件。
function onClick(event) {
var position = Object.create(Point).withPoint(event);
}
另请注意,在某种默认 ctor 中缺少零初始化。点实际上是一个很好的例子,说明为什么这并不总是一个好主意。
就像在 ObjC 上一样,你可以有单独的 "alloc" 和 "init" 条目,例如:
function Point() {}
Point.prototype.withCoordinates = function(x, y) {
this.x = x;
this.y = y;
return this;
}
Point.prototype.withOffsetFromPoint = function(p, delta) {
this.x = p.x + delta;
this.y = p.y + delta;
return this;
}
p = new Point().withOffsetFromPoint(
new Point().withCoordinates(5, 6),
10);
console.log(p) // 15, 16
其中虚拟构造函数基本上是"分配"的东西。
以更现代的方式相同,没有new
:
Point = {
withCoordinates: function(x, y) {
this.x = x;
this.y = y;
return this;
},
withOffsetFromPoint: function(p, delta) {
this.x = p.x + delta;
this.y = p.y + delta;
return this;
}
}
p = Object.create(Point).withOffsetFromPoint(
Object.create(Point).withCoordinates(5, 6),
10);
console.log(p)
另一个(也许是最惯用的(选项是让构造函数接受命名参数(通过"选项"对象(:
p = new Point({ x:1, y:2 })
p = new Point({ point: someOtherPoint })
好吧,也许这是一种愚蠢的方法,但您可以添加一个属性来指定对象是"POINT"。然后,在构造函数上检查此属性。
这并不理想,但如果它符合您的需求......
http://jsfiddle.net/s4w2pn5a/
function Point(x, y) {
this.type = "POINT";
if (!y && x.type == "POINT") {
y = x.y;
x = x.x;
}
this.x = x;
this.y = y;
}
var p1 = new Point(10, 20);
var p2 = new Point(p1);
alert(p2.x);
instanceof
模式,但将实例变量的初始化移动到另一个函数。
function Point (point) {
if (point instanceof Point) {
this.init(point.x, point.y);
} else {
this.init.apply(this, arguments);
}
}
Point.prototype.init = function (x, y) {
this.x = x;
this.y = y;
};
鸭子打字
更好的选择是使用鸭子类型模式,其中Point
构造函数将始终接受鸭子类型Point
function Point (point) {
this.init(point.x, point.y);
}
Point.prototype.init = function (x, y) {
this.x = x;
this.y = y;
};
var point = new Point({
x: 1,
y: 1
});
var point2 = new Point(point);
这提供了更易于阅读的构造函数调用,并允许Point
使用者使用 x
键和y
键传入任何内容。
有关鸭子打字的更多信息,请看"A Drip of JavaScript"。
实例化和配置不是类的责任。您必须添加工厂,构建器,DI容器等...来完成这项工作。我建议您阅读更多有关创建设计模式的信息。
例如:
var PointProvider = function (){};
PointProvider.prototype = {
fromCoords: function (x,y){
return new Point(x,y);
},
clonePoint: function (p){
return new Point(p.x, p.y);
}
};
var pointProvider = new PointProvider();
var p1 = pointProvider.fromCoords(x,y);
var p2 = pointProvider.fromPoint(p1);
您也可以使用多个资源库:
var Point = function (){
if (arguments.length)
this.setCoords.apply(this, arguments);
};
Point.prototype = {
setCoords: function (x,y){
this.x = x;
this.y = y;
return this;
},
setCoordsFromPoint: function (p){
this.x = p.x;
this.y = p.y;
return this;
}
};
var p1 = new Point(x,y);
var p2 = new Point().setCoordsFromPoint(p1);
或带有立面
var p = function (){
var point = new Point();
if (arguments.length == 2)
point.setCoords.apply(point, arguments);
else if (arguments.length == 1)
point.setCoordsFromPoint.apply(point, arguments);
return point;
}
var p1 = p(x,y);
var p2 = p(p1);
所以总结论点计数等属于更高的抽象层次。
顺便说一句,方法重载是其他语言的一部分,例如 java,因此您可以在那里简单地定义 2 个具有不同类型参数的构造函数,例如:
class Point {
private int x;
private int y;
Point(int x, int y){
this.x = x;
this.y = y;
}
Point(Point p){
this.x = p.x;
this.y = p.y;
}
}
可悲的是,这个功能不是javascript的一部分...
- JavaScript模块模式-如何在使用对象/函数之前激发构造函数/init函数
- 日志记录时避免 Promise 构造函数反模式
- 用于初始化对象的编码模式 - 构造函数(新)与 Object.create()(Crockford)
- 如何在 Crockford 的新构造函数模式中共享“构造函数”功能
- 构造函数模式和原型模式之间的区别
- 使用构造函数显示模块模式
- IIFE创建模式-但如何支持构造函数参数
- json模式的构造函数
- AngularJS设计模式:我应该使用工厂来创建构造函数吗
- 为什么实用模块&构造函数模式将其作为全局接受
- 使用这种JavaScript编码模式来定义构造函数的优点是什么
- 使用RegExp构造函数的具有可变模式的JavaScript正则表达式
- 带有私有变量的JavaScript构造函数模式
- JavaScript 中构造函数的静态方法模式
- 模块模式中的构造函数
- JavaScript 构造函数模式
- 构造函数模式下的自执行函数
- 理解构造函数调用模式中的“this”
- 为什么我的JavaScript构造函数模式不能工作?
- 如何使用promise .all来避免promise构造函数反模式