转至繁体中文版     | 网站首页 | 图文教程 | 资源下载 | 站长博客 | 图片素材 | 武汉seo | 武汉网站优化 | 
最新公告:     敏韬网|教学资源学习资料永久免费分享站!  [mintao  2008年9月2日]        
您现在的位置: 学习笔记 >> 图文教程 >> 站长学院 >> Web开发 >> 正文
JavaScript 面向对象编程, 第一部分: 继承         ★★★★

JavaScript 面向对象编程, 第一部分: 继承

作者:闵涛 文章来源:闵涛的学习笔记 点击数:1597 更新时间:2009/4/23 11:25:53

  我们将向你展示 JavaScript 如何实现面向对象的语言中的: 继承. 同时, 这些例子将向你展示如何实现类的封装. 在此, 我们不会讨论多态实现.

  虽然 JavaScript 是脚本语言, 但它所支持的面向对象编程也是非常强大的. 虽然它没有类和实例, 但它有对象, 原型和隐式的继承. 我们将会解释如何模拟继承及其超类与子类之间关系的形式. 原型是理解继承概念的关键, 我们将会教你如何建立原型, 如何检测一个对象是否是另外一个对象的原型, 及其 JavaScript 的模型与 Java 面向对象编程之间的区别. 我们同样会向你展示如何检测对象所包含的各种属性的方法. 在另外一篇文章里, 我还会详细地讲解有关 "原型链 (prototype chain)" 的知识.

  本文大量地参考了 Webreference.com 中 "Object-Oriented Programming with JavaScript, Part I: Inheritance" 的内容, 许多内容我进行了详细的测试和再探讨, 以保证内容不会有太大的失误.
原文地址: http://www.webreference.com/js/column79/

  面向对象语言的特点

  面向对象设计是基于以下 3 个主要原理的: 封装, 继承和多态. 说某种程序语言是支持 OO (面向对象) 设计的, 只有在它的语法中支持以上 3 个概念才可以这么说. 这种语言应该为你提供某些方法, 以使你能很轻松地定义和使用这些范例. 封装涉及到了将某个对象变成一个 "黑盒子"的概念. 当你使用某个对象时, 你不用知道它内部是如何工作的, 你也不必理解对象是如何工作的. 这个对象只需将它绝对有用的信息以接口方式提供出来. 此对象应该给你提供友好的接口, 来让你可以使用其有限的属性集和方法集. 封装还有一层意思, 那就是说某个对象包含了它需要的每一样东西, 这包括数据和对于它的操作. 封装的概念非常的强大, 因为它允许将一个大的软件项目有效地分配给每个开发人员, 对于团队中的每个人, 他们只需要关注自己所实现的对象, 而不需要太多地关注于别人的实现. 开发项目中的开销使得开发团队中成员与接口的数量按指数级别增长. 封装是自 "软件危机" 以来最受欢迎的 OO 设计理念.

  软件的复用是 OO 设计思想中另外一个重要的特点. 在软件体系中实现此思想的主要方法就是继承. 类就是定义对象的功能. 超类是某个新类, 或者说是子类被建立的来源类. 一个子类从它的超类中继承了所的方法和属性. 实际上, 所有的子类都是被自动地生成的, 因此节省了大量的工作. 你不需要一个一个地定义这些子类. 当然, 你可以重载那些继承下来的方法和属性. 事实上, 谁也没有指出哪个子类要建立得和其超类一模一样, 除非你没有重载任何的属性和方法.

  多态可能是这个 3 个概念中最复杂的一个了. 其本质上是说, 每个对象都可以处理各种不同的数据类型. 你不必为处理不同的数据类型而建立不同的类. 其典型的例子就是画图的类, 你不必为实现画圆, 画矩形, 画椭圆而编写不同的类. 你可以建立一个足够聪明的类来调用特定的方法来操作特定的形状.

  通过函数实现继承

  虽然 JavaScript 不支持显示继承操作符, 但你可以通过其实方式实现隐式继承. 对于实现类的继承, 有 2 种比较常用的方式. 第一种将某个类定义成子类的方法是, 通过在负责定义子类函数的内部调用超类的构造函数. 看下面的示例:

