首页 > web前端 > js教程 > 正文

JavaScript 继承详解(三)_js面向对象

php中文网
发布: 2016-05-16 18:50:02
原创
777人浏览过

注:本章中的jClass的实现参考了Simple JavaScript Inheritance的做法。

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

 function Person(name) {<BR>
this.name = name;<BR>
}<BR>
Person.prototype = {<BR>
getName: function() {<BR>
return this.name;<BR>
}<BR>
}<br><br>
function Employee(name, employeeID) {<BR>
this.name = name;<BR>
this.employeeID = employeeID;<BR>
}<BR>
Employee.prototype = new Person();<BR>
Employee.prototype.getEmployeeID = function() {<BR>
return this.employeeID;<BR>
};<BR>
var zhang = new Employee("ZhangSan", "1234");<BR>
console.log(zhang.getName()); // "ZhangSan" <BR>
登录后复制

 

android界面布局详解 中文WORD版
android界面布局详解 中文WORD版

本文档主要讲述的是android界面布局详解;在通过“Hello World!”介绍Android中的布局问题之前,不得不先介绍一下Android中的用户界面,因为布局问题也是用户界面问题之一。在一个Android应用程序中,用户界面通过View和ViewGroup对象构建。Android中有很多种Views和ViewGroups,他们都继承自View类。View对象是Android平台上表示用户界面的基本单元。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过

android界面布局详解 中文WORD版 1
查看详情 android界面布局详解 中文WORD版

修正constructor的指向错误

 

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

 var zhang = new Employee("ZhangSan", "1234");<BR>
console.log(zhang.constructor === Employee); // false<BR>
console.log(zhang.constructor === Object); // true <BR>
登录后复制
我们需要简单的修正:
 function Employee(name, employeeID) {<BR>
this.name = name;<BR>
this.employeeID = employeeID;<BR>
}<BR>
Employee.prototype = new Person();<BR>
Employee.prototype.constructor = Employee;<BR>
Employee.prototype.getEmployeeID = function() {<BR>
return this.employeeID;<BR>
};<BR>
var zhang = new Employee("ZhangSan", "1234");<BR>
console.log(zhang.constructor === Employee); // true<BR>
console.log(zhang.constructor === Object); // false<BR>
登录后复制

 

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

 

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

 // 空的构造函数<BR>
function Person() {<BR>
}<BR>
Person.prototype = {<BR>
init: function(name) {<BR>
this.name = name;<BR>
},<BR>
getName: function() {<BR>
return this.name;<BR>
}<BR>
}<BR>
// 空的构造函数<BR>
function Employee() {<BR>
}<BR>
// 创建类的阶段不会初始化父类的数据,因为Person是一个空的构造函数<BR>
Employee.prototype = new Person();<BR>
Employee.prototype.constructor = Employee;<BR>
Employee.prototype.init = function(name, employeeID) {<BR>
this.name = name;<BR>
this.employeeID = employeeID;<BR>
};<BR>
Employee.prototype.getEmployeeID = function() {<BR>
return this.employeeID;<BR>
};<BR>
登录后复制
这种方式下,必须在实例化一个对象后手工调用init函数,如下:
 var zhang = new Employee();<BR>
zhang.init("ZhangSan", "1234");<BR>
console.log(zhang.getName()); // "ZhangSan"<BR>
登录后复制

 

如何自动调用init函数?

 

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

 // 创建一个全局的状态标示 - 当前是否处于类的构造阶段<BR>
var initializing = false;<BR>
function Person() {<BR>
if (!initializing) {<BR>
this.init.apply(this, arguments);<BR>
}<BR>
}<BR>
Person.prototype = {<BR>
init: function(name) {<BR>
this.name = name;<BR>
},<BR>
getName: function() {<BR>
return this.name;<BR>
}<BR>
}<BR>
function Employee() {<BR>
if (!initializing) {<BR>
this.init.apply(this, arguments);<BR>
}<BR>
}<BR>
// 标示当前进入类的创建阶段,不会调用init函数<BR>
initializing = true;<BR>
Employee.prototype = new Person();<BR>
Employee.prototype.constructor = Employee;<BR>
initializing = false;<BR>
Employee.prototype.init = function(name, employeeID) {<BR>
this.name = name;<BR>
this.employeeID = employeeID;<BR>
};<BR>
Employee.prototype.getEmployeeID = function() {<BR>
return this.employeeID;<BR>
};<br><br>
// 初始化类实例时,自动调用类的原型函数init,并向init中传递参数<BR>
var zhang = new Employee("ZhangSan", "1234");<BR>
console.log(zhang.getName()); // "ZhangSan"<BR>
登录后复制
但是这样就必须引入全局变量,这是一个不好的信号。

 

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

 

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

 // 当前是否处于创建类的阶段<BR>
