写《javascript 的设计模式》的一些总结

2019-11-27 09:05:04 +08:00
 zy445566

最近复刻了一个《 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 的设计模式》

10369 次点击
所在节点    Node.js
21 条回复
ironMan1995
2020-01-07 08:46:01 +08:00
建议看曾探写的 javascript 设计模式与实践

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/623462

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX