javascript框架之继承机制3
继续上一部分,现在我们的实的构造器initialize很完美了,甚至连类式super这样语法糖都不用了,就自动实例了父类。我们转而看一看其属性与方法的继承。许多类库都是一个for...in循环再加一些判定实现原型属性拷贝,或根据这些判定把某些属性揪出来加工一下再放进去。又如,我们要对Array的模板进行扩展,做成一个新类Array2,直接继承后,在有些浏览器中Array2可能有forEach方法,可能没有,如果没有,我们才添加自己实现的forEach方法。因此条件过滤非常重要的。在mootools中,生成类都带有一些类方法(alias与implement),以供更进一步的加工。
var copy = function (a,b,c){ var l = arguments.length; if (a && b && l == 2){ for ( var p in b) a[p] = b[p]; } else if (a && b && l == 3 ){ a[b] = c; } return a; }; |
上面的方法名副其实,就是用于单纯的复制。
var override = function (a,b,filter,scope){ var i, scope = scope || window; for (i in b) { if (filter.call(i,a,b)) a[i] = b[i]; } }; |
override 为有选择地复制,如果我们不想覆盖原生函数,只需要这样:
override(Array,{ /**/ }, function (p,a,b){ return !(p in a) }) |
有了以上方法,我们就可以设置更为复杂的语法糖,如实例属性的getter与setter,它们在java早已用annotation搞定了,在ruby中它们的设置也非常简单,我们没有道理放弃如此诱人的东西。不过,在javascript中我们无法利用注释来实现,因为可恨的火狐在编译时把注释全部去掉。ruby那种实现,相当于让人设计另一套语法。因此,我们还是交由类工厂实现,把它们全部变成原型方法。过程如下,首先取得我们定义的构造方法,然后取得其参数,再取得父类构造器的参数,然后转化为两个数组,如果子类参数数组的长度大于父类的,说明它定义了新的属性,我们把这些新属性提出来,然后把它们首字母大写前面加上set与get,对应的函数用eval生成即可。
//获取函数的参数,以字符串数组形式返回 var argumentNames = function (fn) { var names = fn.toString().match(/^[\s\(]* function [^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\ //g, '') .replace(/\s+/g, '' ).split( ',' ); return names.length == 1 && !names[0] ? [] : names; } //首字母大写函数 var capitalize = function (str){ return str.replace(/\S+/g, function (a){ return a.charAt(0).toUpperCase() + a.slice(1); }); }; //用于提取两个数组不同的部分 var difference = function (arrayA,arrayB){ var len = Math.max(arrayA.length, arrayB.length), i,p, result=[], diff = {}; for (i=0;i<len;i++){ if (i<arrayA.length) diff[ arrayA[i] ] = diff[arrayA[i]] ? 2 : 1; if (i<arrayB.length) diff[ arrayB[i] ] = diff[arrayB[i]] ? 2 : 1; }; for (p in diff) if (diff[p] ==1 ) result.push(p); return result; }; |
在火狐等浏览器中,公开了一个叫__proto__的内部变量,它为实例对象如我们的i的属性。通常实例都有一个construtor属性,它其实也是prototype上。但prototype为类的属性,实例只能访问prototype上的属性。
var i = new IndiaTiger( "印度虎" ,2, "印度" ); var a = new Array alert(i.prototype) //undefined,实例不能直接访问类的prototype属性 alert(a.prototype) //undefined alert(i.constructor) //IE不能访问,其他能 --> function IndiaTiger(){[variant code]} alert(a.constructor) //IE不能访问,其他能 --> function Array(){[native code]} alert(i.__proto__.klassname) //IE不能访问,其他能 IndiaTiger alert(i.__proto__.constructor) //IE不能访问,其他能 --> function IndiaTiger(){[variant code]} alert(a.__proto__.constructor) //IE不能访问,其他能 --> function Array(){[native code]} |
这简单,我们为我们生产的类的prototype上添加一个__proto__属性即可。这样另一条原型链在我们的继承体系中就修复了!
if (!+ "\v1" || window.opera){ klass.prototype.__proto__ = klass.prototype; //让类实例可以访问类的prototype } |
我们来看另一种语法糖,它见于Prototype.js1.6版。
//以下为Prototype的类继承 var Person = Class.create({ initialize: function (name) { this .name = name; }, say: function (message) { return this .name + ': ' + message; } }); // when subclassing, specify the class you want to inherit from var Pirate = Class.create(Person, { // redefine the speak method say: function ($ super , message) { //★★★★注意看是如何重写父类的方法 return $ super (message) + ', yarr!' ; } }); var john = new Pirate( 'Long John' ); john.say( 'ahoy matey' ); // -> "Long John: ahoy matey, yarr!" |
其实就是Ruby那一套东西,目的是达到最大的复用,类继承是整体的复用,方法的super是局部的复用(我也不知怎样叫它)。子类的同名方法把父类的方法用裹其中,大大减少方法的代码长度。我们看一下相应的ruby代码:
class A #定义父类 def a #定义方法 相当于javascript的 function a(){document.write("a 1")}; p 'A a method' end end aa = A . new #创建实例 var aa = new A; aa.a # aa.a();输出 "A a method" #>ruby a.rb #"A a method" #>Exit code: 0 |
像ruby这种一切皆对象的语言中,实现继续轻而易举!
class A #定义父类 def a p 'A a method' end end class B < A #定义子类,让类B继承类A def a p 'B a method start' super p 'B a method end' end end b = B . new b.a #>ruby a.rb #"B a method start" #"A a method" #"B a method end" #>Exit code: 0 |
因此我们应该明白Prototype的$super就是执行与父类的同名方法。如何调用它呢?我们可以为$super方法传入arguments,通过arguments我们在内部就可以找到此方法实体(那个arguments.callee),为了方便找到父类的同类方法,我们在它直接设置到方法上。
DOMは人間の使う物ではない!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架