8
1
们之间的关系,但即使最小化的树结构所表示的心智模式,对我们而言通常也过于
复杂,我们无法快速理解它。
类继承适合为现有的、易于理解的层级结构建模,明确地声明 FileStream是一种
Input Stream,这没有问题。但如果主要动机是函数复用(通常如此),那么为实
现类继承而建立起来的层级结构,很快就会变为充斥着毫无意义的子类型的迷宫
令人感到棘手,并且还会增加代码的冗余程度和维护代码逻辑的难度。
原型
大多数行为是否能映射到客观上“正确”的类别,这点仍值得怀疑。并且,主张类
继承的一派也的确遭遇到了一伙狂热分子的挑战,他们同是
JavaScript
的忠诚捍卫
者。他们宣称
JavaScript
是一种基于原型而不是面向对象的语言,任何带有“类”
字样的方法根本不适用于
JavaScript
。但“原型”的含义是什么?原型和类有着怎样
的区别?
用通用的编程术语来讲,原型是指为其他对象提供基本行为的对象。其他对象可在
此基础上扩展基本行为,加入个性化行为。该过程也称为有差异的继承,有别于类
继承的是它不需要明确指定类型(静态或动态),从形式上而言,它也不是在一种
类型的基础上定义其他类型。类继承的目的是复用,而原型继承则不一定。
一般而言,开发人员使用原型,通常不是为了分类,而是为了利用原型和对
象之间的相似性。
——
Antero Taivalsaari
,诺基亚研究中心
JavaScript
的每个对象均指向一个原型对象并继承其属性。
JavaScript
的原型是实现
复用的好工具:一个原型实例可以为依赖于它的无数实例定义属性。原型还可以继
承自其他原型,从而形成原型链。
到此为止还好理解。但
JavaScript
仿效
Java
,将 prototype 属性绑定到构造器,其
结果是多个层级的对象继承通常需要链接构造器和原型来实现。在介绍
JavaScript
之美的书里,加入
JavaScript
原型链的标准实现方法,可能会令读者望而生畏,简
单来讲,为了定义继承者的初始化属性,而为基础原型创建一个新实例的做法,既
不优雅也不直观。另一种方法
[
手动复制不同原型的属性,改动构造器
constructor
的属性,以此来伪造原型继承
]
则更不可取。
美丽的
mixin
9
构造器—原型链句法,不优雅我们就不说了。它的缺点还体现在需要预先规划,比
起真正的原型关系,它的结构更像类继承语言中的传统层级关系:构造器表示类型
(类),每个类型被定义为一个(且仅有一个)超类型的子类型,所有属性均通过
该类型链来继承。
ES6
class 关键字只是将现有实现方式形式化。构造器—原型链,
句法特征笨拙,确实谈不上优美,撇开这些不谈,传统
JavaScript
的原型化程度显
然没有某些人认为的那样明显。
ES5
标准引入了 Object.create,以增加原型继承的灵活度,拓展应用场景。该方
法允许将原型直接赋给对象,
JavaScript
原型不再受限于构造器(和类型的限制),
因此从理论上讲,对象可以从其他任意对象获得行为,且不受类型转换的限制:
(classes), each type is defined as a subtype of one (and only one) supertype, and all
properties are inherited via this type chain. The ES6
class keyword merely formalizes
the existing semantics. Leaving aside the gnarly and distinctly unbeautiful syntax char-
acteristic in constructor-prototype chains, traditional JavaScript is clearly less prototy-
pal than some would claim.
In an attempt to support less rigid, more opportunistic prototypes, the ES5 specifica-
tion introduced
Object.create. This method allows a prototype to be assigned to an
object directly and therefore liberates JavaScript prototypes from constructors (and
thus categorization) so that, in theory, an object can acquire behavior from any other
arbitrary object and be free from the constraints of typecasting:
var circle = Object.create({
area: function() {
return Math.PI * this.radius * this.radius;
},
grow: function() {
this.radius++;
},
shrink: function() {
this.radius--;
}
});
Object.create accepts an optional second argument representing the object to be
extended. Sadly, instead of accepting the object itself (in the form of a literal, variable,
or argument), the method expects a full-blown
meta property definition:
var circle = Object.create({
/*see above*/
}, {
radius: {
writable:true, configurable:true, value: 7
}
});
Assuming no one actually uses these unwieldy beasts in real code, all that remains is
to manually assign properties to the instance after it has been created. Even then, the
Object.create syntax still only enables an object to inherit the properties of a single
prototype. In real scenarios, we often want to acquire behavior from multiple proto-
type objects: for example, a person can be an employee and a manager.
Mixins
Fortunately, JavaScript offers viable alternatives to inheritance chaining. In contrast to
objects in more rigidly structured languages, JavaScript objects can invoke any func-
tion property regardless of lineage. In other words, JavaScript functions don’t need to
MIXINS
3
Object.create 方法的第
2
个参数可选,表示继承自哪个对象。不幸的是第
2
个参
数不是对象自身(字面量、变量或参数),而是一个完整的 meta 属性定义:
(classes), each type is defined as a subtype of one (and only one) supertype, and all
properties are inherited via this type chain. The ES6
class keyword merely formalizes
the existing semantics. Leaving aside the gnarly and distinctly unbeautiful syntax char-
acteristic in constructor-prototype chains, traditional JavaScript is clearly less prototy-
pal than some would claim.
In an attempt to support less rigid, more opportunistic prototypes, the ES5 specifica-
tion introduced
Object.create. This method allows a prototype to be assigned to an
object directly and therefore liberates JavaScript prototypes from constructors (and
thus categorization) so that, in theory, an object can acquire behavior from any other
arbitrary object and be free from the constraints of typecasting:
var circle = Object.create({
area: function() {
return Math.PI * this.radius * this.radius;
},
grow: function() {
this.radius++;
},
shrink: function() {
this.radius--;
}
});
Object.create accepts an optional second argument representing the object to be
extended. Sadly, instead of accepting the object itself (in the form of a literal, variable,
or argument), the method expects a full-blown
meta property definition:
var circle = Object.create({
/*see above*/
}, {
radius: {
writable:true, configurable:true, value: 7
}
});
Assuming no one actually uses these unwieldy beasts in real code, all that remains is
to manually assign properties to the instance after it has been created. Even then, the
Object.create syntax still only enables an object to inherit the properties of a single
prototype. In real scenarios, we often want to acquire behavior from multiple proto-
type objects: for example, a person can be an employee and a manager.
Mixins
Fortunately, JavaScript offers viable alternatives to inheritance chaining. In contrast to
objects in more rigidly structured languages, JavaScript objects can invoke any func-
tion property regardless of lineage. In other words, JavaScript functions don’t need to
MIXINS
3
假如没人愿意在生产代码中使用这些笨拙的继承方法,那么只好在创建实例后,手
动将属性赋给它。即便如此,Object.create 方法也只是允许对象继承某个原型的
属性。但真实应用场景,往往需要从多个原型对象获得行为
,
例如,一个人可以兼
有雇员和经理身份。

Get JavaScript 之美 now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.