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;}


原创粉丝点击