Javascript中的类式继承(Classical Inheritance)
2012-08-14 17:17
190 查看
译注:因为项目需要,最近学习了下Javscript。本来以为Javascript是个弱类型的脚本语言,大致看下就能搞定。到看到其面向对象部分时,发现与C++和Java的对象机制差别很大,有种耳目一新之感。遂找了些资料看,发现有个叫Douglas
Crockford的大牛写得很深入,但都是英文的,便想把一些经典的翻译过来。此为其一。
你聪明绝顶,又自由自在
——John Lennon
Javscript是种无类型的面向对象语言,它采用一种与类式继承不同的原型式继承机制。虽然这让熟悉C++和Java等传统面向对象语言的程序员感到迷惑,但正如本文即将展示那样,Javascript的原型式继承方式比类式继承方式要强大得多。
但先有个问题——为什么我们关注继承方式呢?主要原因有二。其一是类型转化,强类型语言需要明确的机制以实现不同类型引用间的转化,但对Javascript这种弱类型语言来说,所有的对象引用都是同一类型,无需转化。其二是代码复用,可以定义一种类而建立大量具有相同方法的对象,也可以通过类的继承来创建许多具有相似方法的对象。类式继承方式在这方面做得很好,但原型式继承却能做到更好。
为说明这一点,我先引入几个基本函数,以便模仿传统类式语言的风格,然后给出一些类式语言中没有的模式,最后对这些基本函数作出解释。
虽然这段代码语法有点古怪,但还是很容易辨认出其中的类式继承模式。method方法采用一个名字和函数对象作为参数,将其作为公共方法添加到类中。
故而可以写出
如你所料,myString的值正是“(0)”。
现在创建一个继承自Parenizor的类,它除了toString方法在value非0或非空时产生“-0-”外,其它方面均与Parenizor一样。
现在可以这样写
这次,myString的值是“-0-”了。
虽然Javascript没有类,但却可以写出“类式”代码。
设存在NumberValue类,它有个setValue方法,检查属性value的值是否是某一范围内的数字,若不是则抛出异常。如果仅仅需要从这个类继承setValue和setRange给ZParenizor类话,只需这样写
因此,上述代码其实并不需要ZParenizor类,可以仅仅通过修改实例来实现。
基本函数
为使上述例子正常工作,我写了几个基本函数。首先是给类添加方法的method函数
method的返回值为this。我在写没有返回值的函数时,习惯上返回一个this,这样便于链式编程。
下一个出场的函数是inherits,从字面便可看出,它用来使一个类继承自其它类。这个函数只能在两个类都定义后但引申类添加新方法之前使用。
uber方法在它自己的prototype中寻找给定名称的方法。它在寄生继承或对象增强的情况下使用。如果使用的是类式继承,则需要在parent的prototype中寻找给定名称的方法。return语句使用函数的apply方法以明确设置this属性,及传递参数数组来调用该函数。其中的参数(如果有)是通过arguments数组来获取的。但不幸的是,arguments并非真正的数组,故不得不再次使用apply方法以调用数组的slice方法。
最后出场的是swiss方法。
类式继承是很僵硬的,给“硬对象”添加新成员的唯一方式是创建一个新类。但Javascript中对象是“软”的,新成员可以简单地通过赋值来添加。
因为Javascript中的对象有如此强大的伸缩性,所以你会想对类式继承进行一些新的思考。深层次的继承结构通常并不合适,浅层次的继承才更有效、更有力。
我已经写了8年的Javascript程序,却从未发现需要用到uber函数之处。父类在类模式中固然非常重要,但在原型和函数模式中却可有可无。现在我认为在Javascript中模仿类模型是个错误。
Crockford的大牛写得很深入,但都是英文的,便想把一些经典的翻译过来。此为其一。
Javascript 中的类式继承(Classical Inheritance)
英文原文你聪明绝顶,又自由自在
——John Lennon
Javscript是种无类型的面向对象语言,它采用一种与类式继承不同的原型式继承机制。虽然这让熟悉C++和Java等传统面向对象语言的程序员感到迷惑,但正如本文即将展示那样,Javascript的原型式继承方式比类式继承方式要强大得多。
但先有个问题——为什么我们关注继承方式呢?主要原因有二。其一是类型转化,强类型语言需要明确的机制以实现不同类型引用间的转化,但对Javascript这种弱类型语言来说,所有的对象引用都是同一类型,无需转化。其二是代码复用,可以定义一种类而建立大量具有相同方法的对象,也可以通过类的继承来创建许多具有相似方法的对象。类式继承方式在这方面做得很好,但原型式继承却能做到更好。
为说明这一点,我先引入几个基本函数,以便模仿传统类式语言的风格,然后给出一些类式语言中没有的模式,最后对这些基本函数作出解释。
类式继承
先来写个Parenizor类,它有个属性value及相应的set、get方法,和一个将value值包装在括号中的toString()方法。function Parenizor(value) { this.setValue(value); } Parenizor.method('setValue', function (value) { this.value = value; return this; }); Parenizor.method('getValue', function () { return this.value; }); Parenizor.method('toString', function () { return '(' + this.getValue() + ')'; });
虽然这段代码语法有点古怪,但还是很容易辨认出其中的类式继承模式。method方法采用一个名字和函数对象作为参数,将其作为公共方法添加到类中。
故而可以写出
myParenizor = new Parenizor(0); myString = myParenizor.toString();
如你所料,myString的值正是“(0)”。
现在创建一个继承自Parenizor的类,它除了toString方法在value非0或非空时产生“-0-”外,其它方面均与Parenizor一样。
function ZParenizor(value) { this.setValue(value); } ZParenizor.inherits(Parenizor); ZParenizor.method('toString', function () { if (this.getValue()) { return this.uber('toString'); } return "-0-"; });代码中的inherits方法与Java的extends类似。uber方法则与Java的super相当,允许在子类方法中调用父类的版本。(为避免与保留字冲突,特将名字改变了下。)
现在可以这样写
myZParenizor = new ZParenizor(0); myString = myZParenizor.toString();
这次,myString的值是“-0-”了。
虽然Javascript没有类,但却可以写出“类式”代码。
多重继承
通过操作函数的prototype属性,可以实现多重继承,以建立继承多个类的方法的新类。任意的多重继承难于实现,并会造成命名冲突。虽然Javascript可以实现任意多重继承,但本例遵从一种称为Swiss继承的原则。设存在NumberValue类,它有个setValue方法,检查属性value的值是否是某一范围内的数字,若不是则抛出异常。如果仅仅需要从这个类继承setValue和setRange给ZParenizor类话,只需这样写
ZParenizor.swiss(NumberValue, 'setValue', 'setRange');这就只添加了需要的方法。
寄生继承
还有另一种建立ZParenizor的方法。与从Parenizor继承不同,现在只需创建一个调用Parenizor()的构造函数,将调用结果返回即可。与添加公共方法不同,构造函数这次添加的是称为特权方法的方法。function ZParenizor2(value) { var that = new Parenizor(value); that.toString = function () { if (this.getValue()) { return this.uber('toString'); } return "-0-" }; return that; }类式继承是“是一个”的关系,寄生继承是“以前是但现在不是”的关系。构造函数在对象的构造过程中作用重大。需要注意的是,uber方法在特权函数中仍然可用。
类增强
Javscript允许增加或替换任意一个已经存在的类的方法,并且可以在任何时候在该类现有的或将来的任意实例中调用该方法。任意时刻都可以直接扩展一个类。但继承却只能在父类改变后,才可以在新继承的子类实例中调用改变后的方法。我们将这称为类增强,以避免与Java中具有另外含义的扩展(extends)相冲突。对象增强
在静态面向对象语言中,即使需要一个与其它对象稍微不同的新对象,也要定义一个新类。但在Javascript中,可以为单个对象添加方法,而无需定义额外的类。这一点威力无穷,因为它可以减少类的数量和编写代码的难度。回忆下,Javascrip的t对象就像个哈希表,可以随时添加新属性,如果属性值是函数,则添加的就是新方法。因此,上述代码其实并不需要ZParenizor类,可以仅仅通过修改实例来实现。
myParenizor = new Parenizor(0); myParenizor.toString = function () { if (this.getValue()) { return this.uber('toString'); } return "-0-"; }; myString = myParenizor.toString();现在只是给myParenizor对象添加了个toString方法,而无需使用任何形式的继承。正是因为Javascript语言没有类的概念,我们才能演化任一单个对象实例。
基本函数
为使上述例子正常工作,我写了几个基本函数。首先是给类添加方法的method函数Function.prototype.method = function (name, func) { this.prototype[name] = func; return this; };它给Function.prototype添加公共方法,以使所有函数都能通过类增强来获得该方法。它采用一个名字和一个函数作为参数,将其作为函数添加到函数的prototype对象中。
method的返回值为this。我在写没有返回值的函数时,习惯上返回一个this,这样便于链式编程。
下一个出场的函数是inherits,从字面便可看出,它用来使一个类继承自其它类。这个函数只能在两个类都定义后但引申类添加新方法之前使用。
Function.method('inherits', function (parent) { var d = {}, p = (this.prototype = new parent()); this.method('uber', function uber(name) { if (!(name in d)) { d[name] = 0; } var f, r, t = d[name], v = parent.prototype; if (t) { while (t) { v = v.constructor.prototype; t -= 1; } f = v[name]; } else { f = p[name]; if (f == this[name]) { f = v[name]; } } d[name] += 1; r = f.apply(this, Array.prototype.slice.apply(arguments, [1])); d[name] -= 1; return r; }); return this; });又一次增强了Function!我们创建了parent的新实例,并将其作为新的prototype,同时也修改了constructor属性,及为prototype添加uber方法。
uber方法在它自己的prototype中寻找给定名称的方法。它在寄生继承或对象增强的情况下使用。如果使用的是类式继承,则需要在parent的prototype中寻找给定名称的方法。return语句使用函数的apply方法以明确设置this属性,及传递参数数组来调用该函数。其中的参数(如果有)是通过arguments数组来获取的。但不幸的是,arguments并非真正的数组,故不得不再次使用apply方法以调用数组的slice方法。
最后出场的是swiss方法。
Function.method('swiss', function (parent) { for (var i = 1; i < arguments.length; i += 1) { var name = arguments[i]; this.prototype[name] = parent.prototype[name]; } return this; });swiss方法遍历arguments参数,对每个name属性,从parent的prototype复制一份给新类的prototype。
结论
虽然Javascript可以像类式继承那样使用,但它依旧有些非常独特的特性。我们阐述了类式继承、Swiss继承、寄生继承、类增强、对象增强。但这许多的复用模式却是来自一门比Java更小更简单的语言。类式继承是很僵硬的,给“硬对象”添加新成员的唯一方式是创建一个新类。但Javascript中对象是“软”的,新成员可以简单地通过赋值来添加。
因为Javascript中的对象有如此强大的伸缩性,所以你会想对类式继承进行一些新的思考。深层次的继承结构通常并不合适,浅层次的继承才更有效、更有力。
我已经写了8年的Javascript程序,却从未发现需要用到uber函数之处。父类在类模式中固然非常重要,但在原型和函数模式中却可有可无。现在我认为在Javascript中模仿类模型是个错误。
相关文章推荐
- javascript中的类式继承
- javascript中的类式继承
- 关于javascript中的类式继承
- javascript大师Douglas Crockford的类式继承
- 【javascript继承】之——原型链继承和类式继承
- javascript 类式继承
- Javascript继承1:子类的的原型对象----类式继承
- JavaScript 的原型继承与类式继承学习笔记
- JavaScript 类式继承与原型继承
- javascript中的类式继承
- JavaScript _proto_、prototype原型、原型链、constructor构造器、类式继承、原型继承
- javascript类式继承模式#1——默认模式
- javascript 类式继承与原型继承
- 【javascript继承】之——原型链继承和类式继承
- JavaScript中的类式继承和原型式继承
- javascript设计模式学习笔记之“类式继承”
- 再谈Javascript原型继承
- JavaScript实现继承的5种方式
- JavaScript继承详解(五)
- javascript伪类继承修订版