var initializing = false;<BR>
function jClass(baseClass, prop) {<BR>
// 只接受一个参数的情况 - jClass(prop)<BR>
if (typeof (baseClass) === "object") {<BR>
prop = baseClass;<BR>
baseClass = null;<BR>
}<BR>
// 本次调用所创建的类(构造函数)<BR>
function F() {<BR>
// 如果当前处于实例化类的阶段,则调用init原型函数<BR>
if (!initializing) {<BR>
this.init.apply(this, arguments);<BR>
}<BR>
}<BR>
// 如果此类需要从其它类扩展<BR>
if (baseClass) {<BR>
initializing = true;<BR>
F.prototype = new baseClass();<BR>
F.prototype.constructor = F;<BR>
initializing = false;<BR>
}<BR>
// 覆盖父类的同名函数<BR>
for (var name in prop) {<BR>
if (prop.hasOwnProperty(name)) {<BR>
F.prototype[name] = prop[name];<BR>
}<BR>
}<BR>
return F;<BR>
};<BR>
登录后复制
使用jClass函数来创建类和继承类的方法:
 var Person = jClass({<BR>
init: function(name) {<BR>
this.name = name;<BR>
},<BR>
getName: function() {<BR>
return this.name;<BR>
}<BR>
});<BR>
var Employee = jClass(Person, {<BR>
init: function(name, employeeID) {<BR>
this.name = name;<BR>
this.employeeID = employeeID;<BR>
},<BR>
getEmployeeID: function() {<BR>
return this.employeeID;<BR>
}<BR>
});<br><br>
var zhang = new Employee("ZhangSan", "1234");<BR>
console.log(zhang.getName()); // "ZhangSan"<BR>
登录后复制
OK,现在创建类和实例化类的方式看起来优雅多了。 但是这里面还存在明显的瑕疵,Employee的初始化函数init无法调用父类的同名方法。

 

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

 

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

 // 当前是否处于创建类的阶段<BR>
var initializing = false;<BR>
function jClass(baseClass, prop) {<BR>
// 只接受一个参数的情况 - jClass(prop)<BR>
if (typeof (baseClass) === "object") {<BR>
prop = baseClass;<BR>
baseClass = null;<BR>
}<BR>
// 本次调用所创建的类(构造函数)<BR>
function F() {<BR>
// 如果当前处于实例化类的阶段,则调用init原型函数<BR>
if (!initializing) {<BR>
// 如果父类存在,则实例对象的base指向父类的原型<BR>
// 这就提供了在实例对象中调用父类方法的途径<BR>
if (baseClass) {<BR>
this.base = baseClass.prototype;<BR>
}<BR>
this.init.apply(this, arguments);<BR>
}<BR>
}<BR>
// 如果此类需要从其它类扩展<BR>
if (baseClass) {<BR>
initializing = true;<BR>
F.prototype = new baseClass();<BR>
F.prototype.constructor = F;<BR>
initializing = false;<BR>
}<BR>
// 覆盖父类的同名函数<BR>
for (var name in prop) {<BR>
if (prop.hasOwnProperty(name)) {<BR>
F.prototype[name] = prop[name];<BR>
}<BR>
}<BR>
return F;<BR>
};<BR>
登录后复制
调用方式:
 var Person = jClass({<BR>
init: function(name) {<BR>
this.name = name;<BR>
},<BR>
getName: function() {<BR>
return this.name;<BR>
}<BR>
});<BR>
var Employee = jClass(Person, {<BR>
init: function(name, employeeID) {<BR>
// 调用父类的原型函数init,注意使用apply函数修改init的this指向<BR>
this.base.init.apply(this, [name]);<BR>
this.employeeID = employeeID;<BR>
},<BR>
getEmployeeID: function() {<BR>
return this.employeeID;<BR>
},<BR>
getName: function() {<BR>
// 调用父类的原型函数getName<BR>
return "Employee name: " + this.base.getName.apply(this);<BR>
}<BR>
});<br><br>
var zhang = new Employee("ZhangSan", "1234");<BR>
console.log(zhang.getName()); // "Employee name: ZhangSan"<BR>
登录后复制

 

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