// 超类构造函数
function superClass() {
  this.bye = superBye;
  this.hello = superHello;
}

// 子类构造函数
function subClass() {
  this.inheritFrom = superClass;
  this.inheritFrom();
  this.bye = subBye;
}

function superHello() {
  return "Hello from superClass";
}
 
function superBye() {
  return "Bye from superClass";
}

function subBye() {
  return "Bye from subClass";
}

// 测试构造特性的函数
function printSub() {
  var newClass = new subClass();
  alert(newClass.bye());
  alert(newClass.hello());
}

  当你运行上面的 printSub 函数时, 它会依次执行 subBuy 和 superHello 函数. 我们可以看到, bye 和 hello 方法最先在 superClass 中被定义了. 然而, 在 subClass 中, bye 方法又被重载了, subClass 构造函数头两行的功能只是做了一个简单的原始的继承操作, 但它是通过显示执行 inheritFrom 方法来完成的继承操作. 继承的过程先是将 superClass 的对象原型赋给 subClass 下的 inheritFrom 方法, 然后在执行完 superClass 的构造函数后, superClass 的属性就被自动地加到了 subClass 的属性列表中.这主要是由于在 subClass 中通过 this 来调用的 inheritFrom (也就是 superClass) 构造函数造成的, 通过此种方式调用 superClass 构造函数时, JavaScript 解释器会把 superClass 中的 this 与 subClass 中的 this 理解成位于同一个作用域下的 this 关键字, 所以就产生了继承的效果.

  另外, 需要说明的是, 对于任何一个实例化的对象, 你任意地为它添加属性或方法, 如下所示:

  var newClass = new subClass();
  newClass.addprop = "added property to instance object";

  很明显, 通过此种方式添加的属性和方法只对当前实例化对象有效, 不会影响所有的同类型对象实例. 无疑, 它是你创造的一个独一无二的对象实例.

  通过原型实现继承

  第二种, 也是更强大的方法是通过建立一个超类对象, 然后将其赋值给子类对象的 prototype 属性, 以此方式来建立子类的继承. 假设我们的超类是 superClass, 子类是 subClass. 其 prototype 的赋值格式如下:

subClass.prototype = new superClass;

对于原型继承的实现方式, 让我们刚前面的代码改写一下, 示例如下:

// 超类构造函数
function superClass() {
  this.bye = superBye;
  this.hello = superHello;
}

// 子类构造函数
function subClass() {
  this.bye = subBye;
}
subClass.prototype = new superClass;

function superHello() {
  return "Hello from superClass";
}
 
function superBye() {
  return "Bye from superClass";
}

function subBye() {
  return "Bye from subClass";
}

// 测试构造特性的函数
function printSub() {
  var newClass = new subClass();
  alert(newClass.bye());
  alert(newClass.hello());
}

  我们可以看到, 除了将前面第一种继承方式中 subClass 中的前 2 行内容, 换成函数外的 prototype 赋值语句之外, 没有其它任何的变化, 但代码的执行效果和前面是一样的.

  为已经建立的对象添加属性

  通过原型实现的继承比通过函数实现的继承更好, 因为它支持动态继承. 你可以在构造函数已经完成之后, 再通过 prototype 属性定义超类的其它方法和属性, 并且其下的子类对象会自动地获得新的方法和属性. 下面是示例, 你可以看到它的效果.

function superClass() {
  this.bye = superBye;
  this.hello = superHello;
}

function subClass() {
  this.bye = subBye;
}
subClass.prototype = new superClass;

function superHello() {
  return "Hello from superClass";
}
 
function superBye() {
  return "Bye from superClass";
}

function subBye() {
  return "Bye from subClass";
}

var newClass = new subClass();

/*****************************/
// 动态添加的 blessyou 属性
superClass.prototype.blessyou = superBlessyou;

function superBlessyou() {
  return "Bless You from SuperClass";
}
/*****************************/

