IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    在 Javascript 中实现调用父类同名方法的语法糖(this._super())

    efe发表于 2015-03-02 06:04:32
    love 0

    在很多 OO 的语言中,都提供了某种便捷的语法糖去调用基类中被子类覆盖的方法,比如在 Java 中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class A {
    void method() {
    System.out.println("A");
    }
    }

    public class B {
    void method() {
    super.method();
    System.out.println("B");
    }
    }

    在 Python 中:

    1
    2
    3
    4
    5
    6
    7
    8
    class A
    def method():
    print('A')

    class B(A)
    def method():
    super(B, self).method()
    print()

    这种调用方式的好处在于:基类名称变化后,子类不用多处的修改,同时语义也比直接引用父类方法更加清晰。

    在 JS 中,我设想了以下方式的语法调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var A = function () {};

    A.prototype.method = function () {
    console.log('A#method');
    };

    var B = function () {};

    B.prototype.method = function () {
    this._super(arguments);
    console.log('B#method');
    };

    inherits(B, A);

    var b = new B();
    b.method(); // 打印出: A#method B#method

    本质就是inherits的实现,因为 super 为关键字,所以使用了_super 代替。

    实现方案(1) - 字符串匹配_super关键字,动态改写

    John Resig 在他的博文 使用了该方案实现了 super 语法糖。

    主要原理为:获取方法的代码字符串,通过正则检测字符串中是否包含 _super ,若包含, 则改写该方法,在改写的方法中动态的改变this._super,使其指向父类同名方法,以完成调用父类方法的目的。代码可参考上面给出的文章链接。

    这种实现方案的问题在于:

    1. 改写了原有方法,使得调试起来具有很大迷惑性;
    2. 极端的场景可能会出问题,如字符串中出现个 _super。

    实现方案(2) - 通过arguments.callee.caller查找调用方法名,再进行父类方法调用

    简单的实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    var _super = function (args) {
    var method = this._super.caller;

    if (!method) {
    throw "Cannot call _super outside!";
    }

    var name = method.__name__;
    var superCls = method.__owner__._superClass;
    var superMethod = superCls[name];

    if (typeof superMethod !== 'function') {
    throw "Call the super class's " + name + ", but it is not a function!";
    }

    return superMethod.apply(this, args);
    };

    var inherits = function (SubCls, SuperCls) {
    var fn = function () {};

    if (typeof SuperCls !== 'function') {
    SuperCls = fn;
    }
    var overrides = SubCls.prototype;
    var superPro = SuperCls.prototype;

    fn.prototype = superPro;

    var subPro = SubCls.prototype = new fn;

    for (var k in overrides) {
    var v = overrides[k];
    if (typeof v === 'function') {
    v.__name__ = k;
    v.__owner__ = subPro;
    }

    subPro[k] = v;
    }

    subPro.constructor = SubCls;
    subPro._superClass = superPro;
    subPro._super = _super;
    };

    上述代码主要通过 _super 函数和 inherits 函数实现了调用基类方法的模板功能。

    inherits 函数主要的功能有两个:

    1. 实现了基本的继承
    2. 对原型函数附加了 __name__ 和 __owner__ 属性。前者是为了提供对_super的支持,方便其找到函数名,后者是为了在多级继承的时候,跳出作用域的死循环。

    _super 流程如下:

    1. 找 caller
    2. 获取caller的函数名__name__
    3. 获取 caller 的拥有者__owner__
    4. 找到__owner__的父类
    5. 调用同名函数

    方案二的缺点:

    1. 无法用在严格模式下
    2. 会给函数额外增加自定义的属性(__name__与__owner__)

    综合考虑

    在我们的oo库中,最后选用的是方案二,主要权衡为:

    1. 严格模式带来的缺陷避免收益完全可以由工具(ide, jshint..)取代,我们每次提交代码前都会经过 jshint 的代码检测,因此不使用严格模式对我们来说没有什么影响。
    2. 在实际的编码过程中,基本是不会出现和自定义属性出现重名的场景,这也算是一个约定。


沪ICP备19023445号-2号
友情链接