文章介绍:javascript中的面向对象编程,遍历DOM。
一,面向对象编程
1,面向对象编程简介,
面向对象编程相对面向过程编程而言,面向过程编程方式的特点是把数据保存到变量里,然后由一系列指令操作变量。每个指令(或一系列指令,比如函数)都能创建,删除或修改数据,显得数据与代码在某种程度上是“分离”的。
在面向对象编程(oop)方式中,程序指令与其操作的数据密切关联。换句话说oop把程序的数据包含在"对象"的独立体里,每个对象都有自己的属性(数据)和方法(指令)。
与面向过程编程方式相比,面向对象编程有不少优点,比如:代码复用,封装,继承。
2,创建对象
(1)创建直接实例,
javascript中有一个内置对象object,利用它可以创建一个空白的对象:
myNewObject = new Object();
这样就得到了一个崭新的对象myNewObject,此时它还没有任何属性和方法,因此没有任何实际功能。可以像下面这样添加属性和方法:
//添加属性
myNewObject.info = 'I am a shiny new object';
//添加方法
function myFunc(){
alert(this.info);
};
myNewObject.showInfo = myFunc;
#在把函数myFunc关联到.showInfo属性时只使用了函数名称,而没有包含括号。这是因为我们是要把函数myFunc()的定义赋予mynewObject.showInfo方法。如果加了括号,相当于执行函数然后将函数的返回值赋予变量。
(2)使用关键字this
当我们在函数中使用this函数的时候,this指向函数的"父对象"。
在函数最初声明时,它的父对象是全局对象window,window对象并没有名为info的属性,如果直接调用myFunc()函数,会发送错误。
(3)匿名函数
前面的代码是这样的:
function myFunc(){
alert(this.info);
};
myNewObject.showInfo = myFunc;
同样的功能可以这样实现:
myNewObject.showInfo = function(){
alert(this.info);
};
(4)使用构造函数
如果只需要某个对象的一个实例,使用直接创建对象实例的方法还算不错。但如果要创建同意个对象的多个实例,使用这种方式就要反复重复整个过程:创建对象,添加属性,定义方法等。
如果要创建可能具有多个实例的对象,更好的方式是使用"对象构造函数"。它会创建某种模板,方便实现多次实例化。
查看下面打代码,其中并没有使用new Object(),而是先声明一个函数没有ObjectType(),然后在它的定义里使用关键字this添加属性和方法。
function myObjectType(){
this.info = 'I am a shiny new object';
this.showInfo = function(){
alert(this.info);
}
this.setInfo = function(newInfo){
this.info = newInfo;
}
这段代码添加了一个属性info,两个方法showInfo和setInfo,前一个方法显示info属性当前保存的值;后移个方法接收一个参数newInfo,用它的值覆盖info的值。
(5)对象实例化
在定义了构造函数后,可以方便地创建对象的实例:
var myNewObject = new myObjectType();
(6)构造参数函数
在把对象实例化时,还可以通过给构造函数传递一个或多个参数来定制对象。在下面的代码里,构造函数的定义包含了一个参数personName,它的值会赋予构造函数的name属性。以后在实例化两个对象时,我们给每个实例都传递了一个姓名作为参数。
var myNewObject = new myObjectType();
function Person(personName){
this.name = personName;
this.info = 'I am called '+ this.name;
this.showInfo = function(){
alert(this.info);
}
}
var person1 = new Person('Adam');
var person2 = new Person('Eve');
3,使用prototype扩展和继承对象
(1)扩展对象
例子:给它添加一个新方法sayHello。
Person.prototype.sayHello = function(){
alert(this.name + " says hello");
}
(2)继承
继承是指从一种对象类型创建另一种对象类型,新对象类型继承老对象类型的属性和方法,还可以可选地添加自己的属性和方法。通过这种方式,我们可以先设计出“通用”的对象类型,然后继承来不断细化它们来得到更特定的类型,这样可以节省很多工作。
javascript模拟实现继承的方式也是使用关键字prototype。
例子:从Pet对象中继承Dog
//先定义一个Pet对象
function Pet(){
this.animal = "";
this.name = "";
this.setAnimal = function(newAnimal){
this.animal = newAnimal;
}
this.setName = function(newName){
this.name = newName;
}
//定义一个Dog对象
function Dog(){
this.breed = "";
this.setBreed = function(newBreed){
this.breed = newBreed;
}
}
从Pet继承属性和方法,
Dog.prototype = new Pet();
这样就不仅可以访问Dog里的属性和方法,还可以访问Pet里的属性和方法:
var myDog = new Dog();
myDog.setName("Alan");
myDog.setBreed("Greyhound");
alert(myDog.name + " is a "+myDog.breed);
#使用javascript还可以扩展javascript内置的对象。
4,封装
封装是面向对象编程的一种能力,表示把数据和指令封装到对象内部。其具体实现方法在不同的语言里有所区别。对于javascript来说,在构造函数内部声明的变量只能在对象内部使用,对于外部是不可见的。构造函数内部声明的函数也是这样的。
如果想从外部访问这些变量和函数,需要在赋值时使用关键字this,这时它们就成为了对象的属性和方法。
二,遍历DOM
1,DOM节点
(1)节点类型
nodetype值和节点类型:1,元素 2,属性 3,文本 4,CDATA区域 5,实体引用 6,实体 7,执行指令 8,HTML注释 9,文档 10,文档类型(DTD) 11,文档片段 12,标签。
(2)childNodes属性
每个节点都有一个childNodes属性。这个类似数组的属性包含了当前节点的全部子节点的集合,我们可以访问这些子节点的信息。
childNodes集合称为"节点列表"(NodeList),其中的项目以数值进行索引。集合(在大多数情况下)的表现类似于数组,我们可以像访问数组元素一样访问集合里的项目,还可以像对待数组一样遍历集合的内容,但有些数组方法是不能用的,比如push()和pop()。
节点列表是一个动态集合,这表示集合的任何改变都会立即反映到列表。
使用childNodes属性
利用childNodes属性返回的集合,我们可以查看程序清单里<ol>元素的内容。编写一个简单的程序,读取<ol>元素的子节点,并且放回列表里的总数。
首先,利用<ol>的id获取它:
var olElement = document.getElementById("toDoList");
现在,<ol>元素的子节点就包含在这个对象里了:
olElement.childNodes
由于我们只想操作子节点里的<li>元素,所以在遍历childNodes集合时,只统计nodeType == 1(也就是HTML元素)的节点,忽略其他的元素(比如注释和空白)。处理集合的方式与数组很相似,比如这里使用length属性(就像对数组使用该属性一样)
var count = 0;
for(var i =0;i<olElement.childNodes.length;i++){
if(olElement.childNodes[i].nodeType == 1) count++;
}
(3)firstChild 和 lastChild
在childNodes数组里可以使用firstChild和lastChild选择数组中的第一个和最后一个元素
(4)parentNode属性
parentNode属性保存节点的父节点。
(5)nextSibling 和 previousSibling
兄弟节点是指同样具有相同父节点的那些节点。perviousSibling和nextSibling属性分别返回节点的前一个和后一个兄弟节点,如果不存在相应的节点,就返回NULL。
(6)节点值
DOM节点的nodeValue属性返回保存在节点里的值,我们一般用它返回文本节点里的内容。
从前面统计列表项目数量的范例出发,获取页面的<p>元素里包含的文本。为此,我们需要访问相应的<p>节点,找到它包含的文本节点,再利用nodeValue属性返回其中的信息:
var text = '';
var pElement = document.getElementById("toDoNotes");
for(var i=0;i<pElement.childNodes.length;i++)
{
if (pElement.childNodes[i].nodeType == 3){
text += pElement.childNodes[i].nodeValue;
};
}
alert("The paragraph says:\n\n" + text );
(7)节点名称
nodeName属性以字符串形式返回节点的名称。这个属性是只读的,不能修改它的值。
当nodeName返回元素名称时,并不包括HTML源代码里使用的尖括号<>。
var pElement = document.getElementById("toDoNotes");
alert(pElement.nodeName);
nodeName属性的返回值
- nodeType 1 节点类型:元素 nodeName值:元素(标签)名称
- nodeType 2 节点类型:属性 nodeName值:属性名称
- nodeType 3 节点类型:文本 nodeName值:字符串“#text”
2,利用getElementsByTagName()选择元素
前面介绍过利用document对象的getElementById()方法访问页面里的元素。document的另一个方法getElementsByTagName可以获取特定的全部标签,将其保存在一个数组里。
和getElementById()一样,getElementsByTagName()方法也接收一个参数,然而,它需要的参数并不是元素的ID,而是标签的名称。
3,读取元素的属性
HTML元素通常会具有一些属性,保存着相关的信息:
<div id="id1" title="report">Here is some text.</div>
属性通常放置在标签的前半部分,其形式是"属性=值"。属性本身是所在元素的子节点
在获得了目标元素之后,就可以利用getAttribute()方法读取它的属性值:
var myNode = doccument.getElementById("id1");
alert(myNode.getAttribute("title"));
上面的两行代码会在alert对话框里显示"report"。如果尝试访问不存在的属性。getAttribute()会返回null。利用这个特性可以检测一个节点元素是否定义了特定的属性
4,DOM操作
(1)创建节点
给DOM数添加新节点需要两个步骤:
1,首先是创建一个新节点。节点创建之后处于某种"不确定状态",它的确存在,但不属于DOM树的任何位置,也就不会出现在浏览器窗口里。
2,接下来把接待你添加到DOM树的指定位置,它就成为页面的组成部分了。
接下来介绍document对象用于创建节点的一些方法。
createElement()
createElement()方法可以新建任何类型的标准HTML元素,比如段落,区间,表格,列表等。
假设我们要新建一个<div>元素,为此,只需要把相关的节点名称(也就是"div")传递给createElement方法:
var newDiv = document.createElement("div");
新的<div>元素就存在了,但目前还没有内容,没有属性,在DOM树立也没有位置。稍后就会介绍如何解决这些问题。
createTextNode()
页面里有很多HTML元素需要文本形式的内容,这就需要使用createTextNode()方法。它的工作方式类似于createElement(),但是它的参数不是nodeName,而是元素需要的文本内容:
var newTextNode = document.createTextNode("here is some text content.");
cloneNode()
重复劳动是最没有意义的,如果文档中已有的节点与需要新建的节点很像,就可以使用cloneNode()来新建节点。
和createElement()和createTextNode()方法不同,cloneNode()接受一个单个的参数,这是一个True或false的布尔值。
当参数为true时,表示不仅要复制节点,还要复制它的全部子节点:
var myDiv = document.getElementById("id1");
var newDiv = myDiv.cloneNode(true);
上述代码让javascript复制了元素及其子节点,这样myDiv里的文本(保存在元素的文本子节点里)就会完整的复制到新的<div>元素。
如果是下面这样的代码:
var newDiv = myDiv.cloneNode(false);
新建的<div>元素与原始元素相同,但是没有子节点。它会具有一样的属性(当然,前提是原始节点的类型是元素节点)。
#注意在复制一个节点的时候,记得要修改新的元素的id,因为一个文档中id值应该是唯一的。
(2)操作子节点
前面新建的节点不在DOM树的任何位置,因此并没有上面实际的意义。document对象具有一些特定的方法,专门用于在DOM树里放置节点,接下来介绍他们。
appendChild()
把新节点添加到DOM数的最简单方法也许就是把它作为文档中已有节点的一个子节点。这只需要获取父节点,然后调用appendChild()方法:
var newText = document.createTextCode("here is some text content:");
var myDiv = document.getElementById("id1");
myDiv.appendChild(newText);
这段代码新建一个文本节点,并且把它添加为现有<div> (id为id1)的子节点。
appendChild()方法总是在已有的最后一个子节点之后添加子节点,所以添加的节点会成为父节点的lastChild。
appendChild()方法不仅可以用于文本节点,而且可以用于各种类型的节点。
insertBefore()
appendChild()总是把新的子节点添加到子节点的末尾,而insertBefore()方法可以指定一个子节点,然后把新节点插入到它前面。
这个方法有两个参数:要插入的新节点,指示插入位置的节点(插入到这个节点的前面)。
<div id="id1">
<p id="para1">this paragraph contains some text</p>
<p id="para2">hers is some more text</p>
</div>
<!//新建一个段落!>
var newPare = document.createElement("p")
<!指明父节点,以及想要在哪个子节点之前插入:!>
var myDiv = document.getElementById("id1");
var para2 = document.getElementById("para2");
myDiv.insertBefore(newPara,para2);
replaceChild()
replaceChild()方法可以把父元素现有的一个子节点替换为另一个子节点。它有两个参数,一个是新的子节点,另一个是现有的子节点。 replaceChild(newElement,oldElement)
一个使用replaceChild()的例子
<!DOCTYPE html>
<html>
<head>
<title>Replace Page Element</title>
<script>
function replaceHeading(){
var newH2 = document.createElement("h2");
var newH2Text = document.createTextNode("welcome!");
newH2.appendChild(newH2Text);
var myDiv = document.getElementById("id1");
var oldP = document.getElementById("para1");
myDiv.replaceChild(newH2,oldP);
}
window.onload = function(){
document.getElementById("btn").onclick = replaceHeading;
}
</script>
</head>
<body>
<div id="id1">
<p id = "para1">Welcome to my web page.</p>
<p id = "para2">Please take a look around.</p>
<input id="btn" value="replace Element" type="button" />
</div>
</body>
</html>
removeChild()
removeChild()方法专门用于从DOM数里删除子节点。
var myDiv = document.getElementById("id1");
var myPara = document.getElementById("para2");
myDiv.removeChild(myPara);
removeChild()方法的返回值是对删除节点的引用,在需要时,可以利用它对已经删除的节点实现进一步操作:
var removedItem = myDiv.removeChild("myPara");
alert('Item whit id' + removedItem.getAttribute("id") + ' has been removed.');
(3)编辑元素属性
前一章介绍过使用getAttribute()方法读取元素属性。还有一个相应的setAttribute()方法可以为元素节点创建属性并赋值。它有两个参数,一个是要添加的属性,另一个是属性值。此外,设置现有属性的值就会改变该属性的值。也可以使用这一方法来有效编辑已有的属性的值:
var myPara = document.getElementById("para1");
myPare.setAttribute("title","hello");
(4)动态加载javascript文件
在有些情况下,我们需要给已经在浏览器中加载的页面随时加载javascript代码,为此可以利用createElement()动态新建<script>元素,其中包含需要的代码,然后把这个元素添加到页面的DOM。
var scr = document.createElement("script");
src.setAttribute("src","newScript.js");
document.head.appendChild(scr);
由于appendChild()方法把新节点添加到最后一个子节点之后,所以新的<script>元素会位于页面的<head>部分的末尾。
注意,如果以这种方式动态加载javascript源文件,在文件加载之前,页面不能使用其中包含的代码。
在使用这些额外代码之前,最好先进行检测。
几乎全部现代浏览器在脚本完成下载之后都会触发一个onload事件,它与window.onload事件的工作方式类似,只不过后者是在页面完成时加载触发,而前者是在外部资源(本例是javascript源文件)完整下载并可以使用时触发:
src.onload = function(){
...新代码在加载完成之后要执行的操作...
}
5,一个使用DOM动态创建菜单的例子
动态创建菜单范例的HTML文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Scripting the DOM</title>
<script src= "menu.js"></script>
<script>window.onload = makeMenu ;</script>
</head>
<body>
<h1>h1</h1>
<h2>h2</h2>
<p>p1</p>
<h2>h3</h2>
<p>p2</p>
<h2>h4</h2>
<p>p3</p>
<h2>h5</h2>
<p>p4</p>
<h2>h6</h2>
<p>p5</p>
</body>
</html>
menu.js的javascript代码
function makeMenu(){
//获取所有h2元素
var h2s = document.getElementsByTagName("h2")
//为菜单创建一个新的页面元素
var menu = document.createElement("div")
//创建一个UL元素,并将其添加到菜单div
var menuUI = document.createElement("ul")
menu.appendChild(menuUI);
//遍历H2元素
for(var i =0;i<h2s.length;i++){
//获取h2元素的文本节点
var itemText = h2s[i].childNodes[0].nodeValue;
//添加一个列表项
var menuLi = document.createElement("li");
menuUI.appendChild(menuLi);
var menuLiA = document.createElement("a");
menuLiA = menuLi.appendChild(menuLiA);
//设置链接的href
menuLiA.setAttribute("href","#item"+i);
//设置链接的文本
var menuText = document.createTextNode(itemText);
menuLiA.appendChild(menuText);
//创建相应的锚点元素
var anc = document.createElement("a");
anc.setAttribute("name","item"+i);
//将锚点元素添加到标题列
document.body.insertBefore(anc,h2s[i]);
}
//将菜单添加到页面顶部
document.body.insertBefore(menu,document.body.firstChild);
}
脚本运行的结果