JavaScript与DOM(上)
文档对象模型 Document Object Model
DOM(Document Object Model,文档对象模型)是一个通过和 JavaScript 进行内容交互的 API。Javascript 和 DOM 一般经常作为一个整体,因为 Javascript 通常都是用来进行 DOM 操作和交互的。
关于DOM,有些知识需要注意:
- window 对象作为全局对象,也就是说你可以通过 window 来访问全局对象。
- 属性在对象下面以变量的形式存放,在页面上创建的所有全局对象都会变成 window 对象的属性。
- 方法在对象下面以函数的形式存放,因为左右的函数都存放在 window 对象下面,所以他们也可以称为方法。
- DOM 为 web 文档创建带有层级的结果,这些层级是通过 node 节点组成,这里有几种 DOM node 类型,最重要的是 Element,Text,Document。
- Element 节点在页面里展示的是一个元素,所以如果你有段落元素(``),你可以通过这个 DOM 节点来访问。
- Text 节点在页面里展示的所有文本相关的元素,所以如果你的段落有文本在里面的话,你可以直接通过 DOM 的 Text 节点来访问这个文本
- Document 节点代表是整个文档,它是 DOM 的根节点。
- 每个引擎对 DOM 标准的实现有一些轻微的不同。例如,Firefox 浏览器使用的 Gecko 引擎有着很好的实现(尽管没有完全遵守 W3C 规范),但 IE 浏览器使用的 Trident 引擎的实现却不完整而且还有 bug,给开发人言带来了很多问题。
如果你正在使用 Firefox,我推荐你立即下载 Firebug 插件,对于你了解 DOM 结构非常有用。
Web 上的 JavaScript
Script 元素
当你在网站页面上使用 JavaScript 的时候,需要使用 `` 元素:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>JavaScript!</title>
</head>
<body>
<script type="text/javascript">
// <![CDATA[
// ]]>
</script>
</body>
</html>
上述代码,严格来说 SCRIPT 的 TYPE 属性应该设置为 application/javascript,但是由于 IE 不支持这个,所以平时我们不得不写成 text/javascript 或者直接去掉 type。另外你也可以看到在 SCRIPT 元素里的注释行// <![CDATA[ 是用来告诉支持 XHTML 的浏览器,这里面的代码是字符数据而不是 XHTML 标签,比如如果你在里面的数据使用了 < 或 >,浏览器就不会再解析成 XHTML 标签了。
Defer属性
任何在 SCRIPT 元素里声明的代码在页面加载的时候都会运行,唯一一个例外是给 SCRIPT 元素加上一个 defer 属性。defer 属性告诉浏览器加载完HTML文档以后再执行 JS 代码,但这个属性只能在 IE 下使用。
连接外部脚本
如果你想了解外部脚本,只需要简单地在 SCRIPT 上使用 SRC 属性就行了,使用单独的 JS 文件的好处是可以缓存,而且也不需要担心 CDATA 方面的问题:
<script type="text/javascript" src="my-script.js"></script>
JavaScript 必备
在我们继续 DOM 之前,我们来复习一下 JavaScript 的核心必备知识,如果你还不了解,也没关系,我们在这一章节将稍微花点时间来回顾一下。
JavaScript 有几种数据类型:Number,String,Boolean,Object,Undefined and Null。
单行注释使用双斜杠//,双斜杠后面的所有文字都会被注释掉,多行注意使用/*
和*/
括住。
Number
在 JavaScript 里所有的 Number 都是浮点型的,当声明一个数字变量的时候,记得不要使用任何引号。
// 注:使用var类声明变量
var leftSide = 100;
var topSide = 50;
var areaOfRectangle = leftSide * topSide; // = 5000
String
JavaScript 里声明字符串特别简单,和其它语言一样,在 JS 里使用单引号或双引号都可以。
var firstPart = 'Hello';
var secondPart = 'World!';
var allOfIt = firstPart + ' ' + secondPart; // Hello World!
// +符合是字符连接符。也用于数字相加
Boolean
布尔类型用于条件判断,布尔类型是只有 2 个值:true 和 false。任何使用逻辑操作符的比较都会返回布尔值。
5 === (3 + 2); // = true
// 你也可以将布尔值赋给一个变量
var veryTired = true;
// 这样使用
if (veryTired) {
// 执行代码
}
===也是比较操作符,不仅比较数值,还比较类型。
Function
函数是特殊的对象。
// 使用function操作符来声明新函数
function myFunctionName(arg1, arg2) {
// 函数代码
}
// 你也可以声明匿名函数
function (arg1, arg2) {
// Function code goes here.
}
// 运行函数很简单,直接在函数名称后面加上小括号就可以了
// 或者也可以带上参数
myFunctionName(); // 无参
myFunctionName('foo', 'bar'); // 有参数
// 也可以使用自调用
(function () {
// 这里自调用函数
})();
Array
数组也是特殊的对象,它包含了一批值(或对象),访问这些数据的话需要使用数字索引:
// 2种方式声明数组
// 字面量:
var fruit = ['apple', 'lemon', 'banana'];
// Array构造函数:
var fruit = new Array('apple', 'lemon', 'banana');
fruit[0]; // 访问第1个项(apple)
fruit[1]; // 访问第2个项(lemon)
fruit[2]; // 访问第3个项(banana)
Object
一个对象是一个 key-value 的集合,和数组相似,唯一的不同是你可以为每个数据定义一个名称。
// 2种类型定义Object对象
// 字面量(大括号)
var profile = {
name: 'Bob',
age: 99,
job: 'Freelance Hitman'
};
// 使用Object构造函数
var profile = new Object();
profile.name = 'Bob';
profile.age = 99;
profile.job = 'Freelance Hitman';
IF/Else 语句
JS 里使用最多的语句莫过于条件语句了:
var legalDrinkingAge = 21;
var yourAge = 29;
if ( yourAge >= legalDrinkingAge ) {
alert('You can drink.');
} else {
alert('Sorry, you cannot drink.');
JavaScript 操作符
建议你访问这个页面来查看所有的 JS 操作符,这里我仅仅给出一些例子:
// 加减乘除
var someMaths = 2 + 3 + 4 - 10 * 100 / 2;
// 等于
if ( 2 == (5 - 3 ) { /* 代码 */ } // == 比较是否相等
// 不等于
if ( 2 != (5 - 3 ) { /* 代码 */ }
// 严格等于(推荐)
2 === 2 // 代替 2 == 2
2 !== 3 // 代替 2 != 3
// 赋值:
var numberOfFruit = 9;
numberOfFruit -= 2; // 等价于 "numberOfFruit = numberOfFruit - 2"
numberOfFruit += 2; // 等价于 "numberOfFruit = numberOfFruit + 2"
Loop循环
Loop 循环在是遍历数组或者对象的所有成员的时候非常方便,JavaScript 里使用最多的是 FOR 和 WHILE 语句。
var envatoTutSites = ['NETTUTS', 'PSDTUTS', 'AUDIOTUTS', 'AETUTS', 'VECTORTUTS'];
// WHILE循环
var counter = 0;
var lengthOfArray = envatoTutSites.length;
while (counter < lengthOfArray) {
alert(envatoTutSites[counter]);
counter++; // 等价于counter += 1;
}
// FOR循环
// i只是用于迭代,可以任意取名
for (var i = 0, length = envatoTutSites.length; i < length; i++) {
alert(envatoTutSites[i]);
}
DOM 正文
访问 DOM 节点
我们来个例子,一个 HTML 里包含一段文本和一个无序的列表。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>JavaScript!</title>
</head>
<body>
<p id="intro">My first paragraph...</p>
<ul>
<li>List item 1</li>
<li>List item 1</li>
<li>List item 1</li>
<li>List item 1</li>
<li>List item 1</li>
</ul>
<script type="text/javascript">
// <![CDATA[
// ]]>
</script>
</body>
</html>
上面例子里,我们使用 getElementById DOM 方法来访问 p 段落,在 SCRIPT 里添加如下代码:
var introParagraph = document.getElementById('intro');
// 现在有了该DOM节点,这个DOM节点展示的是该信息段落
变量 introParagraph 现在已经引用到该 DOM 节点上了,我们可以对该节点做很多事情,比如查询内容和属性,或者其它任何操作,甚至可以删除它,克隆它,或者将它移到到 DOM 树的其它节点上。
文档上的任何内容,我们都可以使用 JavaScript 和 DOM API 来访问,所以类似地,我们也可以访问上面的无序列表,唯一的问题是该元素没有 ID 属性,如果 ID 的话就可以使用相同的方式,或者使用如下 getElementsByTagName 方式:
var allUnorderedLists = document.getElementsByTagName('ul');
// 'getElementsByTagName'返回的是一个节点集合
// - 和数组有点相似
getElementsByTagName
getElementsByTagName 方法返回的是一个节点集合,和数组类似也有 length 属性,重要的一个特性是他是 live 的——如果你在该元素里添加一个新的 li 元素,这个集合就会自动更新,介于他和数组类型,所以可以和访问数组一样的方法来访问,所以从 0 开始:
// 访问无序列表: [0]索引
var unorderedList = document.getElementsByTagName('ul')[0];
// 获取所有的li集合:
var allListItems = unorderedList.getElementsByTagName('li');
// 循环遍历
for (var i = 0, length = allListItems.length; i < length; i++) {
// 弹出该节点的text内容
alert(allListItems[i].firstChild.data);
}
以下图例更清晰地展示了 DOM 获取的知识:
DOM 穿梭
“穿梭”这个词主要是用来描述通过 DOM 查找节点,DOM API 提供了大量的节点属性让我们来往上或者往下查询节点。
所有的节点都有这些属性,都是可以用于访问相关的 node 节点:
- Node.childNodes: 访问一个单元素下所有的直接子节点元素,可以是一个可循环的类数组对象。该节点集合可以保护不同的类型的子节点(比如 text 节点或其他元素节点)。
- Node.firstChild: 与‘childNodes’数组的第一个项(‘Element.childNodes[0]‘)是同样的效果,仅仅是快捷方式。
- Node.lastChild: 与‘childNodes’数组的最后一个项(‘Element.childNodes[Element.childNodes.length-1]‘)是同样的效果,仅仅是快捷方式。shortcut。
- Node.parentNode: 访问当前节点的父节点,父节点只能有一个,祖节点可以用‘Node.parentNode.parentNode’的形式来访问。
- Node.nextSibling: 访问 DOM 树上与当前节点同级别的下一个节点。
- Node.previousSibling: 访问 DOM 树上与当前节点同级别的上一个节点。
通过这张图,理解起来就简单多了,但有个非常重要的知识点:那就是元素之间不能有空格,如果 ul 和 li 之间有空格的话,就会被认为是内容为空的 text node 节点,这样 ul.childNodes[0]就不是第一个 li 元素了。相应地,
的下一个节点也不是
- ,因为
和
- 之间有一个空行的节点,一般遇到这种情况需要遍历所有的子节点然后判断 nodeType 类型,1 是元素,2 是属性,3 是 text 节点,详细的 type 类型可以通过此地址:
Node.ELEMENT_NODE == 1
Node.ATTRIBUTE_NODE == 2
Node.TEXT_NODE == 3
Node.CDATA_SECTION_NODE == 4
Node.ENTITY_REFERENCE_NODE == 5
Node.ENTITY_NODE == 6
Node.PROCESSING_INSTRUCTION_NODE == 7
Node.COMMENT_NODE == 8
Node.DOCUMENT_NODE == 9
Node.DOCUMENT_TYPE_NODE == 10
Node.DOCUMENT_FRAGMENT_NODE == 11
Node.NOTATION_NODE == 12
总结
原生的 DOM 方法和属性足够我们日常的应用了,本章节我们只列举了一些例子,下一章节我们列举更多的例子,还会包括浏览器事件模型。