唯一的缺憾是调用父类的代码太长,并且不好理解, 如果能够按照如下的方式调用岂不是更妙:

 var Employee = jClass(Person, {<BR>
init: function(name, employeeID) {<BR>
// 如果能够这样调用,就再好不过了<BR>
this.base(name);<BR>
this.employeeID = employeeID;<BR>
}<BR>
});<BR>
登录后复制

 

优化jClass函数

 

 // 当前是否处于创建类的阶段<BR>
var initializing = false;<BR>
function jClass(baseClass, prop) {<BR>
// 只接受一个参数的情况 - jClass(prop)<BR>
if (typeof (baseClass) === "object") {<BR>
prop = baseClass;<BR>
baseClass = null;<BR>
}<BR>
// 本次调用所创建的类(构造函数)<BR>
function F() {<BR>
// 如果当前处于实例化类的阶段,则调用init原型函数<BR>
if (!initializing) {<BR>
// 如果父类存在,则实例对象的baseprototype指向父类的原型<BR>
// 这就提供了在实例对象中调用父类方法的途径<BR>
if (baseClass) {<BR>
this.baseprototype = baseClass.prototype;<BR>
}<BR>
this.init.apply(this, arguments);<BR>
}<BR>
}<BR>
// 如果此类需要从其它类扩展<BR>
if (baseClass) {<BR>
initializing = true;<BR>
F.prototype = new baseClass();<BR>
F.prototype.constructor = F;<BR>
initializing = false;<BR>
}<BR>
// 覆盖父类的同名函数<BR>
for (var name in prop) {<BR>
if (prop.hasOwnProperty(name)) {<BR>
// 如果此类继承自父类baseClass并且父类原型中存在同名函数name<BR>
if (baseClass &&<BR>
typeof (prop[name]) === "function" &&<BR>
typeof (F.prototype[name]) === "function") {<br><br>
// 重定义函数name - <BR>
// 首先在函数上下文设置this.base指向父类原型中的同名函数<BR>
// 然后调用函数prop[name],返回函数结果<br><br>
// 注意:这里的自执行函数创建了一个上下文,这个上下文返回另一个函数,<BR>
// 此函数中可以应用此上下文中的变量,这就是闭包(Closure)。<BR>
// 这是JavaScript框架开发中常用的技巧。<BR>
F.prototype[name] = (function(name, fn) {<BR>
return function() {<BR>
this.base = baseClass.prototype[name];<BR>
return fn.apply(this, arguments);<BR>
};<BR>
})(name, prop[name]);<br><br>
} else {<BR>
F.prototype[name] = prop[name];<BR>
}<BR>
}<BR>
}<BR>
return F;<BR>
};<BR>
登录后复制
此时,创建类与子类以及调用方式都显得非常优雅,请看:
 var Person = jClass({<BR>
init: function(name) {<BR>
this.name = name;<BR>
},<BR>
getName: function() {<BR>
return this.name;<BR>
}<BR>
});<BR>
var Employee = jClass(Person, {<BR>
init: function(name, employeeID) {<BR>
this.base(name);<BR>
this.employeeID = employeeID;<BR>
},<BR>
getEmployeeID: function() {<BR>
return this.employeeID;<BR>
},<BR>
getName: function() {<BR>
return "Employee name: " + this.base();<BR>
}<BR>
});<br><br>
var zhang = new Employee("ZhangSan", "1234");<BR>
console.log(zhang.getName()); // "Employee name: ZhangSan"<BR>
登录后复制

 

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

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

相关标签:
java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号