写《javascript 的设计模式》的一些总结 Nodejs 相关经验分享
写《javascript 的设计模式》的一些总结 Nodejs 相关经验分享
最近复刻了一个《 javascript 的设计模式》。也再一次温习了 js 的一些看似不怎么用的知识点,但是在设计模式中又是非常重要的。对于这种容易遗忘的细节但却重要的东西,我觉得来记录这些知识点,以便需要的时候可以看看。
0x1 如何通过基类克隆一个子类对象
首先我们知道 JS 是基于设计模式中的原型模式设计原型链,那么类一定是有共通的原型在里面。那么在子类实例化之后,在基类的 this 也就变成了子类的 this,那么只需要通过新建一个对象,并把当前子类的 prototype 即实例化类的__proto__重现给予到新对象中,由于 this.__proto__是子类的 prototype,那么绑定新对象调用子类的 prototype 的 constructor 即可实现,这也是 new 的过程。
但是在 es6 中有一些变更就是如果使用了 class 关键字,是不可以被 call 调用的,但是还是可以通过 ES6 推出的 Reflect.construct 来达到相同效果,代码如下,下面也有详细说明。
// 这是基类
class Shape {
// 代码省略...
clone() {
/**
* 如果子类要改成 class 形式,这个方法要改写成下面形式
* 因为主要是通过 JS 原型链帮助理解原型模式,所以子类不使用 class 形式
* class 和 function 构造函数的区别是 class 的构造函数增加了只能作为构造函数使用的校验,比如 new
* return Reflect.construct(
* this.__proto__.constructor,
* [],
* this.__proto__.constructor
* )
*/
let clone = {};
// 注意如果此类被继承,this 会变成子类的方法
// 同时这里使用的是原型的指针,所以比直接创建对象性能损耗更低
clone.__proto__ = this.__proto__;
this.__proto__.constructor.call(clone);
return clone;
}
}
// 这是子类
function Rectangle() {
this.type = "Rectangle";
}
Rectangle.prototype.__proto__ = new Shape();
Rectangle.prototype.draw = function() {
console.log("I'm a rectangle")
}
0x2 如何实现一个抽象方法
设计模式中有很多抽象方法,但是抽象方法是不能被初始化,只能被继承,那么抽象类要如何实现呢?
其实有两种方法,一种是通过判断 this 是否 instanceof 这个基类,二是用 ES6 的方法使用 new.target,如下:
class AbstractLogger {
constructor() {
if(new.target == AbstractLogger) {
throw new Error('this class must be extends.')
}
}
// 代码省略...
}
0x3 如何实现私有变量
实现私有变量有很多方法,比如 Symbol,但 Symbol 的实现需要通过作用域隔离,其次当访问私有属性的关键字只能返回 undefined,没有错误信息抛出,这是一种非常不好的设计或实践.
那么私有属性还没有推出,如何来更好的实现呢?可以通过数据定义的方式来做这件事情,即 defineProperty。
那么问题又来了,因为私有属性只允许自己调用,子类不能调用,那么如何保证是自己而不是子类或者其它类型呢?那么可以根据当前 this 的__proto__来判断,如果__proto__引用等于自己的 prototype 则为自己。因为如果是子类继承,那么 this 的__proto__等于继承者的 prototype。那么根据这一点,我们可以这样做,如下:
class Meal {
constructor () {
const items = [];
/**
* 为什么不用 Proxy 而使用 defineProperty
* 因为 Proxy 虽然实现和 defineProperty 类似的功能
* 但是在这个场景下,语意上是定义属性,而不是需要代理
*/
Reflect.defineProperty(this, 'items', {
get:()=>{
if(this.__proto__ != Meal.prototype) {
throw new Error('items is private!');
}
return items;
}
})
}
// 省略代码...
}
0x4 如何实现类的终态方法
在设计模式中,很多方法要实现终态化,即基类的方法不能被子类覆盖。
如何做到不被子类方法覆盖父类,貌似在 JS 中是个难题,但真的是难题吗?
并不是,因为当子类实例化的时候需要调用父类的构造函数,但此时父类的构造函数的 this 就是子类的方法,而 JS 对象构造又是基于原型的,那么如果子类自己实现了方法,那么子类实现的方法必然不等于父类原型中的方法,通过这个方法来实现父类方法的终态化。如下:
class Game {
constructor() {
if(this.play!= Game.prototype.play) {
throw new Error("play mothed is final,can't be modify!");
}
}
// 代码省略...
play(){
// 代码省略...
}
}
结语
通过这些有意思的方法实现通过基类克隆一个子类对象,不可被初始化,私有化变量,终态方法的实现。即感叹 JS 的灵活性,但又对各种行为保有余地,这非常棒。同时在写《 javascript 的设计模式》的时候,发现 JS 本身就是一个设计模式的教科书,是很值得我们学习的。
以上例子出自于《 javascript 的设计模式》。
学习了赞
不知道楼主的整体是否很好地总结概括了这本书的风格。如果是的话,不推荐读这样的书,窜味儿,难受
你在 0x2 都用上 new.taget 了,在 0x1 用 Object.create(Object.getPrototypeOf(this))不好吗?
0x3 那个根本就不叫私有属性,私有和公有是按 instance 算的,不是 type
0x4 也是胡来,子类完全可以 super()以后再覆盖
最好还是……别误导新人吧
说的不错,那你能分别用更好的方式实现吗?
0x1 我已经说了
0x2 没这么麻烦,直接 throw 就行,子类覆盖后自然不 throw 了,这个 new.target 的判断是没用的
0x3 应该用函数+闭包定义方法来实现私有,当然这会导致 this.fn !== Object.getPrototypeOf(this).fn
0x4 终态方法的目的是能调用但不能覆写,所以只需要在 class 外面定义个函数然后去.call/.apply 就行,不需要变成方法
插眼。听灰大指导
每个人都是自己的实现和缺陷,其它的就不说了,但 0x2 你先试试看看直接 throw 行不行,我试了好像不行
什么样的情况下不行,你是怎么去调用的?
我觉得你说了要点,窜味儿确实存在,我这其实是有点搬 JAVA 那一套。
如果 JSer 要用的话,确实要根据实际情况根据 JS 特色使用。
在修正误导方面,可能还有很多工作要做。
先赞一个
啥东西 我咋都没用过。。。
子类 constructor()中要调用 super() ,不做 new.target 的判断 还是会 throw。
第一个根本不够成问题,对象复制是一个需要对象自己 aware 的事情,什么操作叫做“克隆”是由对象自己定义的,在对象自己定义这个概念之前,这个词无意义。
抽象方法的最简单的实现就是不在 JavaScript 层面实现,正确做法是用 TypeScript 做类型检查。运行时动态判断的开销是无意义的。而且 new 的目标不是说 AbstractBase 不代表它不是 AbstractIntermediate。
最终方法的实现是令实例冻结,并递归冻结类的构造器。然而这样做的意义其实也不大,因为 JavaScript 用户代码是白盒的,你总是可以强行派生。
所以这东西应该叫“抽象类”,而不是“抽象方法”……
既然是用 js,js 的动态扩展性支持直接在类上添加方法就摆明不适合 java 那套,设计模式不应该生搬硬套,灵活运用语言特性才是最关键的,什么抽象类抽象方法还是放在 typescript 上说吧
同意~
个人认为基于 js 的原型链闭包和函数作为参数传递能够开创一套区别于其他语言的设计模式,没必要完全照着其他语言的设计模式思路硬套,设计模式应该是完美利用语言特性的,你这里强行隔离原型链设置父类和子类有什么意义
其实叫类可能确实比叫方法好一些,但 JS 中类和方法的概念本来就相对模糊。
关于《JavaScript的设计模式》在Node.js中的实践经验,以下是一些总结:
-
外观模式(Facade Pattern): 在Node.js中,外观模式通过提供统一的接口来简化复杂的子系统。例如:
class ComplexModule { initialize() { /* 复杂的初始化逻辑 */ } operation1() { /* 复杂操作1 */ } operation2() { /* 复杂操作2 */ } } class ModuleFacade { constructor() { this.complexModule = new ComplexModule(); } performOperations() { this.complexModule.initialize(); this.complexModule.operation1(); this.complexModule.operation2(); } } const moduleFacade = new ModuleFacade(); moduleFacade.performOperations();
-
单例模式(Singleton Pattern): 单例模式确保一个类只有一个实例,并提供一个全局访问点。这在Node.js中管理资源(如数据库连接)时非常有用。
class Database { constructor() { this.connection = null; } static getInstance() { if (!Database.instance) { Database.instance = new Database(); } return Database.instance; } connect() { this.connection = 'Connected'; } } const db1 = Database.getInstance(); const db2 = Database.getInstance(); console.log(db1 === db2); // true
-
其他模式: 此外,工厂模式、观察者模式和中间件模式等也在Node.js中有广泛应用。这些模式增强了代码的可读性、可维护性和可扩展性。
通过运用这些设计模式,可以构建出更健壮、更易于维护的Node.js应用程序。