写《javascript的设计模式》的一些总结
发布于 5 年前 作者 zy445566 4599 次浏览 来自 分享

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

回到顶部