function printSub() {
  alert(newClass.bye());
  alert(newClass.hello());
  alert(newClass.blessyou());
}

  这就是我们经常看到的为内部对象, 如 String, Math 等再添加其它属性和方法的技巧. 对于任何的内部对象和自定义对象, 你都也可以通过 prototype 来重载其下的属性和方法. 那么在调用执行时, 它将调用你所定义的方法和属性. 下面是示例:

// 为内部 String 对象添加方法
String.prototype.myMethod = function(){
  return "my define method";
}

// 为内部 String 对象重载方法
String.prototype.toString = function(){
  return "my define toString method";
}

var myObj = new String("foo");

alert(myObj.myMethod());
alert(myObj);
alert("foo".toString());

  另外需要注意的是, 所有 JavaScript 内部对的 prototype 属性都是只读的. 你可以像上面那样为内部对象的原型添加或重载属性和方法,但不能更改该内部对象的 prototype 原型. 然而, 自定义对象可以被赋给新的原型. 也就是说, 像下面这样做是没有意思的.

function Employee() {
  this.dept = "HR";
  this.manager = "John Johnson";
}

String.prototype = new Employee;

var myString = new String("foo");

上面的程序在运行之后不会报错, 但显然, 如果你调用 myString.dept 将会得到一个非定义的值.

另外, 一个经常使用的是 prototype 下的 isPrototypeOf() 方法, 它主要用来判断指定对象是否存在于另一个对象的原型链中. 语法如下:

object1.prototype.isPrototypeOf(0bject2);

上面的格式是用来判断 Object2 是否出现 Object1 的原型链中. 示例如下:

function Person() {
  this.name = "Rob Roberson";
  this.age = 31;
}

function Employee() {
  this.dept = "HR";
  this.manager = "John Johnson";
}

Employee.prototype = new Person();

var Ken = new Employee();

当执行 Employee.prototype.isPrototypeOf(Ken), Person.prototype.isPrototypeOf(Ken) 和 Object.prototype.isPrototypeOf(Ken) 时, 结果都会返回 true.

  用于 Netscape 下的特定继承检测

  在 Netscape 浏览器 4.x 到 6, 及其 Mozilla 系列浏览中, JavaScript 将对象间的原型关系存储在一个特殊的内部属性对象中, __proto__ (前后是 2 个下划线). 下面是一个示例:

function Shape() {
  this.borderWidth = 5;
}

function Square() {
  this.edge = 12;
}

Square.prototype = new Shape;

myPicture = new Square;

alert(myPicture.__proto__);
alert(myPicture.borderWidth);

  由于脚本执行过 Square.prototype = new Shape 语句, 所以 myPicture 具有了一个指向 Shape 对象的内部属性 __proto__. 在脚本的执行过程中, 当要获取对象的某个属性值, 并且此对象是通过原型赋值而建立的某个对象, 在自身并没有对某个属性进行定义时, JavaScript 解析器会查看它的 __proto__ 属性对象, 也就它的原型对象, 然后枚举其原型中的所有属性, 而得出的结果要么是有这个属性, 要么是没有这个属性. 如果没有此属性, 再枚举原型对象下面的原型对象, 直到此过程真正的结束. 而所有的这些 JavaScript 引擎内部的操作, 我们是不会知道的, 下面的内容就是对这个问题的解释.

  其实, 对于所有的自定义对象, 无论它有没有使用过 prototype 赋值操作, 它都具有一个 __proto__ 内部对象. 而如果某个对象是通过多层 prototype "继承" 来的, 所有的 "继承" 而来的属性却可以通过简单的一层循环遍历出来, 而不需要使用什么递归算法, 因为 JavaScript 引擎自动给我们做了. 示例如下:

function Shape() {
  this.borderWidth = 5;
}

function Square() {
  this.edge = 12;
}

function RoundSquare()
{
  this.radio = 0.5;
}

Square.prototype = new Shape;
RoundSquare.prototype = new Square;

var myPicture = new RoundSquare;

for (property in myPicture.__proto__) {
alert(property);
}

  我们或者还可以通过更改后面的循环, 来遍历某个子类对象继承来的所有属性, 如下:

for (property in RoundSquare.prototype) {
alert(property);
}

  如果你不怕麻烦, 我们甚至还可以通过级连的方式, 取出其构造函数中定义的原始属性值.

alert(myPicture.__proto__.__proto__.borderWidth);

  无论你是否修改过此属性值, 通过上面语句所取出的属性值都是原始定义值. 让我们沿着这个思路再往下看, 下面的代码涉及到另外一个问题, 这个问题和原型链 (prototype chain) 有关. 代码如下:

function State() {
}

function City() {
}
City.prototype = new State;

function Street() {
}

Street.prototype = new City;

var UniversityAvenue = new Street();

function tryIt() {
  alert(UniversityAvenue.__proto__== Street.prototype);
  alert(UniversityAvenue.__proto__.__proto__==
   City.prototype);
  alert(UniversityAvenue.__proto__.__proto__.__proto__
   == State.prototype);
  alert(UniversityAvenue.__proto__.__proto__.__proto__.
   __proto__== Object.prototype);
  alert(UniversityAvenue.__proto__.__proto__.__proto__.
   __proto__.__proto__== null);
}

  当执行 tryIt 函数时, 所有的显示均为 true. 也就是说, 子类对象的 prototype.__proto__ 总是等于超类对象的 prototype 属性; 超类对象的 prototype.__proto__ 总是等于 Object.prototype; Object.prototype.__proto__ 总是为 null; 而实例对象的 __proto__ 总是等于其类对象的 prototype, 这就是为什么任何自定义对象都具有 __proto__ 属性的原因. 对于上面的叙述, 其对应的代码如下:

Street.prototype.__proto__ == City.prototype     // true
State.prototype.__proto__ == Object.prototype    // true
Object.prototype.__proto__ == null               // true
UniversityAvenue.__proto__ == Street.prototype    // true

  模拟实现 instanceOf 函数

  根据上一节的内容, 我们了解了有关 Netscape 所支持的 __prot

[1] [2]  下一页


[Web开发]用PHP实现Javascript的escape(),unescape()的方法  [网页制作]Javascript获取各种浏览器可见窗口大小
[网页制作]面向对象编程,我的思想  [网页制作]Javascript 表单之间的数据传递
[网页制作]用 Javascript 写的一个映射表类  [网页制作]Javascript 编写的文字放大效果
[网页制作]在CSS样式表里使用JavaScript  [网页制作]Javascript +CSS横向三级导航菜单
[网页制作]CSS+JavaScript打造超酷右键菜单  [网页制作]CSS+JavaScript打造超酷右键菜单(2)
教程录入:mintao    责任编辑:mintao 
  • 上一篇教程:

  • 下一篇教程:
  • 【字体: 】【发表评论】【加入收藏】【告诉好友】【打印此文】【关闭窗口
      注:本站部分文章源于互联网,版权归原作者所有!如有侵权,请原作者与本站联系,本站将立即删除! 本站文章除特别注明外均可转载,但需注明出处! [MinTao学以致用网]
      网友评论:(只显示最新10条。评论内容只代表网友观点,与本站立场无关!)

    同类栏目
    · Web开发  · 网页制作
    · 平面设计  · 网站运营
    · 网站推广  · 搜索优化
    · 建站心得  · 站长故事
    · 互联动态
    更多内容
    热门推荐 更多内容
  • 没有教程
  • 赞助链接
    更多内容
    闵涛博文 更多关于武汉SEO的内容
    500 - 内部服务器错误。

    500 - 内部服务器错误。

    您查找的资源存在问题,因而无法显示。

    | 设为首页 |加入收藏 | 联系站长 | 友情链接 | 版权申明 | 广告服务
    MinTao学以致用网

    Copyright @ 2007-2012 敏韬网(敏而好学,文韬武略--MinTao.Net)(学习笔记) Inc All Rights Reserved.
    闵涛 投放广告、内容合作请Q我! E_mail:admin@mintao.net(欢迎提供学习资源)

    站长:MinTao ICP备案号:鄂ICP备11006601号-18

    闵涛站盟:医药大全-武穴网A打造BCD……
    咸宁网络警察报警平台