AI智能
改变未来

WEB入门之十一 JS面向对象

学习内容

  •  JavaScript类的定义模式
  •  JavaScript继承的实现
  •  JavaScript抽象类
  •  JavaScript解析XML

 

能力目标

  • 深入了解JavaScript类的定义模式
  •  理解JavaScript继承
  •  理解JavaScript抽象类和虚函数
  • 熟练使用JavaScript进行XML解析

本章简介

上一章学习了JavaScript高级编程,包括匿名函数、内部函数、回调函数以及使用JavaScript解析JSON格式的数据,另外还学习了基础的JavaScript面向对象编程,包括类的定义和对象的创建。本章继续深入学习JavaScript面向对象编程,会涉及到类的多种定义模式、继承、抽象类等高级内容。

 

核心技能部分

2.1 类的定义模式

JavaScript并不像C#、Java一样支持真正的类。但是在上一章,可以通过函数来模拟类的定义,实际上JavaScript支持多种类的定义模式,例如构造函数模式、原型模式等。

2.1.1 构造函数模式

构造函数模式是最常用的一种类的定义模式,上一章的示例1.6就是通过构造函数模式来定义类的。下面再使用构造函数模式定义一个Person类。参考代码如下所示。

示例2.1

<script>function School(n,a){this.name=n;this.address=a;this.teach=function(){alert(n+\"正在上课...\");}}var zy=new School(\"郑州中学\",\"瑞达路\");zy.teach();</script>

上述代码使用一个函数模拟定义了一个School类

从运行结果来看,School函数执行了,同时zy获得了一个对象的引用。事实上,当new一个函数时,这个函数就表示构造函数,该函数里面的代码可以看做是为了初始化一个对象而工作。

构造函数通常没有返回值,并且在函数内部使用this关键字表示新创建的对象。构造函数首字母通常大写,这也是为了符合OOP的编码惯例,也为了跟普通函数进行区别。

在JavaScript中通过构造函数创建对象的过程总结如下:

1.浏览器解释器遇到new关键字时创建一个空对象

2.执行构造函数,并将this指针指向新建的对象

3.初始化属性和方法

4.函数执行完毕就返回初始化后的对象

2.1.2 原型模式

prototype即原型,它本身就是一个对象。每个函数(类)都有一个prototype属性(子对象),它表示类的成员集合。当使用new创建对象时,prototype对象的成员都会成为新建对象的成员。

我们首先通过一个例子来演示什么是prototype以及它的用法,参考代码如下所示。

示例2.2

<script>function School(){}School.prototype.name=\"郑州中学\";School.prototype.address=\"瑞达路\";School.prototype.teach=function(){alert(this.name+\"正在上课...\");}var zy=new School();zy.teach();</script>

上述代码首先定义了一个空类,然后使用prototype定义了该类的属性和方法,运行效果跟示例2.1一样。上述代码有一个问题就是每定义一个属性或方法就需要写一次School.prototype,我们可以做如下改进。

示例2.3

<script>function School(){}School.prototype={name:\"郑州中学\",address:\"瑞达路\",teach:function(){alert(this.name+\"正在上课...\");}}var zy=new School();zy.teach();</script>

上述代码通过大括号把成员集中添加到了prototype对象上,写法更加简洁清晰。prototype对象专用于设计类的成员,是JavaScript实现面向对象编程的重要手段。在JavaScript中通过原型创建对象的过程总结如下:

1.浏览器解释器遇到new关键字时创建一个空对象

2.将this指针指向新建的对象

3.把prototype对象的所有成员赋给新建的对象

4.返回初始化后的对象

2.1.3 两种模式的不同之处

构造函数模式和原型模式都能定义类,但是这两种模式之间有着本质的区别。下面我们通过例子进行对比。

示例2.4

<script>function School(n,a){this.name=n;this.address=a;this.teach=function(){document.write(n+\"正在上课...<br/>\");}}var s1=new School(\"郑州中学\",\"瑞达路\");s1.teach();var s2=new School(\"郑州大学\",\"科学大道\");s2.teach();</script>

示例2.4使用的是前面的例子,通过构造函数定义了一个School类,并创建了该类的两个对象s1和s2。

 

在JavaScript中,函数就是类,所以School类中的teach函数其实就是一个对象。这会产生这样一个问题:每次创建一个School类的对象就会创建一个teach对象,而每个teach所实现的功能是一模一样的,没必要多次创建,这是通过构造函数定义类的一个缺点。

下面我们使用原型也定义一个和上面一样的School类,同样也创建该类的两个对象s1和s2,参考代码如下所示。

示例2.5

<script>function School(){}School.prototype.name=\"郑州中学\";School.prototype.address=\"瑞达路\";School.prototype.teach=function(){document.write(this.name+\"正在上课...<br/>\");}var s1=new School(\"郑州中学\",\"瑞达路\");s1.teach();var s2=new School(\"郑州大学\",\"科学大道\");s2.teach();</script>

直接看运行效果,如图2.1.3所示。上述代码虽然使用new创建了两个对象,但是通过运行结果会发现,这两个对象中的成员的值一样,第2个对象采用的仍然是第1个对象的值。原型中的所有成员是被类的所有对象共享的,类似于Java类中的静态成员,即无法像构造函数那样创建不同的对象,这是通过原型定义类的一个缺点。

 

2.1.4 两种模式组合使用

由于构造函数模式和原型模式各有优缺点,所以在实际开发中通常组合使用两种模式来定义类。构造函数负责定义类中的属性,原型负责定义类中的方法,这样创建的每个对象就会有不同的属性值,同时又不会每次都创建类中的函数对象,节省了内存。

下面我们组合使用两种模式定义School类,参考代码如下所示。

示例2.6

<script>function School(n,a){this.name=n;this.address=a;}School.prototype.teach=function(){document.write(this.name+\"正在上课...<br/>\");}var s1=new School(\"郑州中学\",\"瑞达路\");s1.teach();var s2=new School(\"郑州大学\",\"科学大道\");s2.teach();</script>

上述代码通过带参的构造函数给属性传值,通过prototype定义方法。这种组合模式是ECMAScript中使用最广泛、认同度最高的一种定义类的方式。

2.2 继承

JavaScript没有专门的机制来实现继承。ECMAScript中提出了原型链的概念,并将原型链作为实现继承的主要方式,其基本思想是利用原型让一个自定义类继承另一个类的属性和方法。

示例2.7

<script>//定义父类Personfunction Person(n,a,p){this.name=n;this.age=a;this.phone=p;}Person.prototype.sayHello=function(){alert(this.name+\"向你问好!\");}//定义子类Studentfunction Student(s){this.score=s;}//子类Student继承父类PersonStudent.prototype=new Person();Student.prototype.sayHello=function(){alert(\"学生\"+this.name+\"向你问好!\");}Student.prototype.getDetials=function(){alert(this.name+\"的成绩是\"+this.score);}//创建子类对象var s=new Student();s.name=\"张无忌\";s.age=\"28\";s.phone=\"18638643721\";s.score=100;s.sayHello();s.getDetials();</script>

上述代码定义了两个对象,分别是父类Person和子类Student。子类Student通过Student.prototype=new Person(); 继承了父类Person,这就是原型继承。另外,子类Student重写了父类Person中的sayHello方法,还定义了自己独有的属性score和方法getDetials。

需要注意的是:子类重写父类方法的代码以及定义自身独有方法的代码必须出现在原型继承Student.prototype=new Person(); 之后。

JavaScript也可以像Java一样,在定义子类时调用父类的构造方法为属性赋值。下面我们把示例2.7中的子类Student修改为带参数的构造方法,参考代码如下所示。

示例2.8

<script>//定义父类Personfunction Person(n,a,p){this.name=n;this.age=a;this.phone=p;}Person.prototype.sayHello=function(){alert(this.name+\"向你问好!\");}//定义子类Studentfunction Student(n,a,p,s){Person.call(this,n,a,p);this.score=s;}//子类Student继承父类PersonStudent.prototype=new Person();Student.prototype.sayHello=function(){alert(\"学生\"+this.name+\"向你问好!\");}Student.prototype.getDetials=function(){alert(this.name+\"的成绩是\"+this.score);}//创建子类对象var s=new Student(\"张无忌\",28,\"18638643721\",100);s.sayHello();s.getDetials();</script>

上述代码使用带参数的构造函数定义了一个子类Student,在子类的构造函数中使用Person.call(this,n,a,p);调用了父类的构造函数。call方法用来绑定到某个函数上进行自身调用,第一个参数this表示函数本身,后面的都是函数的参数。

2.3 解析XML

XML是指可扩展标记语言(Extensible  Markup  Language),类似于HTML。它是W3C的推荐标准,其设计宗旨是存储和传输数据而非显示数据。XML标签没有被预定义,需要开发人员自定义标签。

XML的主要作用可以概括为以下6个:

(1)使数据从HTML分离。

如果需要在HTML文档中显示动态数据,则每次数据改变时将花费大量的时间来编辑HTML。通过XML,数据能够存储在独立的XML文件中,从而专注地使用HTML进行布局和显示,且修改底层数据时无须再次对HTML进行任何改变。通过几行JavaScript即可读取一个外部XML文件,然后更新HTML中的数据内容。

(2)简化数据共享。

XML数据以纯文本格式进行存储,提供了一种独立软件和硬件的数据存储方法,使创建不同应用程序共享的数据变得更加容易。

(3)简化数据传输。

通过XML可以在不兼容的系统之间轻松交换数据。对于开发人员而言,在因特网上不兼容的系统之间交换数据特别耗费时间。可以通过各种不兼容的应用程序来读取数据,使用XML交换数据可以降低这种复杂性。

(4)简化平台的变更。

升级到新系统(硬件或软件平台)相对费时的情况下,不兼容的数据在转换大量的数据时经常丢失。XML数据以文本格式存储,这使XML可以在不丢失数据的前提下更易于扩展或升级到新的操作系统、应用程序或浏览器。

(5)延伸了数据使用。

XML独立于硬性、软件以及应用程序之外,使数据更可用、有用。不同的应用程序都能够在HTML页面以及XML数据源中访问。通过XML的数据不仅可以供各种阅读设备(手持计算机、语音设备、新闻阅读器等)使用,还可以供盲人等残障人士使用。

2.3.1 节点和节点树

根据DOM的规定,XML文档中每个单元(元素、属性、文本、注释等)都是节点。例如:

(1)整个文档是一个文档节点。

(2)每个XML标签是一个元素节点。

(3)包含在XML元素中的文本是文本节点。

(4)每个XML属性是一个属性节点。

(5)注释属于注释节点。

XML DOM将XML文档视为树结构,这种树结构被称为节点树。程序通过节点树访问所有节点、修改或删除其内容以及创建新元素。节点树展示了节点的集合以及它们之间的关系。节点树从根节点开始,在树的最低层级向文本节点长出“枝条”。

下面我们编写一个描述和存储图书信息的book.xml,并给出节点数的图形,参考代码如下所示。

示例2.9

<?xml version=\"1.0\" encoding=\"UTF-8\"?><bookstore><book category=\"children\"><title lang=\"en\">Harry Potter</title><author>J K. Rowling</author><year>2005</year><price>29.99</price><publish pubdate=\"2011-01-09\"/></book><book category=\"cooking\"><title lang=\"en\">Everyday Italian</title><author>Giada De Laurentiis</author><year>2005</year><price>30.00</price></book><book category=\"web\"><title lang=\"en\">Learning XML</title><author>Erik T. Ray</author><year>2003</year><price>39.95</price></book><book category=\"web\"><title lang=\"en\">XQuery Kick Start</title><author>James McGovern</author><author>Per Bothner</author><author>Kurt Cagle</author><author>James Linn</author><author>Vaidyanathan Nagarajan</author><year>2003</year><price>49.99</price></book></bookstore>

将示例2.9中的books.xml使用节点树表示

下面我们分析一下book.xml文件:

(1)根节点是<bookstore>,文档中所有的其他节点都包含在<bookstore>中。

(2)根节点<bookstore>包含4个<book>节点。

(3)第一个<book>节点包含4个节点:<title>、<author>、<year>和<price>。其中,每个节点包含一个文本节点(分别为Harry Potter、J K.Rowling、2005和29.99)。

(4)元素节点的文本存储在文本节点中。<year>2005</year>中,元素节点<year>拥有一个值为“2005”的文本节点,“2005”不是<year>元素的值。

(5) 属性节点与子元素属于同一级别的节点。例如,<book>元素的属性“category”与<book>的子元素<title>同级。

2.3.2 DOM编程接口

由于客户端无法识别并读取Java对象数据,所以,在Web开发中,服务器端经常返回的是XML数据。因此,客户端需要使用JavaScript解析XML数据。

1. XML DOM对象

XML DOM对象中封装了常用的操作XML文档的属性和方法。常用的XML DOM对象如下:

(1)XML DOM Attr对象:表示Element对象的属性。

(2)XML DOM Comment对象:表示文档中注释节点的内容。

(3)XML DOM Document对象:表示整个XML文档。

(4)XML DOM Element对象:表示XML文档中的元素。

(5)XML DOM Node对象:表示文档树中的一个节点。

(6)XML DOM Text对象:表示元素或属性的文本内容。

(7) XML DOM XMLHttpRequest对象:提供对HTTP协议的访问,包括发出POST、HEAD以及普通GET请求的能力。XMLHttpRequest可以同步或异步地返回Web服务器的响应,并且通过文本或者一个DOM文档的形式返回内容。此对象并不限于和XML文档一起使用,可以接收任何形式的文本文档。

2. 加载XML文档

浏览器都内建了用于读取和操作XML文件的XML解析器。解析器将XML读入内存,并转换为可以被JavaScript访问的XML DOM对象。

XML数据可以通过XML文档保存在磁盘介质上,或者通过XML字符串在内存中创建。XMLDocument对象将XML文档和XML字符串加载到内存,然后通过JavaScript实现DOM解析。如果XML文档需要在服务端解析,还可以使用C#、Java等编程语言通过DOM API进行解析。

使用JavaScript实现DOM时,不同浏览器的加载方式有所不同。

1.使用load ( )方法加载XML文档

示例2.10

<script>//IE浏览器xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");xmlDoc.async = false;xmlDoc.load(\"book.xml\");</script>示例2.11<script>//Firefox、Mozilla等浏览器xmlDoc = document.implementation.createDocument(\"\", \"\", null);xmlDoc.async = false;xmlDoc.load(\"book.xml\");</script>

在示例2.10和示例2.11中,第一行代码用于在浏览器中创建空的XML Document对象;第二行关闭异步加载,可以确保在文档完整加载之前,解析器不会继续执行脚本;第三行通知解析器加载名为“books.xml”的文档。在实际开发中可以通过异常处理来编写通用的浏览器加载方法。示例2.12通过异常处理模块编写跨浏览器的XML文档加载代码。

示例2.12

<script>try {xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");} catch (e) {try {xmlDoc = document.implementation.createDocument(\"\", \"\", null);} catch (e) {alert(e.message);}}try {xmlDoc.async = false;xmlDoc.load(\"book.xml\");document.write(\"XML文档已经加载完毕,可以进行解析了。<br>\");} catch (e) {alert(e.message);}</script>

2.使用loadXml()方法加载XML文本

如果服务器端返回的不是xml文件,而是xml格式的文本信息,那么在IE浏览器中使用XML Document对象的loadXml()方法加载XML文本;在Firefox等浏览器中使用DOM解析器对象DOMParser的parseFromString()方法加载XML文本。

下面我们通过一个例子来演示加载XML文本,参考代码如下所示。

示例2.13

<script type=\"text/javascript\">text = \"<bookstore>\";text = text + \"<book>\";text = text + \"<title>Harry Potter</title>\";text = text + \"<author>J K. Rowling</author>\";text = text + \"<year>2005</year>\";text = text + \"</book>\";text = text + \"</bookstore>\";try{xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");xmlDoc.async = \"false\";} catch (e) {try{parser = new DOMParser();xmlDoc = parser.parseFromString(text, \"text/xml\");} catch (e) {alert(e.message);}}try {xmlDoc.loadXML(text);document.write(\"XML字符串已经加载完毕,可以进行解析了。\");} catch (e) {alert(e.message);}</script>

3. 节点操作

节点操作通过XML DOM对象的属性和方法实现。对象多达几十个,并且每个对象都有各自的属性和方法,常用对象的属性和方法见

 XML DOM对象常用属性

属性   

说明

nodeName

获取节点名称

nodeValue

获取节点的值

parentNode

获取节点的父节点

childNodes

获取节点的所有子节点集合

attributes

获取当前节点所有的属性节点

documentElement

获取文档的根节点

表2-1-2  XML DOM对象常用方法

方法

说明

getElementsByTagName(name)

获取带有指定标签名(name)的所有元素

CreateElement(name)

创建指定标签名的元素节点

appendChild(node)

向调用节点末尾插入子节点node

removeChild(node)

从调用节点中删除子节点node

下面是一个通过XML DOM对象的属性和方法来解析book.xml的例子,参考代码如下所示。

示例2.14

<script>try {xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");} catch (e) {try {xmlDoc = document.implementation.createDocument(\"\", \"\", null);} catch (e) {alert(e.message);}}try {xmlDoc.async = false;xmlDoc.load(\"book.xml\");} catch (e) {alert(e.message);}//使用属性和方法从book.xml中的<title>元素中获取文本的JavaScript代码var txt=xmlDoc.getElementsByTagName(\"title\")[0].childNodes[0].nodeValue;alert(txt);</script>

对示例2.14的说明如下:

(1)xmlDoc:由解析器创建的XML Document对象,创建方式见示例2.13。

(2)getElementsByTagName(\”title\”)[0]:获取第一个<title>元素。

(3)childNodes[0]:获取<title>元素的第一个子节点(文本节点)。

(4)nodeValue:获取节点的值(文本自身)。

访问节点包括遍历节点、定位节点、获取节点的详细信息等操作,这些操作都通过XML DOM的属性和方法实现。通常,访问节点需要使用对象XML Node List和XML Node。前者表示一个节点列表(集合),后者表示一个节点。每个节点都具有nodeName、nodeValue和nodeType属性,分别用于获得节点名称、节点值和节点的类型。元素节点还可以通过attributes属性返回属性节点的列表。

Ø nodeName。其特点是:nodeName是只读的;元素节点的nodeName与标签名相同;属性节点的nodeName是属性的名称。

Ø nodeType。nodeType属性规定节点的类型,是只读的。nodeType常用的值包括:“1”

表示节点是元素节点:“2”表示节点是属性节点;“3”表示节点是文本节点;“8”

表示节点是注释节点。

Ø nodeValue。其特点是:元素节点的nodeValue是不可用的;文本节点的nodeValue是文本自身;属性节点的nodeValue是属性的值。

在DOM层次中还可以通过以下3种方法查找和定位节点:

(1)使用getElementsByTagName()方法。

(2)循环(遍历)节点树。

(3)通过节点的层次关系在节点树中导航,见表2-1-3。

表2-1-3  节点层次关系

属性

功能

parentNode

获取父节点

childNodes

获取子节点集合

firstChild

获取第一个子节点

lastChild

获取最后一个子节点

nextSibling

获取同级别中后一个节点

previousSibling

获取同级别中前一个节点

下面我们通过一个例子来演示如何使用节点层次关系来解析book.xml文件,参考代码如下所示。

示例2.15

<script>try {xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");} catch (e) {try {xmlDoc = document.implementation.createDocument(\"\", \"\", null);} catch (e) {alert(e.message);}}try {xmlDoc.async = false;xmlDoc.load(\"book.xml\");} catch (e) {alert(e.message);}var nlist = xmlDoc.getElementsByTagName(\"book\")[0].childNodes;document.write(\"<br>book节点包含子元素的个数: \" + nlist.length);document.write(\"<br>\");for ( var i = 0; i < nlist.length; i++) {var node = nlist[i];alert(node.nodeType);document.write(\"子元素的名字:\" + node.nodeName + \",子元素的值:\"+ node.childNodes[0].nodeValue);var att = node.attributes;//获取该元素的全部属性节点for ( var j = 0; j < att.length; j++) {document.writeln(\"<br/>子元素\" + node.nodeName + \"有属性\"+ att[i].nodeName + \"=\" + att[i].nodeValue);}document.write(\"<br/>\");}</script>

上述代码首先使用getElementsByTagName(\”book\”)[0].childNodes获得了book下的所有子节点,然后使用for循环遍历这个子节点集合,在循环过程中输出每个节点的名字和值,有属性的同时使用for循环输出属性节点的名字和值。

 

本章总结

本章在上一章的基础上进一步讲解了JavaScript面向对象编程,包括类的定义模式和继承等,这一部分内容着重强调理解,大家可以结合Java面向对象编程思想帮助自己理解。JavaScript解析XML在实际开发中应用频繁,大家必须要掌握熟练。

任务实训部分

1:自定义类

训练技能点

Ø 构造函数模式

Ø 原型模式

需求说明

使用构造函数模式和原型模式组合定义一个员工类,包括员工姓名、职位和工资,以及

开会的方法。创建两个员工对象进行测试。

2:JavaScript继承

训练技能点

 通过prototype实现继承

需求说明

首先定义一个宠物类(Pet)作为父类,属性和方法自定义;然后定义一个Dog类和Cat类都继承Pet类;最后创建一个Dog对象和Cat对象进行测试。

 

3:解析XML文件

训练技能点

Ø JavaScript解析XML文件

需求说明

创建一个XML文件存储3个学生信息(姓名、性别、成绩);使用JavaScript解析该文件并逐行输出这3个学生的所有信息。

实现步骤

(1)定义一个XML文件存储3个学生信息

(2)使用JavaScript解析XML文件并输出,参考代码如下所示。

<script>try {xmlDoc = new ActiveXObject(\"Microsoft.XMLDOM\");} catch (e) {try {xmlDoc = document.implementation.createDocument(\"\", \"\", null);} catch (e) {alert(e.message);}}try {xmlDoc.async = false;xmlDoc.load(\"students.xml\");} catch (e) {alert(e.message);}var stus=xmlDoc.getElementsByTagName(\"student\");for(var j=0;j<stus.length;j++){var stu=stus[j].childNodes;for(var k=0;k<stu.length;k++){document.write(stu[k].firstChild.nodeValue+\" \");}document.write(\"<br/>\");}</script>

4:解析XML文本

训练技能点

Ø JavaScript解析XML文本

需求说明

通过字符串存储XML格式的数据:3个员工信息(姓名、职务、工资),然后使用JavaScript解析该XML文本并逐行输出这3个员工的所有信息。

 

巩固练习

一、选择题

1. 以下关于JavaScript构造函数说法正确的是()。

A. JavaScript类不存在构造函数

B. JavaScript类的构造函数必须带有参数

C. 构造函数首字符必须大写

D. 构造函数中的this表示新建的对象

2. 以下关于JavaScript原型的说法错误的是()。

A. 原型也是一个对象

B. 每个自定义类都默认拥有原型属性

C. 原型拥有类的成员集合

D. 原型里的this表示原型自身

3. 以下关于JavaScript继承说法错误的是()。

A. JavaScript没有继承机制

B. JavaScript可以借助原型实现继承

C. JavaScript子类无法重写父类方法

D. JavaScript子类中可以调用父类的构造函数

4. 下列关于XML说法错误的是()。

A. XML和HTML的作用一样,只不过标记需要自定义而已

B. XML主要用来存储、传输数据,能够跨平台

C. 不同浏览器加载XML文件的方式不同

 

 

二、上机练习

 定义一个XML文本存储3本图书信息,包括图书名、作者和价格,然后使用JavaScript进行解析并输出所有的图书信息。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » WEB入门之十一 JS面向对象