javascript中的类式继承
来源:互联网 发布:淘宝云客服报名关闭 编辑:程序博客网 时间:2024/05/18 02:45
作者:Douglas Crockford
Java JavaScript Strongly-typedLoosely-typedStaticDynamicClassicalPrototypalClassesFunctionsConstructorsFunctionsMethodsFunctionsJavaScript是一种无类别的、面向对象的语言,而且它用原型继承而不是用类继承。对于一直被训练于使用传统的面向对象语言(比如java、c++)的程序员来说对此可能会感到疑惑。JavaScript的原型继承比传统的面向对象继承具有更强大的表达力,下面我们将会看到。
首先,我们为什么要关心继承的问题?这里主要有两个原因。首先是为了类型便利。我们希望语言系统能自动的对类似的类投射引用。从一个需要显式铸造常规引用对象的类型系统里,往往能获取较少的类型安全。在强类型语言中这是至关重要的,但是在对象引用不需要铸造的松弛语言类型比如JavaScript中这是不相干的。
另一个原因是代码重用。对于拥有相同方法实现的一些对象来说这是很常见的。类使得从单一的一套定义中来生成这些对象成为了可能。拥有一些和其他对象类似的对象也是很常见的,不同之处仅在于添加或修改一小部分的方法。类式继承对于此很有用处,但是原型继承会更有用处。
为了演示这一点,我们引入了一点sugar,它让我们可以用类似于传统的类语言的方式来写代码。我们等下回展示在类语言中获取不到的有用的模式。最后,我们会解释这些sugar。
上面的语法有点不同寻常,但是很容易从中识别出类模式。method方法有一个name和一个function参数,把它们增加给类作为公共方法。
因此我们可以这样写:
myParenizor = new Parenizor(0);
myString = myParenizor.toString();
正如你所期望的,myString 的值为"(0)"。
现在我们来写继承自Parenizor的另一个类,除了该类的toString方法在value是0的情况下产生"-0-"外,其余的成员都是相同的。
上面的inherits方法类似于Java的extends。uber方法类似于Java的super。它使得子类的方法可以调用父类的方法。(为避免保留字限制,名字已更改)
那么现在我们可以这样写:
myZParenizor = new ZParenizor(0);
myString = myZParenizor.toString();
这一次,myString的值为"-0-"。
JavaScript没有类,但是我们可以模仿。
假定有一个数值类,它有一个setValue方法来检查值是否是一个一定范围内的数值,有必要的话扔出一个异常。对于我们的ZParenizor类我们仅需要它的setValue和setRange方法。我们当然不需要它的toString方法。因此我们这样写:
ZParenizor.swiss(NumberValue, 'setValue', 'setRange');
对于我们的类上面的方法就仅仅增加我们需要的方法。
类式继承是一种is-a的关系,寄生式继承是一种was-a-but-now's-a的关系。构造函数在对象的构造函数中有一个更大的角色。注意uber这个天生的超类方法在是有方法中仍然是可以获取的到的。
因此在上面的例子中,我完全不需要一个ZParenizor类。我可以简单的修改我的实例来达到相同的目的。
我们增加了一个toString方法到我们的myParenizor实例中却没用任何形式的继承。我们可以演变个实例,因为语言是无类型的。
它增加了一个公共方法到Function.prototype,因此所有的函数可以通过类的扩充都可以得到该方法。它有一个name和一个函数参数,并把他们添加到函数的原型对象中。
它返回this。当我写一个不需要返回值的方法的时候,我通常让它返回this。这可以让你链式编程。
下面是inherits方法,它用来说明一个类从另一个类的继承。他应该该在两个类都被定义之后调用,但是要在继承类的方法被增加之前调用。
再一次的,我们扩展了Function。我们生成一个父类的实例并把它作为新的原型。我们也改正了constructor指向,并给prototype增加了uber方法。
uber方法在它的原型中寻找命名了的方法,这是在寄生式继承或者对象扩展中来调用的函数。如果我们正在做类式继承,我们需要在父亲的prototype中找到那个函数。return语句用了函数的apply方法来调用这个函数,明确的设置this并传了一个数组参数。这个参数(如果存在)从arguments类数组中获取。不幸的是,arguments并不是一个真正的数组,因此我们不得不再次调用apply方法来调用数组的slice方法。
最后,是swiss方法:
swiss方法会遍历arguments。对每一个的name,它从父类的成员中复制一份给新类的prototype。
类对象是很硬的。给一个硬对象添加一个新成员的唯一方式就是去创建一个新类。在Javascript里面,对象是软的。通过简单的分配就能把一个新成员增加到一个软的对象上。
因为JavaScript中的对象是如此的灵活,你可能想以不同的角度来思考类层次结构。深层次的结构是不恰当的。浅层次结构才有效并富有表现力。
到现在我已经写JavaScript 8年了,我从来没有发现要用uber方法的需求。super的想法在类模式中是相当重要的,但是它在原型式和函数式模式中似乎是不必要的。我现在看到,我早期在JavaScript中尝试支持类式模型的做法是错误的。
原文链接:http://www.crockford.com/javascript/inheritance.html
首先,我们为什么要关心继承的问题?这里主要有两个原因。首先是为了类型便利。我们希望语言系统能自动的对类似的类投射引用。从一个需要显式铸造常规引用对象的类型系统里,往往能获取较少的类型安全。在强类型语言中这是至关重要的,但是在对象引用不需要铸造的松弛语言类型比如JavaScript中这是不相干的。
另一个原因是代码重用。对于拥有相同方法实现的一些对象来说这是很常见的。类使得从单一的一套定义中来生成这些对象成为了可能。拥有一些和其他对象类似的对象也是很常见的,不同之处仅在于添加或修改一小部分的方法。类式继承对于此很有用处,但是原型继承会更有用处。
为了演示这一点,我们引入了一点sugar,它让我们可以用类似于传统的类语言的方式来写代码。我们等下回展示在类语言中获取不到的有用的模式。最后,我们会解释这些sugar。
类式继承
首先我们写一个Parenizor类,对于它的value它有set和get方法。另外还有一个toString方法用来包裹value在一个括号中: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方法有一个name和一个function参数,把它们增加给类作为公共方法。
因此我们可以这样写:
myParenizor = new Parenizor(0);
myString = myParenizor.toString();
正如你所期望的,myString 的值为"(0)"。
现在我们来写继承自Parenizor的另一个类,除了该类的toString方法在value是0的情况下产生"-0-"外,其余的成员都是相同的。
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没有类,但是我们可以模仿。
多重继承
通过操作一个函数的原型对象,我们可以实现多重继承,允许我们可以从多重继承的类的方法中创建一个类。混杂的多重继承可能很难实现,而且可能存在潜在的方法名冲突。我们可以在JavaScript中实现混杂的多重继承,但是对于这个例子我们会用一个更为规律的形式,被称为Swiss Inheritance。假定有一个数值类,它有一个setValue方法来检查值是否是一个一定范围内的数值,有必要的话扔出一个异常。对于我们的ZParenizor类我们仅需要它的setValue和setRange方法。我们当然不需要它的toString方法。因此我们这样写:
ZParenizor.swiss(NumberValue, 'setValue', 'setRange');
对于我们的类上面的方法就仅仅增加我们需要的方法。
寄生式继承
这有另一种方式来写ZParenizor。不用从Parenizor继承,我们写一个构造函数来调用Parenizor的构造函数,把结果作为它的返回值。并且不用增加公共方法,构造函数增加了私有方法(privileged methods)。function ZParenizor2(value) { var that = new Parenizor(value); that.toString = function () { if (this.getValue()) { return this.uber('toString'); } return "-0-" }; return that;}
类式继承是一种is-a的关系,寄生式继承是一种was-a-but-now's-a的关系。构造函数在对象的构造函数中有一个更大的角色。注意uber这个天生的超类方法在是有方法中仍然是可以获取的到的。
类扩展
JavaScript的动态性允许我们来增加或者替换一个已经存在的类的方法。我们可以在任何时候来调用method方法,而且这个类的现在和将来的所有实例都会拥有着那个方法。我们可以在任何时候来扩展一个类。继承会追溯的工作。我们调用这种类扩展来避免和Java的extends混淆,它意味着其他的一些东西。对象扩展
在静态面向对象语言中,如果你想拥有一个和另一个对象略微不同的对象,你需要定义一个新类。在JavaScript中,你可以向个体对象增加方法而不需要额外的其他类。这会有巨大的能量,因为你可以写很少的类并且你写的类也可能会非常简单。回想一下,JavaScript对象就像哈希表。你可以在任何时间为其增加新的值。如果你增加的值是一个函数,那么它就会变为一个方法。因此在上面的例子中,我完全不需要一个ZParenizor类。我可以简单的修改我的实例来达到相同的目的。
myParenizor = new Parenizor(0);myParenizor.toString = function () { if (this.getValue()) { return this.uber('toString'); } return "-0-";};myString = myParenizor.toString();
我们增加了一个toString方法到我们的myParenizor实例中却没用任何形式的继承。我们可以演变个实例,因为语言是无类型的。
Sugar
为了使上面的例子能工作,我写了四个sugar方法。首先是method方法,它用来为类增加实例方法:Function.prototype.method = function (name, func) { this.prototype[name] = func; return this;};
它增加了一个公共方法到Function.prototype,因此所有的函数可以通过类的扩充都可以得到该方法。它有一个name和一个函数参数,并把他们添加到函数的原型对象中。
它返回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。我们生成一个父类的实例并把它作为新的原型。我们也改正了constructor指向,并给prototype增加了uber方法。
uber方法在它的原型中寻找命名了的方法,这是在寄生式继承或者对象扩展中来调用的函数。如果我们正在做类式继承,我们需要在父亲的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,它从父类的成员中复制一份给新类的prototype。
结论
JavaScript可以像类语言那样被应用,但是它也有一定级别的丰富的表现力,这是很独特的。我们已经看了类式继承,Swiss继承,寄生式继承,类扩展和对象扩展。代码重用模式的这个很大的集合就来自于一种被认为比Java更简单更小型的语言。类对象是很硬的。给一个硬对象添加一个新成员的唯一方式就是去创建一个新类。在Javascript里面,对象是软的。通过简单的分配就能把一个新成员增加到一个软的对象上。
因为JavaScript中的对象是如此的灵活,你可能想以不同的角度来思考类层次结构。深层次的结构是不恰当的。浅层次结构才有效并富有表现力。
到现在我已经写JavaScript 8年了,我从来没有发现要用uber方法的需求。super的想法在类模式中是相当重要的,但是它在原型式和函数式模式中似乎是不必要的。我现在看到,我早期在JavaScript中尝试支持类式模型的做法是错误的。
0 0
- javascript中的类式继承
- javascript中的类式继承
- JavaScript中的类继承
- JavaScript中的类继承
- JavaScript中的类继承
- JavaScript中的类继承
- JavaScript中的类继承
- JavaScript中的类继承
- Javascript中的类式继承(Classical Inheritance)
- JavaScript中的Java式继承
- JavaScript中的类继承(转载)
- javascript 类式继承
- JavaScript中的创建类和继承
- JavaScript 中的类与继承(1)
- JavaScript中的继承性
- javascript 中的继承
- JavaScript 中的继承
- javascript中的继承
- ASP.Net+XML打造留言薄
- Ibatis的增删改查操作
- 纯ASP上传图像文件到数据库的最佳例子
- 黑马程序员-7k面试题之交通灯管理系统
- 一款非常不错的PHP集成环境
- javascript中的类式继承
- 设计ASP时对其安全性以及稳定性设计的经验
- VB.NET版+三层实现登陆
- 最近才发现我的程序里有如此多的漏洞
- STL简介
- JAVA编写文件格式转换UTF-8
- hdu 2085 数塔 -- dp模板题
- ASP.NET Server Control Design Time Support
- Android 自定义ViewGroup