JavaScript继承详解

首先让咱们来回顾一下第一章中介绍的例子:第一章html

 

 1 function Person(name) {
 2 
 3 this.name = name;
 4 
 5 }
 6 
 7 Person.prototype = {
 8 
 9 getName: function() {
10 
11 return this.name;
12 
13 }
14 
15 }
16 
17 
18 
19 function Employee(name, employeeID) {
20 
21 this.name = name;
22 
23 this.employeeID = employeeID;
24 
25 }
26 
27 Employee.prototype = new Person();
28 
29 Employee.prototype.getEmployeeID = function() {
30 
31 return this.employeeID;
32 
33 };
34 
35 var zhang = new Employee("ZhangSan", "1234");
36 
37 console.log(zhang.getName()); // "ZhangSan" 

 

修正constructor的指向错误

 

从上一篇文章中关于constructor的描述,咱们知道Employee实例的constructor会有一个指向错误,以下所示:闭包

 var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.constructor === Employee); // false
console.log(zhang.constructor === Object); // true

咱们须要简单的修正:app

 function Employee(name, employeeID) {
this.name = name;
this.employeeID = employeeID;
}
Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
Employee.prototype.getEmployeeID = function() {
return this.employeeID;
};
var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.constructor === Employee); // true
console.log(zhang.constructor === Object); // false

 

 

建立Employee类时实例化Person是不合适的

 

但另外一方面,咱们又必须依赖于这种机制来实现继承。 解决办法是不在构造函数中初始化数据,而是提供一个原型方法(好比init)来初始化数据。框架

 // 空的构造函数
function Person() {
}
Person.prototype = {
init: function(name) {
this.name = name;
},
getName: function() {
return this.name;
}
}
// 空的构造函数
function Employee() {
}
// 建立类的阶段不会初始化父类的数据,由于Person是一个空的构造函数
Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
Employee.prototype.init = function(name, employeeID) {
this.name = name;
this.employeeID = employeeID;
};
Employee.prototype.getEmployeeID = function() {
return this.employeeID;
};

这种方式下,必须在实例化一个对象后手工调用init函数,以下:函数

 var zhang = new Employee();
zhang.init("ZhangSan", "1234");
console.log(zhang.getName()); // "ZhangSan"

 

 

如何自动调用init函数?

 

必须达到两个效果,构造类时不要调用init函数和实例化对象时自动调用init函数。看来咱们须要在调用空的构造函数时有一个状态标示。优化

 // 建立一个全局的状态标示 - 当前是否处于类的构造阶段
var initializing = false;
function Person() {
if (!initializing) {
this.init.apply(this, arguments);
}
}
Person.prototype = {
init: function(name) {
this.name = name;
},
getName: function() {
return this.name;
}
}
function Employee() {
if (!initializing) {
this.init.apply(this, arguments);
}
}
// 标示当前进入类的建立阶段,不会调用init函数
initializing = true;
Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
initializing = false;
Employee.prototype.init = function(name, employeeID) {
this.name = name;
this.employeeID = employeeID;
};
Employee.prototype.getEmployeeID = function() {
return this.employeeID;
};

// 初始化类实例时,自动调用类的原型函数init,并向init中传递参数
var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.getName()); // "ZhangSan"

可是这样就必须引入全局变量,这是一个很差的信号。this

 

 

如何避免引入全局变量initializing?

 

咱们须要引入一个全局的函数来简化类的建立过程,同时封装内部细节避免引入全局变量。spa

 // 当前是否处于建立类的阶段
var initializing = false;
function jClass(baseClass, prop) {
// 只接受一个参数的状况 - jClass(prop)
if (typeof (baseClass) === "object") {
prop = baseClass;
baseClass = null;
}
// 本次调用所建立的类(构造函数)
function F() {
// 若是当前处于实例化类的阶段,则调用init原型函数
if (!initializing) {
this.init.apply(this, arguments);
}
}
// 若是此类须要从其它类扩展
if (baseClass) {
initializing = true;
F.prototype = new baseClass();
F.prototype.constructor = F;
initializing = false;
}
// 覆盖父类的同名函数
for (var name in prop) {
if (prop.hasOwnProperty(name)) {
F.prototype[name] = prop[name];
}
}
return F;
};

使用jClass函数来建立类和继承类的方法:prototype

 var Person = jClass({
init: function(name) {
this.name = name;
},
getName: function() {
return this.name;
}
});
var Employee = jClass(Person, {
init: function(name, employeeID) {
this.name = name;
this.employeeID = employeeID;
},
getEmployeeID: function() {
return this.employeeID;
}
});

var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.getName()); // "ZhangSan"

OK,如今建立类和实例化类的方式看起来优雅多了。 可是这里面还存在明显的瑕疵,Employee的初始化函数init没法调用父类的同名方法。code

 

 

如何调用父类的同名方法?

 

咱们能够经过为实例化对象提供一个base的属性,来指向父类(构造函数)的原型,以下:

 // 当前是否处于建立类的阶段
