这种模式是 JavaScript 的核心,也是每个 JS 开发人员“必须具备”的知识。今天我们将看到一些关于如何实施和使用它的示例和建议。

本文将包含JavaScript和TypeScript的示例。
定义
我们将从定义开始:
JavaScript 中的原型模式是一种创建型设计模式,它允许您创建从原型对象继承属性和方法的对象。这类似于面向对象编程中基于类的继承。
一些来源指出原型的每个实现都必须包括clone方法的创建,其他人则说如果编程语言包括任何其他方式来创建对象的副本——你可以使用它们,这仍然会被算作原型模式。
我在一开始就说过这种模式是JavaScript的核心,因为JS中的一切都基于原型模式。
类实现
这被认为是一个经典的实现,你可以在每一本关于编程的书中找到它。
核心思想是使用一组需要原型化的方法来实现一个类,并clone通过添加额外的方法或属性来扩展实现的方法。
JavaScript 示例
让我们开始编码吧。
class Prototype {
greeting = "Hello!";
greet() {
console.log(this.greeting);
}
clone() {
return new this.constructor();
}
}
在这个例子中,我们有一个简单的类,它有 2 个方法来描述这个类的行为,greeting以及 method greet。但是这个方法背后的神奇之处在于clone,它基本上是创建我们类的一个新实例。
请注意,您可以将方法替换为new this.constructor()with new Prototype()。
我们按如下方式使用此模式:
我们按如下方式使用此模式:
const object = new Prototype()
object.greet();
我们执行我们需要对实例执行的基本操作,然后当我们需要创建另一个实例时,修改它,我们这样做:
const cloned = object.clone();
cloned.greeting = "Hi there!";
cloned.greet();
正如您在此处看到的,我们使用clone方法创建了一个新实例,然后根据需要对其进行修改。
TypeScript 示例
说到TypeScript,修改一个已经存在的接口有点棘手。我们希望避免使用as关键字,因为它被认为是一种不好的做法,并且只能在您无法使用其他TypeScript工具处理类型化时使用。
我们对该类的实现与JavaScript实现没有区别:
class Prototype {
greeting: string = "Hello!";
greet(): void {
console.log(this.greeting);
}
clone(): Prototype {
return new Prototype();
}
}
我们在这里添加了一些典型化,但也请记住,readonly如果我们想避免属性在孩子中被覆盖,您可以修改属性。
用法:
const object: Prototype = new Prototype();
object.greet();
如果我们只想重写某些属性,我们不会遇到任何问题,除非我们更改类型,例如从string到number。
const cloned: Prototype = object.clone();
cloned.greeting = "Hi there!";
cloned.greet();
现在,如果我们需要在这里添加属性怎么办?那时候事情变得有点棘手。TypeScript 特性允许我们声明多个具有相同名称的接口,这将导致这些接口的联合。因此,我们可以使用此功能来声明我们想要添加到原型中的附加属性。
interface Prototype {
farewell: () => void;
}
const withNewProperties: Prototype = object.clone();
withNewProperties.farewell = function(): void {
console.log('Bye')
}
酷吧?
但是当我们向我们的代码库添加额外的分解时,它会变得更加棘手。如果我们想象我们导出了我们的Prototype类,然后又导入了它,我们就不能再创建一个同名的新接口,因为导入的成员不再被认为是一种类型。这意味着我们无法再使用此 TypeScript 功能。
好吧,我们可以使用as关键字并强制它键入我们想要的内容,但再一次,我们还有其他技巧可以实现我们的目标。
并非所有人都知道as关键字的替代方法是使用以下语法在我们的变量前加上我们想要的类型const example = <Array>variable。这种方法和as下面的方法之间的区别在于,只有可以直接继承的类型才能被分配,例如,如果我们的variable是string——我们将有一个类型错误,但如果我们的变量是object——我们会很好。
import Prototype from "./class"
interface Modified extends Prototype {
farewell: () => void;
}
const prototype: Prototype = new Prototype();
const cloned: Modified = <Modified>prototype.clone();
cloned.farewell = function(): void {
console.log('Bye')
}
让我们来看看代码。
如您所见,我们创建了扩展我们的新接口Prototype并添加了我们想要的附加属性。然后我们克隆我们的实例,但是你看到我们<Modified>prototype.clone()将新创建的接口分配给一个克隆的对象,这就是TypeScript将如何考虑我们的clone变量具有正确的类型。
对象实现
对象实现在JavaScript中被认为是一种更古老的方法。有趣的是,如果你将类实现编译成ES3,最后——你将获得对象实现。
JavaScript 示例
const prototype = {
greeting: "Hello!",
greet: function() {
console.log(this.greeting);
},
clone: function() {
return Object.create(this);
},
};
class当我们创建我们使用的对象的新实例时,这个实现与您在实现中看到的很接近,只有一点点不同Object.create。这将创建我们对象的一个新实例。
用法与方法没有区别class。
const object = prototype.clone();
object.greet();
如果我们想添加新的属性,我们可以像这样简单地做:
object.name = "John";
object.greet = function() {
console.log(this.greeting + " My name is " + this.name);
};
object.greet();
如您所见,实现非常简单,并且在您想选择比类更简单的方法时很有用。
TypeScript示例
让我们先为我们的对象创建一个描述:
interface Prototype {
greeting: string;
greet: () => void;
clone: () => Prototype;
}
和实现本身:
const prototype: Prototype = {
greeting: "Hello!",
greet: function() {
console.log(this.greeting);
},
clone: function() {
return Object.create(this);
},
};
用法与您之前看到的没有什么不同:
const object: Prototype = prototype.clone();
object.greet();
如果我们想添加新属性,我们可以这样做:
interface Prototype {
name: string;
}
object.name = "John";
object.greet = function() {
console.log(this.greeting + " My name is " + this.name);
};
object.greet();
在这个例子中应用了相同的限制,就像在类TypeScript例子中一样,我们在这里使用双名称声明interface,如果我们将做import或export- 这意味着我们需要做与类型赋值相同的事情。
提示与技巧
在文章的开头我说过,goes implementation 被认为是一个具有clone方法的实现。
但!
JavaScript 有其他方法可以创建新的对象实例。让我们浏览一下如何执行此操作的方法列表:
-
Object.create(prototype) -
Object.assign({}, prototype) -
{...prototype}
结论
相当灵活的模式。
不幸的是,它并没有在 JavaScript 开发人员中广泛传播。
优点
- 您只需克隆一个预定义的对象即可摆脱新实例的重复初始化
- 继承的灵活替代方案
- 可以轻松定义新属性
缺点
- 需要一些巫术才能使其与TypeScript一起工作
- 当涉及到复杂的对象层次结构时,很难支持
- 有些人可能认为这些模式在性能和内存效率方面效率低下









