Eloquent JavaScript 笔记 十三:DOM
来源:互联网 发布:r9网络不稳定怎么回事 编辑:程序博客网 时间:2024/06/06 02:05
1. Document Structure
再看上一章的html例子:
<!doctype html><html> <head> <title>My home page</title> </head> <body> <h1>My home page</h1> <p>Hello, I am Marijn and this is my home page.</p> <p>I also wrote a book! Read it <a href="http://eloquentjavascript.net">here</a>.</p> </body></html>这个HTML文档的结构如下:
document
在js代码中,document是一个全局变量,它代表了整个HTML文档。 document.documentElement 就是 <html> 这个tag对应的对象。 document.head, document.body 分别对应<head> 和 <body> 对象。
2. Trees
还记得第11章的语法树吗?那么多递归访问,把我转的晕头转向。DOM也是这样的语法树。树根是 document.documentElement。
上图中每一个方框都是node。node有多种类型(nodeType),每种类型在js中都有一个对应的常量值,常见的三种类型是:
1. regular elements:
document.ELEMENT_NODE: 1
上图中的灰色方框
2. text nodes:
document.TEXT_NODE: 3
上图中的淡蓝色方框
3. comments
document.COMMENT_NODE: 8
<!-- 这是注释 -->
3. The Standard
DOM 不是专门为js设计的,甚至都不是专门为HTML设计的,它是一种语言中立的接口,或者说它是为XML设计的接口。而XML是一种更宽泛领域的超语言,HTML只是它的一个小应用。所以,后面会看到,我们在使用DOM操纵HTML时,经常会感觉有些别扭。例如,每一个element 都有childNodes属性,通过该属性可以访问它的所有子节点,而childNodes不是Array类型,它是NodeList类型,它没有slice和forEach方法。
如果操作DOM的行为比较多,代码会非常的冗长而难看。还好,已经有写第三方库提供了简单的方法,例如 jQuery。
4. Moving through the tree
上图描述了一个DOM结构,以及访问各个node的方法:
childNodes, parentNode, firstChild, previousSibling, nextSibling, lastChild
举个例子,在HTML中查找是否存在 "book" 这个单词:
function talksAbout(node, string) { if (node.nodeType == document.ELEMENT_NODE) { for (var i = 0; i < node.childNodes.length; i++) { if (talksAbout(node.childNodes[i], string)) return true; } return false; } else if (node.nodeType == document.TEXT_NODE) { return node.nodeValue.indexOf(string) > -1; }}console.log(talksAbout(document.body, "book"));// → true
5. Finding Elements
三个函数:
1. getElementsByTagName()
var link = document.body.getElementsByTagName("a")[0];console.log(link.href);
2. getElementById()
<p>My ostrich Gertrude:</p><p><img id="gertrude" src="img/ostrich.png"></p><script> var ostrich = document.getElementById("gertrude"); console.log(ostrich.src);</script>3. getElementsByClassName()
这个函数和getElementsByTagName()类似。 <p class="big"> ... </p>
6. Changing the Document
functions:
removeChild()
appendChild()
insertBefore()
replaceChild()
例子:
<p>One</p><p>Two</p><p>Three</p><script> var paragraphs = document.body.getElementsByTagName("p"); document.body.insertBefore(paragraphs[2], paragraphs[0]);</script>
一个node只能存在于一个位置,所以,把第2个p插入第0个p时,会先把它从原来的位置删除。
注意,replaceChild() 和 insertBefore() 的第一个参数是新插入的节点,第二个节点是参考位置的节点。7. Creating Nodes
先看个例子:
把<img> 节点替换成Text节点,Text的内容是 <img> 的alt属性的值。
<p>The <img src="img/cat.png" alt="Cat"> in the <img src="img/hat.png" alt="Hat">.</p><p><button onclick="replaceImages()">Replace</button></p><script> function replaceImages() { var images = document.body.getElementsByTagName("img"); for (var i = images.length - 1; i >= 0; i--) { var image = images[i]; if (image.alt) { var text = document.createTextNode(image.alt); image.parentNode.replaceChild(text, image); } } }</script>用 document.createTextNode() 创建Text节点。
注意,getElementsByTagName()、getElementsByClassName() 和 childNodes 等得到的节点列表是 live 的,也就是说,当DOM结构发生变化之后,通过这些方法的node列表(上面的var images)也会随之变化。
所以,上面代码遍历images时,要从后往前遍历。
可以把live的节点列表变成solid(固定的):
var arrayish = {0: "one", 1: "two", length: 2};var real = Array.prototype.slice.call(arrayish, 0);real.forEach(function(elt) { console.log(elt); });// → one// two第十一章也用过类似的方法:把一个 “像” 数组的对象转换成数组,因为它们的数据存储方式相同。
再看一个例子:
<blockquote id="quote"> No book can ever be finished. While working on it we learn just enough to find it immature the moment we turn away from it.</blockquote><script> function elt(type) { var node = document.createElement(type); for (var i = 1; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; } document.getElementById("quote").appendChild( elt("footer", "—", elt("strong", "Karl Popper"), ", preface to the second editon of ", elt("em", "The Open Society and Its Enemies"), ", 1950"));</script>使用 document.createElement(tag) 创建普通的节点。
8. Attributes
8.1. 有些节点的属性,在DOM中有相同名字的属性。
例如: <a id="mylink" href="..."> 中的href。
var mylink = document.getElementById("mylink");mylink.href="http://blog.csdn.net";8.2. 有些节点属性在DOM中没有对应的属性
我们自己定义的属性也没有,所以,需要用 getAttribute 和 setAttribute 来访问:
<p data-classified="secret">The launch code is 00000000.</p><p data-classified="unclassified">I have two feet.</p><script> var paras = document.body.getElementsByTagName("p"); Array.prototype.forEach.call(paras, function(para) { if (para.getAttribute("data-classified") == "secret") para.parentNode.removeChild(para); });</script>我们自己定义的属性最好加上data-前缀,以避免命名冲突。
8.3. 再看一个比较复杂的例子
查找 <pre> 中代码的关键字,把它们用粗体显示 (<strong>)
<p>Here it is, the identity function:</p><pre data-language="javascript">function id(x) { return x; }</pre><script> function highlightCode(node, keywords) { var text = node.textContent; node.textContent = ""; // Clear the node var match, pos = 0; while (match = keywords.exec(text)) { // regex 中有 /g,全局匹配,那么,有多少次匹配,while循环就会执行多少次。 var before = text.slice(pos, match.index); node.appendChild(document.createTextNode(before)); var strong = document.createElement("strong"); strong.appendChild(document.createTextNode(match[0])); node.appendChild(strong); pos = keywords.lastIndex; // 本次匹配的下一个字符的位置 } var after = text.slice(pos); node.appendChild(document.createTextNode(after)); } var languages = { javascript: /\b(function|return|var)\b/g }; function highlightAllCode() { var pres = document.body.getElementsByTagName("pre"); for (var i = 0; i < pres.length; i++) { var pre = pres[i]; var lang = pre.getAttribute("data-language"); if (languages.hasOwnProperty(lang)) highlightCode(pre, languages[lang]); } }</script>上面代码中,attribute的访问很容易理解,反倒是RegExp的使用方法有些费解。仔细看第11行和第17行的注释。
8.4. node的class属性
例如: <p class="big"> ... </p>
在DOM中class对应的属性是className,我们也可以用 getAttribute("class") 、setAttribute("class") 来访问它。
9. Layout
9.1. 类型
block:另起一行
<p>, <h1>, <div> 等
inline:不会另起一行
<a>, <strong>, <span> 等
9.2. size
offsetWith, offsetHeight:带border的宽和高
clientWith, clientHeight: 不带border的宽和高
9.3. 例子
<p style="border: 3px solid red"> I'm boxed in</p><script> var para = document.body.getElementsByTagName("p")[0]; console.log("clientHeight:", para.clientHeight); console.log("offsetHeight:", para.offsetHeight);</script>9.4. 获取一个element的精确位置
getBoundingClientRect( )
9.5. 浏览器的滚动条滚动之后的坐标pageXOffset, pageYOffset
9.6. 重新计算layout
改动DOM内容,读取position和size属性,或则调用getBoundingClientRect( ) 都需要重新计算layout,如果操作过于频繁(例如:在循环中),会导致性能很差,网页响应慢。
看个例子:
<p><span id="one"></span></p><p><span id="two"></span></p><script> function time(name, action) { var start = Date.now(); // Current time in milliseconds action(); console.log(name, "took", Date.now() - start, "ms"); } time("naive", function() { var target = document.getElementById("one"); while (target.offsetWidth < 2000) target.appendChild(document.createTextNode("X")); }); // → naive took 32 ms time("clever", function() { var target = document.getElementById("two"); target.appendChild(document.createTextNode("XXXXX")); var total = Math.ceil(2000 / (target.offsetWidth / 5)); for (var i = 5; i < total; i++) target.appendChild(document.createTextNode("X")); }); // → clever took 1 ms</script>
10. Styling
10.1. style 属性:
<p><a href=".">Normal link</a></p><p><a href="." style="color: green">Green link</a></p>10.2. display 类型:
display:block; 自己占一行
display:inline; 自己不单独占一行
display:none; 隐藏 (不改变DOM结构)
例如:
This text is displayed <strong>inline</strong>,<strong style="display: block">as a block</strong>, and<strong style="display: none">not at all</strong>.10.3. 修改style
<p id="para" style="color: purple"> Pretty text</p><script> var para = document.getElementById("para"); console.log(para.style.color); para.style.color = "magenta";</script>
中间带连接线的style,有两种访问方式:
style["font-family"]
style.fontFamily
11. Cascading Styles
11.1. cascading
多处设置的style合并到一起(三种地方:.css 文件, <head> 中的style段,elements的style属性)
element的style属性中的设置,优先级最高11.2. selector
tag
p {
font-size: 16px;
}
class
.subtle {
color: gray;
}
id#header {
background: blue;
}
匹配的越精确,优先级越高。下面的p.a.b#main 就比上面的 p 优先级高。
/* p elements, with classes a and b, and id main */p.a.b#main { font-size: 20px;}
p > a { ... }
<p> 的第一级 <a> 类子节点
p a { ... }
<p> 下面所有级别的 <a> 类节点
这些是最常用的selector,还有一些更复杂的,不再一一列出。
12. Query Selectors
上面讲了用 tag、class 和 id 获取elements,也可以通过selector获取elements。
querySelectorAll(sel),
获取所有符合selector的elements,document.querySelectorAll(), element.querySelectorAll()
querySelector(sel)
返回第一个符合条件的element
这两个方法返回的elements不是live的,不会随着DOM的变化而变化
例:
<p>And if you go chasing <span class="animal">rabbits</span></p><p>And you know you're going to fall</p><p>Tell 'em a <span class="character">hookah smoking <span class="animal">caterpillar</span></span></p><p>Has given you the call</p><script> function count(selector) { return document.querySelectorAll(selector).length; } console.log(count("p")); // All <p> elements // → 4 console.log(count(".animal")); // Class animal // → 2 console.log(count("p .animal")); // Animal inside of <p> // → 2 console.log(count("p > .animal")); // Direct child of <p> // → 1</script>
13. Positioning and Animating
style的position属性有三种类型:
static
默认。从上到下,从左到右,在浏览器中流式布局。
relative
top和left 相对于它的默认 (position:static时) 的位置
absolute
top和left 相对于父节点的坐标。父节点的position不能是static。如果父节点是static,则向上层找父节点。如果找不到符合条件的父节点,则相对于document。
例子:动态修改positin: relative 的top和left<p style="text-align: center"> <img src="img/cat.png" style="position: relative"></p><script> var cat = document.querySelector("img"); var angle = 0, lastTime = null; function animate(time) { if (lastTime != null) angle += (time - lastTime) * 0.001; lastTime = time; cat.style.top = (Math.sin(angle) * 20) + "px"; cat.style.left = (Math.cos(angle) * 200) + "px"; requestAnimationFrame(animate); } requestAnimationFrame(animate);</script>一只猫沿着椭圆轨迹平移。
requestAnimationFrame(func) 这个函数告诉browser,下次刷新屏幕时,执行参数中的函数。浏览器大约每秒刷新60次屏幕。
当然,我们也可以使用 setTimeout() 或 setInterval() 来定时移动,但,requestAnimationFrame() 可以获得更平滑的效果。
注意,坐标后面一定要加上单位(px, em等),否则,设置无效。
14. Exercise: Build a Table
<script> function buildTable(data) { var table = document.createElement("table"); var header = document.createElement("tr"); table.appendChild(header); var keys = Object.keys(data[0]); keys.forEach(function (name) { var th = document.createElement("th"); th.textContent = name; header.appendChild(th); }); data.forEach(function (row) { var tr = document.createElement("tr"); table.appendChild(tr); keys.forEach(function (name) { var value = row[name]; var td = document.createElement("td"); td.textContent = value; if (name == "height") { td.className = "number"; } tr.appendChild(td); }); }); return table; } document.body.appendChild(buildTable(MOUNTAINS));</script>
比在控制台打印table简单多了,因为,不再需要考虑布局问题。
15. Exercise: Elements by Tag Name
function byTagName(node, tagName) { var nodes = []; if (node.nodeType == document.ELEMENT_NODE) { if (node.tagName.toLowerCase() == tagName.toLowerCase()) { nodes.push(node); } for (var i = 0; i < node.childNodes.length; i++) { var eles = byTagName(node.childNodes[i], tagName); nodes = nodes.concat(eles); } } return nodes;}
- Eloquent JavaScript 笔记 十三:DOM
- 《Eloquent JavaScript》笔记--函数;
- Eloquent JavaScript 笔记 三: Functions
- Eloquent JavaScript 笔记 十: Modules
- Eloquent JavaScript 笔记 十七:HTTP
- DOM笔记(十三):JavaScript的继承方式
- 《Eloquent JavaScript》笔记--对象与数组
- 《Eloquent JavaScript》笔记--程序的结构;
- Eloquent JavaScript 笔记 二:Program Structure
- Eloquent JavaScript 笔记 五: High-Order Functions
- Eloquent JavaScript 笔记 四:Objects and Arrays
- Eloquent JavaScript 笔记 七: Electronic Life
- Eloquent JavaScript 笔记 十一:A Programming Language
- Eloquent JavaScript 笔记 十四:Handling Event
- Eloquent JavaScript 笔记 十五:A Platform Game
- Eloquent JavaScript 笔记 十六:Drawing on Canvas
- Eloquent JavaScript 笔记 十九:Node.js
- Eloquent JavaScript 笔记 二十:略有遗憾
- hadoop1.2.1测试环境搭建
- Linux下OpenCV打开USB接口的UVC摄像头的方法以及索引号为什么要是202的原因
- 118. Pascal's Triangle
- 多线程编程
- vue-自定义指令-拖拽
- Eloquent JavaScript 笔记 十三:DOM
- 学习Linux命令(34)
- 【Unity】简单的事件分发系统
- html的下拉列表框
- python爬虫:抓取页面上的超链接
- 观察者模式(Observer)(行为模式)
- ZOJ--1648:Circuit Board(跨立实验线段判交)
- 谈谈我对服务熔断、服务降级的理解
- WEB前端学习笔记-HTML(上)