在 Javascript 中实现调用父类同名方法的语法糖(this._super())
在很多 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();
|
本质就是inherits的实现,因为 super 为关键字,所以使用了_super 代替。
实现方案(1) - 字符串匹配_super关键字,动态改写
John Resig 在他的博文 使用了该方案实现了 super 语法糖。
主要原理为:获取方法的代码字符串,通过正则检测字符串中是否包含 _super
,若包含, 则改写该方法,在改写的方法中动态的改变this._super,使其指向父类同名方法,以完成调用父类方法的目的。代码可参考上面给出的文章链接。
这种实现方案的问题在于:
- 改写了原有方法,使得调试起来具有很大迷惑性;
- 极端的场景可能会出问题,如字符串中出现个
_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 函数主要的功能有两个:
- 实现了基本的继承
- 对原型函数附加了
__name__
和 __owner__
属性。前者是为了提供对_super的支持,方便其找到函数名,后者是为了在多级继承的时候,跳出作用域的死循环。
_super 流程如下:
- 找 caller
- 获取caller的函数名
__name__
- 获取 caller 的拥有者
__owner__
- 找到
__owner__
的父类
- 调用同名函数
方案二的缺点:
- 无法用在严格模式下
- 会给函数额外增加自定义的属性(
__name__
与__owner__
)
综合考虑
在我们的oo库中,最后选用的是方案二,主要权衡为:
- 严格模式带来的缺陷避免收益完全可以由工具(ide, jshint..)取代,我们每次提交代码前都会经过 jshint 的代码检测,因此不使用严格模式对我们来说没有什么影响。
- 在实际的编码过程中,基本是不会出现和自定义属性出现重名的场景,这也算是一个约定。