为了账号安全,请及时绑定邮箱和手机立即绑定

toString方法无法被继承?荐

标签:
JQuery

背景

  在我看来,toString方法是一个类最重要的方法之一。在JavaScript中,将一个对象转化为字符串形式的默认方法就是调用其toString方法。因此,为类型实现一个合理的toString方法对于开发和调试都有一定的好处。在面向对象编程中,在父类中定义toString方法,以此为它的各个子类提供相似的字符串表现形式是常用的做法之一,但是如果您使用Microsoft AJAX Library的面向对象机制进行开发时就会遇到一个问题。  那就是toString方法无法被继承。  说的更明白一些,就是子类无法获得父类的toString方法的实现。除非在子类中直接定义一个toString方法,否则它只能含有JavaScript中默认的toString方法。很显然,这没有任何意义,也失去了面向对象的重要特性。

问题重现

  我们通过一个再简单不过的例子来重现这个问题:

Type.registerNamespace("Demo");// Definition of Demo.Parent class.Demo.Parent = function() {}Demo.Parent.prototype = {    toString : function()    {        return Object.getTypeName(this);    }}Demo.Parent.registerClass("Demo.Parent");// Definition of Demo.Child class, which inherits Demo.Parent.Demo.Child = function(){    Demo.Child.initializeBase(this);}Demo.Child.prototype = {}Demo.Child.registerClass("Demo.Child", Demo.Parent);// Call the toString method implicitly.alert(new Demo.Parent());alert(new Demo.Child());

  上面的代码定义了两个类,父类Demo.Parent和子类Demo.Child。其中父类Demo.Parent中定义了toString方法,因此按照面向对象编程的机制,子类Demo.Child也会使用父类的toString方法实现。可惜结果并不如人意,在IE中,上面的代码会显示如下的结果:

Demo.Parent[object Object]

  通过调用Demo.Parent对象的toString方法,我们得到了期望中的表示当前对象实际类型的字符串。但是调用Demo.Child对象的toString方法却只得到了JavaScript中默认的结果。

这是怎么回事?

  对于使用JavaScript面向对象机制的实现有一定了解的朋友会知道,JavaScript中是使用了prototype链的特性来实现的面向对象的效果。在Microsoft AJAX Library中,“继承”的做法其实只是遍历父类prototype上的所有属性,并为子类的prototype对象添加不存在的属性。简单地说,它的代码实现就如下面的代码所示(请注意,真正的实现并非只有这部分代码,但是这部分代码是继承实现的关键):

for (var memberName in baseType.prototype){    var memberValue = baseType.prototype[memberName];    if (!this.prototype[memberName])    {        this.prototype[memberName] = memberValue;    }}

  这么做的目的,是希望让子类的prototype对象能够拥有父类的prototype对象中定义的成员,并能够使自身重新定义的方法实现覆盖父类的同名方法。显然,这样就获得了“继承”的效果。不过,如此实现“继承”的重要部分就是使用for...in语法来遍历一个对象上的所有属性——可能有些朋友已经看出问题所在了。没错,我们现在来写一段最简单的代码来验证我们的猜想:

for (var memberName in Demo.Parent.prototype){    alert(memberName);}

  果然不出所料,遍历Demo.Parent的prototype对象上的成员却没有得到任何的结果。我们再来写一个更原始的例子,我们直接遍历一个Object对象:

var obj = new Object();for (var memberName in obj){    alert(memberName);}

  toString方法不是每个对象都该有的吗,但是为什么没有遍历出来?其实通过进一步尝试可以发现,与toString方法相似,一些每个对象都有的方法,例如valueOf,hasOwnProperty等等,都无法通过for...in语法来获得。而且,遍历String.prototype对象也无法得到例如split、indexOf等JavaScript定义的方法。这究竟是怎么回事?   答案可以在ECMAScript标准(Ecma-262)中找到。根据标准的描述,JavaScript中的对象是一个无序的属性(Property)集合(属性可以使任何类型,我们传统所说的“方法”其实都是Function类型的对象),而每个属性都拥有有零个或多个特性(Attribute)来“指示”该属性可以被如何使用。例如,一个拥有DontDelete特性的属性就无法从对象里删除。也就是说,以下的操作将没有任何效果:

var array = new Array();delete array.length;

  ECMAScript中为属性定义了4种特性,它们分别是ReadOnly、DontEnum、DontDelete、Internal。很显然,造成对象的toString方法无法被遍历到“元凶”就是DontEnum特性,拥有这个特性的属性将无法通过for...in语法来得到——而似乎JavaScript中的原生属性都有DontEmun特性。

如何解决?

  这样的问题必须解决,否则我们的面向对象机制过于“残缺”了。幸好,我们仍旧能够直接从对象上通过名称来直接获取成员。因此我们可以修改Microsoft AJAX Library一个方法实现:

Type.prototype.resolveInheritance = function (){    if (this.__basePrototypePending)    {        var baseType = this.__baseType;        baseType.resolveInheritance();        for (var memberName in baseType.prototype)        {            var memberValue = baseType.prototype[memberName];            if (!this.prototype[memberName])            {                this.prototype[memberName] = memberValue;            }        }        var dontEnumMembers = ["toString", "toLocaleString", "valueOf",             "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"];                    for (var i = 0; i < dontEnumMembers.length; i++)        {            var memberName = dontEnumMembers[i];            if (this.prototype[memberName] != Object.prototype[memberName])            {                continue;            }                    var memberValue = baseType.prototype[memberName];            if (memberValue != Object.prototype[memberName])            {                this.prototype[memberName] = memberValue;            }        }        delete this.__basePrototypePending;    }}

  我不想在这里详细地解释这部分代码,但是请注意我们做了哪些额外的事情。首先我们准备了一个数组dontEnumMemebers,存放了所有定义在Object.prototype对象上的原生属性(它们都是方法),我们如果使用这些名称为自定义的类型定义成员的话,子类将无法继承父类中的这些方法。因此我们会判断在父类中是否使用这些名称定义了方法(通过和Object.prototype对象中的属性进行比较得到这个信息),如果有,则将其复制给子类的prototype对象上。自然,在这之前我们还需要判断子类本身是否定义了该方法,我们不能使用父类的方法来覆盖子类的方法。  重新运行最早的那部分代码,我们现在已经可以得到正确的结果了:

Demo.ParentDemo.Child

注意

  虽然我们解决了Microsoft AJAX Library中的继承问题,但是请注意,我们并没有,也无法解决for...in语法无法遍历出toString等成员的问题。例如$create方法会接受多个对象作为存放组件属性,事件以及组件之间相互引用信息的集合。如果这些集合中某一项的key为toString等特定的名称,则可能就会因为无法遍历得到该项而出现错误。不过避免这个问题的方法其实也很简单,只要不使用如下的名称作为key即可:

  • toString

  • toLocaleString

  • valueOf

  • hasOwnProperty

  • isPrototypeOf

  • propertyIsEnumerable 

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消