概要
JS虽然没有直接有面向对象的特性,但还是能prototype为了模拟面向对象的特性,如继承和多态。而大多数面向对象的语言(例如C++。Java等一下)相比,JS为了实现面向对象还是有点繁琐,抽象。需要对JS的prototype模式有深刻的理解。
在开发过程中,有时候会遇到这样一个问题:假设在子类中“覆盖”了超类的某个方法,但仍须要在子类方法中调用一次超类方法,这时候应该怎么做?假设是Java,一个简单的superkeyword就可以解决这个问题,但假设是JS呢?
解决这个问题的最基本方法能够是:在子类中,使用超类类型。通过applykeyword。以当前类实例引用运行一次超类方法。例如以下:
定义类A
// 定义类A function A(a) { this.a = a; } // 为类A定义show方法 A.prototype.show = function() { alert("A: " + this.a); };
定义类B并从A继承
// 定义类B function B(a, b) { // 调用A的构造函数 A.apply(this, arguments); this.b = b; } // 链接A的原型 B.prototype = new A();
实例化类B对象并调用show方法
var b = new B(100, 200); b.show();
此时。会运行定义在A中的show方法(显示A.a的值)。表示类B已经从类A中继承了show方法
类B覆盖show方法
// 覆盖show方法 B.prototype.show = function() { A.prototype.show.apply(this, arguments); alert("B: " + this.b); }; // 运行覆盖后的方法 b.show();
在B的prototype中又一次定义show方法,即能够觉得B类覆盖了A类的show方法。
注意 A.prototype.show.apply(this, arguments) 这一句,实际上是利用了JS的原型特性。在B类对象中(以B类对象)运行了一次A类的show方法。
JS特殊的动态语言特性使得“覆盖”这个语义能够随时发生。甚至能够通过操作prototype来取消”覆盖“语义,这也正是JS的灵活和强大之处。
更进一步
通过上面的样例,能够发现,JS的”继承“,”覆盖“和”调用超类方法“尽管不难理解,但写起来仍较为繁琐,以下的代码能够简化这个流程。
(以下代码灵感部分来源于ExtJS库,部分參考自prototype库)
namespace
// 定义根命名空间 ALV = {}; // 定义注冊命名空间的方法 ALV.namespace = function(ns) { var root = window; var parts = ns.split("."); for (var i = 0; i < parts.length; i++) { var p = parts[i]; if (!root[p]) { root[p] = {}; } root = root[p]; } };
合并对象的apply方法
// 合并对象成员 ALV.apply = function(obja, objb, def) { if (def) { ALV.apply(obja, def); } if (obja && objb && typeof objb === 'object') { for (var o in objb) { obja[o] = objb[o]; } } };
定义类的方法
// 原型定义 ALV.define = function(clazz, config) { var parts = clazz.split("."); var root = window; for (var i = 0; i < parts.length - 1; i++) { root = root[parts[i]]; } var cn = parts[parts.length - 1]; if (!root[cn]) { root[cn] = function() {}; } clazz = root[cn]; // 将proto对象的成员赋值给类的原型 ALV.apply(clazz.prototype, config); return clazz; };
定义子类并继承超类的方法
// 定义继承的方法 ALV.extend = function(base, child, proto) { // 将超类原型赋值给类的原型 var c = ALV.define(child); if (base && typeof base === "function") { c.prototype = new base(); } if (proto && typeof proto == "object") { ALV.apply(c.prototype, proto); } // 调用超类方法 c.prototype.callParent = function(args) { var m; for (var o in this) { if (this[o] === this.callParent.caller) { m = o; } } var method = base.prototype[m]; if (method && typeof method === "function") { method.apply(this, args); } }; };
上述代码中。子类的 prototype 链接到了超类对象上。完毕了 prototype 的继承。而 callParent 方法中,通过对当前类调用方法的查找,找到方法名(m变量),再在超类的 prototype 中找到同名方法,利用超类方法的 apply 操作,在子类对象上完毕对超类方法的调用。
測试代码
// 定义命名空间 ALV.namespace("Alvin.test"); // 定义超类 ALV.define("Alvin.test.A", { a: 100, show: function() { alert("A: " + this.a); } }); // 定义子类 ALV.extend(Alvin.test.A, "Alvin.test.B", { a: 100, b: 200, show: function() { this.callParent(arguments); alert("B: " + this.b); } }); // 实例化B类对象 var b = new Alvin.test.B(); b.show();
从測试代码中能够看到,Alvin.test.B 类继承了 Alvin.test.A 类,且覆盖了当中的 show 方法。在 B 类的 show 方法中,this.callParent(arguments) 调用完毕了对 A 类show方法的调用。
这样 B 类就可以自然地访问超类方法没有在指定到底有什么关注的超类的名称。
版权声明:本文博主原创文章,博客,未经同意不得转载。