JavaScript 中的原型模式
在Medium上找到我
JavaScript 语言中可以实现多种设计模式,在本文中我们将介绍原型设计模式。
原型设计模式是一种基于对象的创建型设计模式。
如果您需要回顾一下它们通常对应的三种设计模式,这里有一个小概述:
- 创建型设计模式
你无需直接实例化对象,这些对象会为你创建对象。这种方法的好处是,在决定针对特定情况需要创建哪些对象时,它为你的程序提供了更多的灵活性。
- 行为设计模式
这些模式专注于对象之间的通信。
- 结构设计模式
最后,这些模式专注于类和对象组合。它们可用于通过继承来组合接口,并定义组合多个对象的方法以实现新功能。
如果这是你第一次学习原型模式,现在你可能已经大致了解了。但如果还没有,那么我的工作就是帮你解开这个谜团,我的朋友。
那么原型模式到底是什么,它有什么作用?
此模式的主要重点是帮助创建可用作构造函数创建的任何对象的蓝图的对象。它通过所谓的原型继承来实现这一点。
由于 JavaScript 本身支持原型继承,幸运的是,它在语言中使用起来非常容易,以至于您实际上不需要学习任何新概念,只需要学习语法本身。
话虽如此,原型设计模式仍然是一个非常有用的策略——这使得它成为用 JavaScript 编写程序的重要且有益的方法。我们稍后会解释其中的原因。
当通过构造函数创建对象并包含该name
属性时,使用相同构造函数创建的进一步对象也将具有相同的属性,如下所示:
function Movie(title) {
this.title = title
}
const harryPotter = new Movie('Harry Potter')
const rushHour2 = new Movie('Rush Hour 2')
const fastAndFurious = new Movie('Fast And Furious')
console.log(harryPotter.constructor.name)
console.log(rushHour2.constructor.name)
console.log(fastAndFurious.constructor.name)
听起来像是典型的类对象,但实际上它完全避免使用类。原型设计模式只是创建现有功能对象的副本,而不是定义全新的对象。
在 JavaScript 中使用该模式的最大好处是相对于面向对象的类而言,性能得到了提升。这意味着,当你在对象内部定义函数时,它们将通过引用创建。换句话说,所有子对象都将指向同一个方法,而不是创建各自的副本!
以下是该模式实际运行的代码示例:
const Warrior = function(name) {
this.name = name
this.hp = 100
}
Warrior.prototype.bash = function(target) {
target.hp -= 15
}
Warrior.prototype.omniSlash = function(target) {
// The target's hp may not be under 50 or this attack will fail on the opponent
if (target.hp < 50) {
return
}
target.hp -= 50
}
const sam = new Warrior('Sam')
const lenardo = new Warrior('Lenardo')
sam.bash(lenardo)
在我们的代码示例中,我们使用 定义了战士的攻击方法Warrior.prototype.<method> = function() {...}
。您可以看到,我们使用new
关键字实例化了一些战士,所以现在我们看到的是两个实例。这两个实例都name
根据name
调用者传入的参数设置了它们的属性。
当我们在原型上定义方法bash
和时,正如所演示的那样,我们正在查看的两个单独的实例实际上引用了相同的和函数!omniSlash
bash
omniSlash
const Warrior = function(name) {
this.name = name
this.hp = 100
}
Warrior.prototype.bash = function(target) {
target.hp -= 15
}
Warrior.prototype.omniSlash = function(target) {
// The target's hp may not be under 50 or this attack will fail on the opponent
if (target.hp < 50) {
return
}
target.hp -= 50
}
const sam = new Warrior('Sam')
const lenardo = new Warrior('Lenardo')
console.log(sam.bash === lenardo.bash) // true
如果我们像这样定义它们,那么它们就不一样了,所以本质上 JavaScript为每个实例创建了另一个相同方法的副本:
const Warrior = function(name) {
this.name = name
this.hp = 100
this.bash = function(target) {
target.hp -= 15
}
this.omniSlash = function(target) {
// The target's hp may not be under 50 or this attack will fail on the opponent
if (target.hp < 50) {
return
}
target.hp -= 50
}
}
const sam = new Warrior('Sam')
const lenardo = new Warrior('Lenardo')
console.log(sam.bash === lenardo.bash) // false
所以,如果我们不像上一个例子那样使用原型模式,实例化多个实例时会有多疯狂?我们会克隆一些方法,占用大量内存,而这些方法本质上做的事情完全一样,甚至不需要复制,除非它依赖于实例内部的状态!
扩展原型的另一种变体是如下语法:
const Warrior = function(name) {
this.name = name
this.hp = 100
}
Warrior.prototype = {
bash(target) {
target.hp -= 15
},
omniSlash(target) {
// The target's hp may not be under 50 or this attack will fail on the opponent
if (target.hp < 50) {
return
}
target.hp -= 50
},
}
这相当于:
const Warrior = function(name) {
this.name = name
this.hp = 100
}
Warrior.prototype.bash = function(target) {
target.hp -= 15
}
Warrior.prototype.omniSlash = function(target) {
// The target's hp may not be under 50 or this attack will fail on the opponent
if (target.hp < 50) {
return
}
target.hp -= 50
}
结论
这篇文章到此结束!希望你觉得这篇文章很有价值,并期待未来有更多精彩内容!
在Medium上找到我
文章来源:https://dev.to/jsmanifest/the-prototype-pattern-in-javascript-nfo