JS ECMA5:将对象转换为子类
代码示例:
ClassA.js
var classB = require('./ClassB');
function ClassA() {
this.ID = undefined;
this.Type = undefined;
....
}
ClassA.prototype.init = function init(id){
this.ID = id;
this.get();
if (this.Type === 'C' && Object.getPrototypeOf(this) === ClassA.prototype) {
return new classB().init(this.ID);
}
}
ClassB.js
function ClassB() {
ClassA.call(this);
this.additionalProp = undefined;
}
ClassB.prototype = Object.create(ClassA.prototype);
ClassB.prototype.constructor = ClassB;
我已实现两个类 ClassA 和 ClassB。 ClassB 是 CLassA 的子类,并具有一些附加属性。
ClassB 的原型链设置如下:
B.prototype = Object.create(A.prototype); B.prototype.constructor = B;
对象的信息是通过 ID 从 API 中检索的。 在调用 API 时,我不知道对象是否需要是 ClassA 或 ClassB 的实例。因此,我总是从 ClassA 的对象开始调用 API。 当 API 返回特定类型时,我想将我的对象从 ClassA 转换为更具体的 ClassB。
我试图在 ClassA 之外调用 ClassB 的构造函数 - 这会引发错误:
未捕获的 TypeError:对象原型可能只是一个对象或 null:未定义
我认为我根本不应该在 ClassA 中引用 ClassB,但值得一试...
感谢您帮助新手!:)
At the time of the API call I do not know if the object needs to be an instance of ClassA or ClassB. So I always start with an object of one class, then when the API returns a specific type I want to convert my object.
千万不要这么做。在 API 返回后 构造您的对象 ,并首先使用正确的类型构造它。
I always start with an object of the class to call the API
这是您的设计出错的地方。该类不应负责调用 API,它应该只负责表示数据并让方法对数据起作用。不要从一个可以以某种方式自我补充的空对象开始,让构造函数从作为参数传递的数据中完全初始化对象。
将 API 调用放在类之外的单独函数中。也许创建一个单独的类来表示 API 端点。如果绝对必要,请将其设为基类的静态方法,然后它仍然可以创建
new ClassA
或
new ClassB
实例。
您在评论中提到了这个错误:
Uncaught TypeError: Object prototype may only be an Object or null: undefined
您之所以会得到这个错误,是因为
ClassA.js
和
ClassB.js
之间存在循环关系:每个都试图导入其他导出的内容。在某些情况下,这样做可能没问题,但在这里,
ClassB.js
中的代码试图在顶层代码中使用
ClassA.prototype
,而
ClassA.js
又从
ClassB.js
导入
ClassB
。您最终会得到一个
ClassA
导入的占位符,而
Object.create(ClassA.prototype)
调用不起作用。
如果您在同一个文件中定义它们,这将容易得多,这要归功于函数声明提升。
我还会修改
init
,以便它
始终
返回实例,因为您有时需要返回一个新对象,但其他时候则不需要。因此,让它始终返回实例可以简化调用代码。
这是一个最小更改示例:
function ClassA() {
this.ID = undefined;
this.Type = undefined;
// ....
}
ClassA.prototype.get = function () {
// *** Just for debugging, ID 2 is `ClassB`, any other is `ClassA`
this.Type = this.ID === 2 ? "C" : "X";
};
ClassA.prototype.init = function init(id) {
this.ID = id;
this.get();
if (this.Type === "C" && Object.getPrototypeOf(this) === ClassA.prototype) {
return new ClassB().init(id); // *** Use `ClassB`, not `ClassA`
}
return this; // *** So the caller always gets an instance they can use
};
function ClassB() {
ClassA.call(this);
this.additionalProp = undefined;
}
ClassB.prototype = Object.create(ClassA.prototype);
ClassB.prototype.constructor = ClassB;
module.exports.ClassA = ClassA;
module.exports.ClassB = ClassB;
然后使用它(仅用于示例):
var both = require("./both");
var ClassA = both.ClassA;
var ClassB = both.ClassB;
var obj1 = new ClassA();
obj1 = obj1.init(1);
console.log(obj1 instanceof ClassA); // true
console.log(obj1 instanceof ClassB); // false
var obj2 = new ClassA();
obj2 = obj2.init(2);
console.log(obj2 instanceof ClassA); // true
console.log(obj2 instanceof ClassB); // true
话虽如此,我想我会重构它。您曾说过,有一个单独的
init
方法,因为有时您想在拥有
id
之前对对象使用方法。这让我觉得
ClassA
(至少)试图做太多事情,既包括它在不知道它是什么时(没有
id
)可以做的事情,也包括它在知道它是什么时可以做的事情。构造函数返回的实例应该完全准备好并随时可用。因此,最好将
ClassA
中不需要
id
的部分拆分成其他部分。这也意味着
ClassA
不必引用
ClassB
,这不是最佳实践。
我想我可能还会将
get
分离出来,与类分开,并让它返回适当的实例。
例如:
ClassA.js
:
function ClassA(data) {
this.ID = data.id;
this.Type = data.type;
// ....
}
// ...other `ClassA` methods...
module.exports = ClassA;
ClassB.js
:
var ClassA = require("./ClassA");
function ClassB(data) {
ClassA.call(this, data);
this.additionalProp = data.additionalProp;
}
ClassB.prototype = Object.create(ClassA.prototype);
ClassB.prototype.constructor = ClassB;
// ...other `ClassB` methods...
module.exports = ClassB;
get.js
(或其他):
var ClassA = require("./ClassA");
var ClassB = require("./ClassB");
function get(id) {
var data = /*...get from API...*/;
var cls = "additionalData" in data ? ClassB : ClassA;
return new cls(data);
}
这提供了更好的关注点分离。