JavaScript继承详解(四)

在本章中,我们将分析Douglas Crockford关于JavaScript继承的一个实现 – Classical Inheritance in JavaScript。 Crockford是JavaScript开发社区最知名的权威,是JSON、JSLint、和ADSafe之父,是《JavaScript: The Good Parts》的作者。 现在是Yahoo的资深JavaScript架构师,参与YUI的设计开发。 这里有一篇文章详细介绍了Crockford的生平和著作。当然Crockford也是我等小辈崇拜的对象。

调用方式

首先让我们看下使用Crockford式继承的调用方式: 注意:代码中的method、inherits、uber都是自定义的对象,我们会在后面的代码分析中详解。

// 定义Person类function Person(name) {this.name = name;}// 定义Person的原型方法Person.method("getName", function() {return this.name;});// 定义Employee类function Employee(name, employeeID) {this.name = name;this.employeeID = employeeID;}// 指定Employee类从Person类继承Employee.inherits(Person);// 定义Employee的原型方法Employee.method("getEmployeeID", function() {return this.employeeID;});Employee.method("getName", function() {// 注意,可以在子类中调用父类的原型方法return "Employee name: " + this.uber("getName");});// 实例化子类var zhang = new Employee("ZhangSan", "1234");console.log(zhang.getName()); // "Employee name: ZhangSan"

这里面有几处不得不提的硬伤:

当然Crockford的实现还支持子类中的方法调用带参数的父类方法,如下例子:

function Person(name) {this.name = name;}Person.method("getName", function(prefix) {return prefix + this.name;});function Employee(name, employeeID) {this.name = name;this.employeeID = employeeID;}Employee.inherits(Person);Employee.method("getName", function() {// 注意,uber的第一个参数是要调用父类的函数名称,后面的参数都是此函数的参数// 个人觉得这样方式不如这样调用来的直观:this.uber("Employee name: ")return this.uber("getName", "Employee name: ");});var zhang = new Employee("ZhangSan", "1234");console.log(zhang.getName()); // "Employee name: ZhangSan"代码分析

首先method函数的定义就很简单了:

<pre name="code" class="javascript">Function.prototype.method = function(name, func) {// this指向当前函数,也即是typeof(this) === "function"this.prototype[name] = func;return this;};要特别注意这里this的指向。当我们看到this时,不能仅仅关注于当前函数,而应该想到当前函数的调用方式。 比如这个例子中的method我们不会通过new的方式调用,所以method中的this指向的是当前函数。

inherits函数的定义有点复杂:

Function.method('inherits', function (parent) {// 关键是这一段:this.prototype = new parent(),这里实现了原型的引用var d = {}, p = (this.prototype = new parent());// 只为子类的原型增加uber方法,这里的Closure是为了在调用uber函数时知道当前类的父类的原型(也即是变量 – v)this.method('uber', function uber(name) {// 这里考虑到如果name是存在于Object.prototype中的函数名的情况// 比如 "toString" in {} === trueif (!(name in d)) {// 通过d[name]计数,不理解具体的含义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 {// 个人觉得这段代码有点繁琐,既然uber的含义就是父类的函数,那么f直接指向v[name]就可以了f = p[name];if (f == this[name]) {f = v[name];}}d[name] += 1;// 执行父类中的函数name,但是函数中this指向当前对象// 同时注意使用Array.prototype.slice.apply的方式对arguments进行截断(因为arguments不是标准的数组,没有slice方法)r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));d[name] -= 1;return r;});return this;});注意,在inherits函数中还有一个小小的BUG,那就是没有重定义constructor的指向,所以会发生如下的错误:var zhang = new Employee("ZhangSan", "1234");console.log(zhang.getName()); // "Employee name: ZhangSan"console.log(zhang.constructor === Employee); // falseconsole.log(zhang.constructor === Person);// true改进建议

根据前面的分析,个人觉得method函数必要性不大,反而容易混淆视线。 而inherits方法可以做一些瘦身(因为Crockford可能考虑更多的情况,原文中介绍了好几种使用inherits的方式,而我们只关注其中的一种), 并修正了constructor的指向错误。

Function.prototype.inherits = function(parent) {this.prototype = new parent();this.prototype.constructor = this;this.prototype.uber = function(name) {f = parent.prototype[name];return f.apply(this, Array.prototype.slice.call(arguments, 1));};};调用方式: function Person(name) {this.name = name;}Person.prototype.getName = function(prefix) {return prefix + this.name;};function Employee(name, employeeID) {this.name = name;this.employeeID = employeeID;}Employee.inherits(Person);Employee.prototype.getName = function() {return this.uber("getName", "Employee name: ");};var zhang = new Employee("ZhangSan", "1234");console.log(zhang.getName()); // "Employee name: ZhangSan"console.log(zhang.constructor === Employee); // true有点意思 爱人,却不一定能够听懂。他们听见的,多是抱怨不休,心烦意乱。

JavaScript继承详解(四)

相关文章:

你感兴趣的文章:

标签云: