JavaScript可让咱们经过原型实现继承,许多开发人员,尤为是那些有传统面向对象背景的开发人员,都但愿将JavaScript的继承系统简化并抽象成一个他们更熟悉的系统。
因此,这不可避免地引导咱们走向类的领域。类是面向对象开发人员所指望的内容,尽管JavaScript自己不支持传统的类继承。
一般,这些开发人员但愿它有以下特性:javascript
//经过subClass()方法,建立一个Person类做为Object的一个子类,该方法以后实现
var Person = Object.subClass({
init: function (isDancing) {
this.dancing = isDancing;
},
dance: function () {
return this.dancing;
}
});
//经过继承Person类,建立一个Ninja子类
var Ninja = Person.subClass({
init: function () {
//须要一种调用父类构造器的方法——这里展现咱们将这样作
this._super(false);
},
dance: function () {
//Ninja-specific stuff here
return this._super();
},
swingSword: function () {
return true;
}
});
//建立一个实例对Person类进行测试,看其是否可以跳舞
var person = new Person(true);
assert(person.dance(),
"The person is dancing.");
//建立一个实例对Ninja类进行测试,看其是否有swingSword方法以及继承过来的dance方法
var ninja = new Ninja();
assert(ninja.swingSword(),
"The sword is swinging.");
assert(!ninja.dance(),
"The ninja is not dancing.");
//执行instanceof测试,验证类的继承
assert(person instanceof Person,
"Person is a Person");
assert(ninja instanceof Ninja && ninja instanceof Person,
"Ninja is a Ninja and a Person");复制代码
注意事项:java
(function () {
var initializing = false,
//粗糙的正则表达式用于判断函数是否能够被序列化。
superPattern =
/xyz/.test(function () {
xyz;
})?
/\b_super\b/:
/.*/;
//给Object添加一个subClass方法
Object.subClass = function (properties) {
var _super = this.prototype;
//初始化超类
initializing = true;
var proto = new this();
initializing = false;
for (var name in properties) {
//将属性复制到prototype里
proto[name] = typeof properties[name] === 'function' &&
typeof _super[name] === 'function' &&
superPattern.test(properties[name]) ?
//定义一个重载函数
(function (name, fn) {
return function () {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
}
})(name, properties[name]) :
properties[name];
}
//创造一个仿真类构造器
function Class() {
if (!initializing && this.init) {
this.init.apply(this, arguments);
}
}
//设置类的原型
Class.prototype = proto;
//重载构造器引用
Class.constructor = Class;
//让类继续可扩展
Class.subClass = arguments.callee;
return Class;
};
})();复制代码
代码实现的一开始就很深奥,并且还可能让人困惑。在后续代码中,咱们须要知道浏览器是否支持函数序列化。但该测试又有至关复杂的语法,因此如今就要获得结果,而后保存结果,以便在后续代码中再也不进行复杂的操做,由于后续代码自己已经够复杂了。
函数序列化就是简单接收一个函数,而后返回该函数的源码文本。稍后,咱们可使用这种方法检查一个函数在咱们感兴趣的对象中是否存在引用。
在大多数浏览器中,函数的toString()方法都会奏效。通常来讲 ,一个函数在其上下文中序列化成字符串,会致使它的toString()方法被调用。因此,能够用这种方法测试函数是否能够序列化。
在设置一个名为initializing的变量为false以后,咱们使用以下表达式测试一个函数是否可以被序列化:正则表达式
/xyz/.test(function () { xyz; })复制代码
该表达式建立一个包含xyz的函数,将该函数传递给正则表达式的test()方法,该正则表达式对字符串“xyz”进行测试。若是函数可以正常序列化(test()方法将接收一个字符串,而后将触发函数的toString()方法),最终结果将返回true。
使用该文本表达式,咱们在随后的代码中使用了该正则表达式:浏览器
superPattern =
/xyz/.test(function () {
xyz;
}) ?
/\b_super\b/ :
/.*/;复制代码
创建了一个名为superPattern的变量,稍后用它来判断一个函数是否包含字符串"_super"。只有函数支持序列化才能进行判断,因此在不支持序列化的浏览器上,咱们使用一个匹配任意字符串的模式进行代替。服务器
此时,咱们准备开始定义一个方法用于子类化父类,咱们使用以下代码进行实现:app
Object.subClass = function (properties) {
var _super = this.prototype;复制代码
给Object添加一个subClass()方法,该方法接收一个参数,该参数是咱们指望添加到子类的属性集。
为了用函数原型模拟继承,咱们建立父类的一个实例,并将其赋值给子类的原型。咱们在代码中定义了一个initializing变量,每当咱们想使用原型实例化一个类的时候,都将该变量设置为true。
所以,在构造实例时,咱们能够确保再也不实例化模式下进行构建实例,并能够相应地运行或跳过init()方法:函数
if (!initializing && this.init) {
this.init.apply(this, arguments);
}复制代码
尤为重要的是,init()方法能够运行各类昂贵的启动代码(链接到服务器、建立DOM元素,还有其余未知内容),因此若是只是建立一个实例做为原型的话,咱们要避免任何没必要要的昂贵启动代码。测试
大多数支持继承的语言中,在一个方法被覆盖时,咱们保留了访问被覆盖方法的能力。这是颇有用的,由于有时候咱们是想彻底替换方法的功能,但有时候咱们却只是想增长它。在咱们特定的实现中,咱们建立一个名为_super的临时新方法,该方法只能从子类方法内部进行访问,而且该方法引用的是父类中的原有方法。
例如:ui
var Person = Object.subClass({
init: function (isDancing) {
this.dancing = isDancing;
}
});
var Ninja = Person.subClass({
init: function () {
this._super(false);
}
});复制代码
在Ninja构造器内,咱们调用了Person的构造器,并传入了一个相应的值。这能够防止从新复制代码——咱们能够重用父类中已经编写好的代码。
该功能的实现是一个多步骤的过程。为了加强子类,咱们向subClass()方法传入了一个对象哈希,只须要将父类的属性和传入的属性合并在一块儿就能够了。
首先,使用以下代码,建立一个超类的实例做为一个原型:this
initializing = true;
var proto = new this();
initializing = false;复制代码
注意,咱们是如何“保护”初始化代码的,正如咱们在前一节中讨论的initializing变量的值。
如今,是时候将传入的属性合并到proto对象中了。若是不在乎父类函数,合并代码将很是简单:
for (var name in properties) {
proto[name] = properties[name];
}复制代码
可是,咱们须要关心父类的函数,因此前面的代码和除了调用父类函数的函数以外是等价的。重写函数时,能够经过_super调用父函数,咱们须要经过名为_super的属性,将子类函数和父类函数的引用进行包装。但在完成该操做以前,咱们须要检测即将被包装的子类函数。可使用以下条件表达式:
typeof properties[name] === "function" &&
typeof _super[name] === "function" &&
superPattern.test( properties[name] )复制代码
这个表达式包含三个检测条件:
只有三个条件都为true的时候,咱们才能作所要作的事情,而不是复制属性值。注意,咱们使用了以前设置的正则表达式,和函数序列化一块儿,测试函数是否会调用等效的父类。
若是条件表达式代表咱们必须包装功能,咱们经过给即时函数的结果进行赋值,将该结果做为子类的属性:
(function (name, fn) {
return function () {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
}
})(name, properties[name])复制代码
该即时函数建立并返回了一个新函数,该新函数包装并执行了子类的函数,同时能够经过_super属性访问父函数。首先须要先保持旧的this._super引用(无论它是否存在),而后处理完之后再恢复该引用。这在同名变量已经存在的状况下会颇有用(不想意外的丢失它)。接下来,建立新的_super方法,它只是在父类原型中已经存在的一个方法的引用。值得庆幸的是,咱们不须要作任何额外的代码修改或做用域修改。当函数成为咱们对象的一个属性时,该函数的上下文会自动设置(this引用的是当前的子类实例,而不是父类实例)。最后,调用原始的子类方法执行本身的工做(也有可能使用了_super),而后将_super恢复成原来的状态,并将方法调用结果进行返回。有不少方式能够达到相似的结果(有的实现,会经过访问arguments.callee,将_super方法绑定到方法自身),可是该特定技术提供了良好的可用性和简便性。