11 |
|
(2)、節點類型說明
、節點,元素 等。
屬性節點,就是元素節點的屬性,如 id 、class 、name 等。屬性節點不能被看作是元素節點,因而在DOM 中屬性沒有被認為是文檔樹的一部分,換句話說就是說屬性節點是包含他的元素節點的一部分,他並不作為一個單獨的節點在文檔樹中出現。
文本節點,就是只包含文字內容的節點。可以包含更多訊息,也可以只包含空白,在文檔樹中元素的文字內容和屬性的文字內容都是由文字節點來表示的。
註釋節點,就是文檔中的註釋,其形式為 。
文檔節點,就是整個文檔,是文檔樹的根節點,是文檔中所有其他節點的父節點。這裡要注意:文檔節點並不是 HTML 文件的根元素,在建構 DOM 樹時,根元素並不適合作為根節點,於是就有了文檔節點,而根元素是作為文檔節點的子節點出現的。 將整個 HTML 文件程式碼之上看為一個文檔節點,這個節點下包含一個文檔類型節點 和一個元素節點 ,兩個子節點。
文檔類型節點,每一個 Document 都有一個 DocumentType 屬性, 是文檔類型節點。
文檔片段節點,是輕量級的或最小的 Document 對象,它表示文檔的一部分或者是一段,它不屬於文檔樹。 不過它有一種特殊的行為,這一行為非常有用,例如當請求把一個DocumentFragment 節點插入到文件的時候,插入的不是 DocumentFragment 自身,而是它所有的子孫節點。這時 DocumentFragment 就成了有用的佔位符,暫時存放那些依序插入文件的節點,同時它也有利於實現文件的剪下、複製、貼上等操作。像 JS 程式碼中插入元素所定義的變量,這個變數只是作為一個暫時的佔位符。這就是所謂的文檔片段節點。
文件片段也叫文檔碎片,建立一個文檔碎片可使用 document.createDocumentFragment() 方法,可直接在父節點下插入 n 個子節點。 在理論上文檔碎片可以提高 DOM 操作效能。文件碎片在低版本瀏覽器可以大大提高頁面效能,但是在高級的瀏覽器,幾乎效能沒有提高,而且還會影響效能。所以不建議使用,這東西基本上已經被淘汰了。下面是建立文檔碎片的實例。
實例:在 ul 元素下插入一個文檔碎片
属性的一系列操作是与元素的类型息息相关的,如果我们不对元素的节点类型作判断,就不知道如何操作:例如:obj.xx = yy 还是 obj.setAttribute(xx, yy),setAttribute() 方法可添加一个新属性并指定值,或者把一个现有的属性设定为指定的值,如果我们知道节点的类型,就可以直接设置或者是使用方法设置,所以我们有必要判断节点的类型,避免耗费资源,造成意想不到的结果。判断节点类型可使用 nodeType 属性用数值常量进行判断,其操作很简单,就是判断某个节点的节点类型是否等于该节点类型的数值常量。
实例:节点类型判断
<body>
<!-- 这是一个注释。 -->
<div id="div1">这是一个div元素节点。</div>
<script>
//判断是否为注释节点
var commentNode = document.body.childNodes[1];
if(commentNode.nodeType == 8){
alert('该节点是注释节点。');
}
//判断是否为元素节点
var divNode = document.getElementById('div1');
if(divNode.nodeType == 1){
alert('该节点是元素节点。');
}
//判断是否为文本节点
var textNode = document.getElementById('div1').childNodes[0];
if(textNode.nodeType == 3){
alert('该节点是文本节点。');
}
</script>
其实我们在获取节点的子节点时,不使用 childNodes 属性,而使用 children 属性,就可以避免这一问题,因为通过 childNodes 属性返回的是子节点集合,不仅包括元素节点,而且还包括文本节点,浏览器会将标签之间的空白默认为文本节点,而使用 children 属性,则只返回元素节点,不包括文本节点,还不包括注释节点。
节点名称和节点值,直接使用实例演示:
<body>
<!-- nodeName 和 nodeValue 演示 -->
<div id="div1">节点名称和节点值。</div>
<script>
//元素节点 = nodeType:1
var divNode = document.getElementById('div1');
console.log('元素节点的节点名称和值:' + divNode.nodeName + '/' + divNode.nodeValue);
//nodeName:返回 元素的标签名(DIV)全部大写。nodeValue:返回 null
//属性节点 = nodeType:2
var attrNode = divNode.attributes[0];
console.log('属性节点的节点名称和值:' + attrNode.nodeName + '/' + attrNode.nodeValue);
//nodeName:返回 属性的名称(id)。nodeValue:返回 属性的值(div1)
//文本节点 = nodeType:3
var textNode = divNode.childNodes[0];
console.log('文本节点的节点名称和值:' + textNode.nodeName + '/' + textNode.nodeValue);
//nodeName:返回 #text。nodeValue:返回 节点所包含的文本
//(comment)注释节点 = nodeType:8
var commentNode = document.body.childNodes[1];
console.log('注释节点的节点名称和值:' + commentNode.nodeName + '/' + commentNode.nodeValue);
//nodeName:返回 #comment。nodeValue:返回 注释的内容
//(DocumentType)文档类型节点 = nodeType:10
console.log('文档类型节点的节点名称和值:' + document.doctype.nodeName + '/' + document.doctype.nodeValue);
//nodeName:返回 document的名称(html)。nodeValue:返回 null
//(DocumentFragment)文档片段节点 = nodeType:11
var farg = document.createDocumentFragment();
console.log('文档片段节点的节点名称和值:' + farg.nodeName + '/' + farg.nodeValue);
//nodeName:返回 #document-fragment。nodeValue:返回 null
</script>
2、DOMReady
(1)、JS 在页面中的位置
页面中的 JS 代码,可以引入外部的 JS 文件,也可以放在 标签中或 标签中,放在 body 中的 JS 会在页面加载的时候被执行,而 head 中的 JS 会在被调用的时候才执行。
放在 标签中或 标签中 的区别:
浏览器解析 HTML 文档是从上到下、从左到右依次进行的,如果把 JS 放在 head 里的话,则先被解析,但这时候 body 还没有被解析,所以会返回空值,也就是会出错。放在 head 中的 JS 代码会在页面加载完成之前就读取,而放在 body 中的 JS 代码,会在整个页面加载完成之后读取。这就说明了,如果我们想定义一个全局对象,而这个对象是页面中的某个按钮时,我们必须将其放入 body 中,道理很明显:如果放入head,那当你定义的时候,那个按钮都没有被加载,可能获得的是一个 undefind。
脚本应该放置的位置:
页面中的 JS 会在浏览器加载页面的时候被立即执行,有时候我们想让一段脚本在页面加载的时候执行,而有时候我们想在用户触发一个事件的时候执行脚本。
需调用才执行的脚本或事件触发执行的脚本放在 HTML 的 head 部分中,head 部分中的脚本,可以保证脚本在任何调用之前被加载。
当页面被加载时执行的脚本放在 HTML 的 body 部分。放在 body 部分的脚本通常被用来生成页面的内容。
body 和 head 部分可同时有脚本:文件中可以在 body 和 head 部分同时存在脚本。
外部脚本:
有时候需要在几个页面中运行同样的脚本程序, 这时就需要用到外部脚本,而不需要在各个页面中重复的写这些代码。
(2)、JS 在页面中的应用
①、放在 body 部分中:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript实例</title>
</head>
<body>
<h1 id="main">这是h1标题中的一些文本。</h1>
<script>
document.getElementById('main').style.color = 'red';
</script>
</body>
</html>
上面的实例,通过 JS 改变了 h1 标题的 color 属性,当打开页面时标题显示为红色。
②、放在 head 部分中:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript实例</title>
<script>
document.getElementById('main').style.color = 'red';
</script>
</head>
<body>
<h1 id="main">这是h1标题中的一些文本。</h1>
</body>
</html>
将同样的 JS 代码放在 head 部分中,就无法正常运行了,浏览器报错:未捕获的类型错误:不能读取一个空元素的样式属性。出现这样的错误就是没有分清 HTML标签和 DOM 节点之间的区别,HTML 是超文本标记语言,用于展示内容,而行为交互是需要通过 DOM 操作来实现的,HTML 标签要通过浏览器解析,才能变成 DOM 节点,当我们向地址栏输入一个 URL 时,开始加载页面,然后就能够看到内容,在这期间就有一个 DOM 节点构建的过程,节点是以树的形式组织的。JS 对于 DOM 的操作必须在 DOM 树构建完毕后,而上面的实例,head 部分中的 JS 会被浏览器先解析,但是这时候 body 中的元素还没有被解析,DOM树并没有构建完毕,所以就出事了。那如果就想把 JS 代码放在 head 部分中,还不想出错,该怎么解决呢?下文再做具体分析。
(3)、DOMReady
HTML 标签需要通过浏览器解析才会变成 DOM 节点,在刷新 URL 地址的时候就有 DOM 节点的构建过程,当页面上所有的 HTML 标签都转换为节点以后,DOM 树才构建完毕,这就简称为 DOMReady。
那浏览器是如何将 HTML 标签解析变成节点的呢,浏览器是通过渲染引擎来实现的,渲染引擎的职责就是渲染,即把请求的 HTML 内容显示到浏览器屏幕上。所谓渲染,就是浏览器把请求到的 HTML 内容显示在屏幕上的过程,所谓渲染引擎,就是浏览器的内核,是浏览器最核心的东西。
各大浏览器的渲染引擎:
Firefox、Chrome 和 Safari 是基于两种渲染引擎构建的:
Firefox 使用 Geoko 內核,是 Mozilla 自主研發的渲染引擎,Gecko 是開源引擎,Gecko 也是一個跨平台內核,可以在Windows、Linux和Mac OS X等主要作業系統中運行。
Safari 和 Chrome 都使用 WebKit 內核,WebKit 和 WebCore 皆是 KHTML 的衍生產品,HT 謝謝 樣本。 KHTML 擁有速度快速的優點,對錯誤語法的容忍度比 Mozilla 產品所使用的 Gecko 引擎小。 WebKit 是一款開源渲染引擎,他本來是為 linux 平台研發的,後來由蘋果公司移植到 Mac 及 Windows上使用。
而IE 使用的是Trident 核心(又稱為MSHTML),是微軟的視窗作業系統(Windows) 搭載的網頁瀏覽器Inter ,該核心程式在1997年的IE 4 中首次被採用,之後不斷地加入新的技術並隨著新版本的IE 發布,但是Trident 只能用於Windows 平台。 渲染引擎在取得內容之後的基本流程:
構建樹的基本流程: render 樹 -> 繪製render 樹
渲染引擎開始解析HTML,並將標籤轉化為一棵DOM樹,接著,他解析外部CSS 文件及style 標籤中的樣式信息,這些樣式信息以及 HTML 中的可見性指令將被用來建構另一棵樹- render 樹,用來渲染DOM 樹的樹 - 渲染樹(render tree)。
r
ender 樹由一些包含顏色和大小等屬性的矩形組成,他將被按照正確的順序顯示到屏幕上。 render 樹構建好了之後,將會執行佈局過程,它將確定每個節點在屏幕上的確切坐標。再下一步就是繪製,即遍歷 render 樹,並使用 UI 後端層繪製每個節點。
值得注意的是,這個過程是逐步完成的,為了更好的用戶體驗,渲染引擎將會盡可能早的將內容呈現到屏幕上,並不會等到所有的HTML 都解析完成之後再去建置和佈局render 樹。他是解析完一部分內容就顯示一部分內容,同時,可能還在透過網路下載其餘內容。
瀏覽器開啟頁面的常規流程:
⑀⑀、下載到同時進行的。
②、在渲染到頁面的某一部分時,其上面的所有部分都已經下載完成(並不是說所有相關聯的元素都已經下載完)。
③、如果遇到语义解释性的标签嵌入文件(JS 脚本,CSS 样式),那么此时IE的下载过程会启用单独连接进行下载。
④、并且在下载后进行解析,解析过程中,停止页面所有往下元素的下载。
⑤、样式表在下载完成后,将和以前下载的所有样式表一起进行解析,解析完成后,将对此前所有元素(含以前已经渲染的)重新进行渲染。
⑥、JS、CSS中如有重复定义,则之后定义的函数将覆盖前面定义的函数。
3、DOMReady 实现
脚本在 HTML DOM 加载渲染布局显示完成后才能运行,且要放在 body 部分内容的后面,而 DOMReady 则定义使脚本无论放在哪里都能执行。
(1)、使用定时器
把 JS 代码放在 head 部分中,还不想出错,就是等 DOM 树构建完毕后,再执行脚本,那么是否可以使用定时器延迟脚本的执行,避免这个错误呢。
<!DOCTYPE html><html><head>
<meta charset="UTF-8">
<title>JavaScript实例</title>
<script>
//在DomReady完毕后2秒执行。打开页面可以看到一个样式变化的过程。
setTimeout(function (){
document.getElementById('main').style.color = 'red';
},2000);</script></head><body><h1 id="main">这是h1标题中的一些文本。</h1></body></html>
测试上面的代码,可以看到,在打开页面时标题显示为黑色,过一会后才会转变为红色,虽然这样不会报错了,但这并不是我们想要的效果。那可以再将延迟时间缩短,不就解决了吗,我们将时间设置为 30 毫秒再试试。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript实例</title>
<script>
//将事件缩至30毫秒
setTimeout(function (){
document.getElementById('main').style.color = 'red';
},30);
</script>
</head>
<body>
<h1 id="main">这是h1标题中的一些文本。</h1>
</body>
</html>
测试上面代码,在打开页面时显示为红色,但如果刷新页面的话,还是能看到黑色的闪动了一下,虽然无伤大雅,但这样还存在着一个很严重的问题,如果 DomReady 时间超过了 30 毫秒,那还是会出错,显然这方法是不可行的。
(2)、使用 window.onload
window.onload 事件是在浏览器绘制完 DOM 节点,再加载完页面上的所有资源后,然后执行脚本。也就是说在文档解析渲染,资源加载完成之前,不让脚本执行。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript实例</title>
<script>
window.onload = function (){
document.getElementById('main').style.color = 'red';
};
</script>
</head>
<body>
<h1 id="main">这是h1标题中的一些文本。</h1>
</body>
</html>
测试上面的代码,打开页面后显示为红色,再刷新也不会出现黑色的闪动,显然该方法是可行的。如果把 JS 代码放在 head 部分中,一般情况下都需要绑定一个监听,即window.onload 事件,等全部的 HTML 文档渲染完成后,再执行代码,这样就妥妥的了。
该方法在文档外部资源不多的情况下,是没什么问题,但如果网站有很多图片,我们要用 JS 做到在点击每张图片时弹出图片的 src 属性,这时候就有问题了,而且是出大事了,我们都知道 DOM 树很快就构建完成了,但是这么多图片还在缓慢的加载中,想要先执行 JS 的效果,就得等到所有的图片全部加载完毕后才能实现,而在这期间页面不会响应用户任何操作,浏览器就跟死了一般。所以使用 window.onload 对于很多实际的操作(比如DOM操作,事件绑定等)就显得太迟了,比如图片过多,window.onload 却迟迟不能触发,影响用户体验。而 DOMReady 就可以满足提前绑定事件的需求。
(3)、jQuery 实现 DOMReady
最简单的方法就是使用 jQuery,jQuery 是一个 JS 函数库,封装了大量的 JS 方法,使用 jQuery 可以极大地简化 JS 编程。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript实例</title>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script>
//jQuery 实现
$(document).ready(function (){
document.getElementById('main').style.color = 'red';
});
</script>
</head>
<body>
<h1 id="main">这是h1标题中的一些文本。</h1>
</body>
</html>
(4)、JS 实现 DOMReady
用 JS 实现 DOMReady 其实也很简单,可以添加一个监听事件,即 addEventListener,该方法的语法为:document.addEventListener("事件名称", 函数, false);,false 表示在冒泡阶段捕获。再传入DOMContentLoaded 事件,这个事件是从 onload 事件延伸而来的,当一个页面完成加载时,初始化脚本的方法是使用 onload 事件,该方法的缺点是仅在所有资源都完全加载后才被触发,如果页面的图片很多的话,从用户访问到 onload 触发可能需要较长的时间,所以开发人员随后创建了一种自定义事件,DOMReady,他在 DOM 构建之后、资源加载之前就可以被触发,他的表现形式就是 DOMContentLoaded 。jQuery 源码中也是使用该方法完成的。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JavaScript实例</title>
<script>
function domReady(fn){
document.addEventListener('DOMContentLoaded', fn, false);
}
domReady(function (){
document.getElementById('main').style.color = 'red';
});
</script>
</head>
<body>
<h1 id="main">这是h1标题中的一些文本。</h1>
</body>
</html>
上面代码,我们给参数传入一个回调函数,将 DOMReady(封装函数) 方法封装为一个函数,方便以后使用,该方法支持所有现代浏览器,但是不支持 IE9 之前的浏览器。
4、HTML 嵌套规则
了解 HTML 嵌套规则,是进行 DOM 操作的基础。
HTML 存在许多种类型的标签,有的标签下面只允许特定的标签存在,这就叫 HTML 嵌套规则。
如果不按 HTML 嵌套规则写,浏览器就不会正确解析,会将不符合嵌套规则的节点放到目标节点的下面,或者变成纯文本。
所以在编写任何代码时,都需要按照规则编写,有利于解析,有利于操作,有利于优化,有利于维护,有利于重构。
HTML 元素可简单的分为块状元素和内联元素两类。下面是一些需要注意的嵌套规则:
①、块元素可以包含内联元素或某些块元素,但内联元素却不能包含块元素,他只能包含其他的内联元素,li元素内可以包含 p 元素。
<div>
<h1></h1>
<p></p>
</div>
<a href=""><span class=""></span></a>
<ul>
<li>
<div></div>
</li>
</ul>
②、块元素不能包含在p元素内。
<p>这是一些文本
<div style="width:100px;height:200px;border:1px solid black;"></div>
</p>
<p>这是一些文本
<ul>
<li>苹果</li>
<li>香蕉</li>
</ul>
</p>
测试上面的代码,虽然他们都能被正常显示,但是并不处在同一层。
③、有几个特殊的块元素只能包含内联元素,不能包含块元素,这几个特殊的块元素是 h1-h6、p 和 dt。
④、块元素与块元素并列,内联元素与内联元素并列。
<div>
<h2></h2>
<p></p>
</div>
<div>
<a href=""></a>
<span class=""></span>
</div>
5、DOM 操作
DOM 节点是一个非常复杂的东西,对于他的每一个属性的访问,有可能会向上搜寻到 n 多个原型点,因此 DOM 操作是个很耗性能的操作。
使用 DOM 操作很复杂,所以建议尽量使用现成的框架来实现业务,比如 MVVM 框架,将所有的 DOM 操作,都转交给框架内部做精细有效的处理。
以上就是JavaScript学习总结【9】、DOM Ready的内容,更多相关内容请关注PHP中文网(www.php.cn)!