var initializing = false;
function jClass(baseClass, prop) {
// 只接受一个参数的状况 - jClass(prop)
if (typeof (baseClass) === "object") {
prop = baseClass;
baseClass = null;
}
// 本次调用所建立的类(构造函数)
function F() {
// 若是当前处于实例化类的阶段,则调用init原型函数
if (!initializing) {
// 若是父类存在,则实例对象的base指向父类的原型
// 这就提供了在实例对象中调用父类方法的途径
if (baseClass) {
this.base = baseClass.prototype;
}
this.init.apply(this, arguments);
}
}
// 若是此类须要从其它类扩展
if (baseClass) {
initializing = true;
F.prototype = new baseClass();
F.prototype.constructor = F;
initializing = false;
}
// 覆盖父类的同名函数
for (var name in prop) {
if (prop.hasOwnProperty(name)) {
F.prototype[name] = prop[name];
}
}
return F;
};

调用方式:

 var Person = jClass({
init: function(name) {
this.name = name;
},
getName: function() {
return this.name;
}
});
var Employee = jClass(Person, {
init: function(name, employeeID) {
// 调用父类的原型函数init,注意使用apply函数修改init的this指向
this.base.init.apply(this, [name]);
this.employeeID = employeeID;
},
getEmployeeID: function() {
return this.employeeID;
},
getName: function() {
// 调用父类的原型函数getName
return "Employee name: " + this.base.getName.apply(this);
}
});

var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.getName()); // "Employee name: ZhangSan"

 

目前为止,咱们已经修正了在第一章手工实现继承的种种弊端。 经过咱们自定义的jClass函数来建立类和子类,经过原型方法init初始化数据, 经过实例属性base来调用父类的原型函数。

惟一的缺憾是调用父类的代码太长,而且很差理解, 若是可以按照以下的方式调用岂不是更妙:

 var Employee = jClass(Person, {
init: function(name, employeeID) {
// 若是可以这样调用,就再好不过了
this.base(name);
this.employeeID = employeeID;
}
});

 

优化jClass函数

 

 // 当前是否处于建立类的阶段
var initializing = false;
function jClass(baseClass, prop) {
// 只接受一个参数的状况 - jClass(prop)
if (typeof (baseClass) === "object") {
prop = baseClass;
baseClass = null;
}
// 本次调用所建立的类(构造函数)
function F() {
// 若是当前处于实例化类的阶段,则调用init原型函数
if (!initializing) {
// 若是父类存在,则实例对象的baseprototype指向父类的原型
// 这就提供了在实例对象中调用父类方法的途径
if (baseClass) {
this.baseprototype = baseClass.prototype;
}
this.init.apply(this, arguments);
}
}
// 若是此类须要从其它类扩展
if (baseClass) {
initializing = true;
F.prototype = new baseClass();
F.prototype.constructor = F;
initializing = false;
}
// 覆盖父类的同名函数
for (var name in prop) {
if (prop.hasOwnProperty(name)) {
// 若是此类继承自父类baseClass而且父类原型中存在同名函数name
if (baseClass &&
typeof (prop[name]) === "function" &&
typeof (F.prototype[name]) === "function") {

// 重定义函数name -
// 首先在函数上下文设置this.base指向父类原型中的同名函数
// 而后调用函数prop[name],返回函数结果

// 注意:这里的自执行函数建立了一个上下文,这个上下文返回另外一个函数,
// 此函数中能够应用此上下文中的变量,这就是闭包(Closure)。
// 这是JavaScript框架开发中经常使用的技巧。
F.prototype[name] = (function(name, fn) {
return function() {
this.base = baseClass.prototype[name];
return fn.apply(this, arguments);
};
})(name, prop[name]);

} else {
F.prototype[name] = prop[name];
}
}
}
return F;
};

此时,建立类与子类以及调用方式都显得很是优雅,请看:

 var Person = jClass({
init: function(name) {
this.name = name;
},
getName: function() {
return this.name;
}
});
var Employee = jClass(Person, {
init: function(name, employeeID) {
this.base(name);
this.employeeID = employeeID;
},
getEmployeeID: function() {
return this.employeeID;
},
getName: function() {
return "Employee name: " + this.base();
}
});

var zhang = new Employee("ZhangSan", "1234");
console.log(zhang.getName()); // "Employee name: ZhangSan"

 

至此,咱们已经建立了一个完善的函数jClass, 帮助咱们在JavaScript中以比较优雅的方式实现类和继承。

在之后的章节中,咱们会陆续分析网上一些比较流行的JavaScript类和继承的实现。 不过万变不离其宗,那些实现也无非把咱们这章中提到的概念颠来簸去的“炒做”, 为的就是一种更优雅的调用方式。

 

本文转载自:http://www.cnblogs.com/sanshi/archive/2009/07/09/1519890.html

相关文章
相关标签/搜索