1.Prototype 原型模式
JavaScript 是一种基于原型的面向对象编程语言。即便 JavaScript 现在也引入了类的概念,但它也只是基于原型的语法糖而已
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同), 在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新 对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式 (Prototype Design Pattern),简称原型模式。
创建成本大的对象比如:如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取,这种情况下,我们就可以利用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执 行这些耗时的操作。
在原型设计模式接口中,需要包含一个clone()
方法来让使用使用该接口的类实现相应的clone
方法,clone()方法的实现可根据情况自定义,同时还需要确定选择浅拷贝还是深拷贝。
基本原理
-
Prototype Interface:描述 clone() 方法的接口
-
Prototype :实现原型接口的对象
-
Client:使用和创建Prototype的客户端
interface IProtoType {
clone(): this
}
class MyClass implements IProtoType {
field: number[]
constructor(field: number[]) {
this.field = field
}
clone() {
return Object.assign({}, this) //浅拷贝
// return JSON.parse(JSON.stringify(this)); //深拷贝
}
}
// The Client
const OBJECT1 = new MyClass([1, 2, 3, 4]) //创建一个包含数组的对象
console.log(`OBJECT1: ${JSON.stringify(OBJECT1)}`)
const OBJECT2 = OBJECT1.clone() // Clone
console.log(`OBJECT2: ${JSON.stringify(OBJECT2)}`)
OBJECT2.field[1] = 101
//因为对象第一层数据为array(引用类型),所以浅拷贝的话改变该数据时会同时修改,采用深拷贝则不用
console.log(`OBJECT2: ${JSON.stringify(OBJECT2)}`)
console.log(`OBJECT1: ${JSON.stringify(OBJECT1)}`)
应用举例
下面分别使用浅拷贝和深拷贝克隆Document对象实例的属性和方法
interface IProtoType {
clone(mode: number): Document
}
class Document implements ProtoType {
name: string
array: [number[], number[]]
constructor(name: string, array: [number[], number[]]) {
this.name = name
this.array = array
}
clone(mode: number): Document {
//可选深拷贝和浅拷贝两种
let array
if (mode === 2) {
array = JSON.parse(JSON.stringify(this.array))//深拷贝
} else {
array = Object.assign([], this.array) //默认为浅拷贝
}
return new Document(this.name, array)
}
}
// 创建一个Document对象包含两个数组
const ORIGINAL_DOCUMENT = new Document('Original', [
[1, 2, 3, 4],
[5, 6, 7, 8],
])
console.log(ORIGINAL_DOCUMENT)
const DOCUMENT_COPY_1 = ORIGINAL_DOCUMENT.clone(1) //浅拷贝
DOCUMENT_COPY_1.name = 'Copy 1' //浅拷贝+原始类型,不会同时修改原对象
DOCUMENT_COPY_1.array[1][2] = 200 //浅拷贝+array引用类型,会同时修改原数据
console.log(DOCUMENT_COPY_1)
console.log(ORIGINAL_DOCUMENT)
console.log()
const DOCUMENT_COPY_2 = ORIGINAL_DOCUMENT.clone(1) //浅拷贝
DOCUMENT_COPY_2.name = 'Copy 2'
DOCUMENT_COPY_2.array[1] = [9, 10, 11, 12]
console.log(DOCUMENT_COPY_2)
console.log(ORIGINAL_DOCUMENT)
console.log()
const DOCUMENT_COPY_3 = ORIGINAL_DOCUMENT.clone(2) // deep copy
DOCUMENT_COPY_3.name = 'Copy 3'
DOCUMENT_COPY_3.array[1][0] = 1234
console.log(DOCUMENT_COPY_3)
console.log(ORIGINAL_DOCUMENT)
console.log()
Document {
name: 'Original',
array: [ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ] ]
}
Document {
name: 'Copy 1',
array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}
Document {
name: 'Original',
array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}
Document {
name: 'Copy 2',
array: [ [ 1, 2, 3, 4 ], [ 9, 10, 11, 12 ] ]
}
Document {
name: 'Original',
array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}
Document {
name: 'Copy 3',
array: [ [ 1, 2, 3, 4 ], [ 1234, 6, 200, 8 ] ]
}
Document {
name: 'Original',
array: [ [ 1, 2, 3, 4 ], [ 5, 6, 200, 8 ] ]
}
关于网上的一张关于赋值、浅拷贝、深拷贝的区分图:
浅拷贝只会复制对象中基本数据类型数据和引 用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……而深拷贝得到 的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间
如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就 变得复杂多了
2.Singleton 单例模式
单例设计模式(Singleton Design Pattern):一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
基本原理
在调用Singleton类时构造函数就会检查静态instance是否以及被赋值了,如果被赋值有了
export class Singleton {
static instance: Singleton
id: number
/* */
constructor(id: number) {
this.id = id
if (Singleton.instance) {
return Singleton.instance
}
Singleton.instance = this
}
}
//所以单例模式创建的所有对象都指向一个原始对象
const OBJECT1 = new Singleton(1)
const OBJECT2 = new Singleton(2)
console.log(OBJECT1 === OBJECT2) // true
console.log(OBJECT1.id) // returns 1
console.log(OBJECT2.id) // returns 1 因为对象实例以及被船舰过了,直接返回的是第一创建按的对象实例
应用举例
实现一个关于游戏排行榜的应用,游戏对象是由相应的游戏类创建,而排行榜又分出来作为一个单例类,这样创建多个游戏对象时就可以直接引用同一个单例类Singleton了
class Leaderboard {
static instance: Leaderboard
#table: { [id: number]: string } = {}
constructor() {
if (Leaderboard.instance) { //单例
return Leaderboard.instance
}
Leaderboard.instance = this
}
public addWinner(position: number, name: string): void {
this.#table[position] = name
}
public print(): void {
console.log('-----------Leaderboard-----------')
for (const key in this.#table) {
console.log(`|\t${key}\t|\t${this.#table[key]}\t|`)
}
console.log()
}
}
interface IGame {
addWinner(position: number, name: string): void
}
class Game1 implements IGame {
leaderboard: Leaderboard
constructor() {
this.leaderboard = new Leaderboard()
}
addWinner(position: number, name: string): void {
this.leaderboard.addWinner(position, name)
}
}
class Game2 implements IGame {
leaderboard: Leaderboard
constructor() {
this.leaderboard = new Leaderboard()
}
addWinner(position: number, name: string): void {
this.leaderboard.addWinner(position, name)
}
}
class Game3 implements IGame {
leaderboard: Leaderboard
constructor() {
this.leaderboard = new Leaderboard()
}
addWinner(position: number, name: string): void {
this.leaderboard.addWinner(position, name)
}
}
const GAME1 = new Game1()
GAME1.addWinner(2, 'Cosmo')
const GAME2 = new Game2()
GAME2.addWinner(3, 'Sean')
const GAME3 = new Game3()
GAME3.addWinner(1, 'Emmy')
GAME1.leaderboard.print()
GAME2.leaderboard.print()
GAME3.leaderboard.print()
这样一来GAME类就用创建多个leaderboard而是共同引用一个leaderboard实例
-----------Leaderboard-----------
| 1 | Emmy |
| 2 | Cosmo |
| 3 | Sean |
-----------Leaderboard-----------
| 1 | Emmy |
| 2 | Cosmo |
| 3 | Sean |
-----------Leaderboard-----------
| 1 | Emmy |
| 2 | Cosmo |
| 3 | Sean |