• Eloquent JavaScript #11# The Document Object Model


    Notes

    1、js与html

    在html中运行js的3种方式:

    <!--1-->
    <h1>Testing alert</h1>
    <script>alert("hello!");</script>
    
    <!--2-->
    <h1>Testing alert</h1>
    <script src="code/hello.js"></script>
    
    <!--3-->
    <button onclick="alert('Boom!');">DO NOT PRESS</button>

    js在浏览器中解释、运行,浏览器对js具有诸多限制,例如不允许js访问本地文件、不能修改任何和所访问网页无关的东西。因此js仿佛是在一个用钢板围起来的沙盒里活动,而html会被浏览器获取、解析为文档模型(一种特殊的数据结构),浏览器依据这个文档模型来渲染画面,这个文档模型是在js沙盒范围内的东西,js可以自由地读取和修改它。

    2、DOM

    html被解析为一种叫文档对象模型(Document Object Model)的东西,浏览器就是依据它来进行页面渲染的。全局绑定document是我们访问这些文档对象的入口,它的documentElement属性指向html标签所对应的对象,还有head和body属性,分别指向各种对应的对象。DOM是一种树形结构,document.documentElement即是根节点。再比如说document.body也是一个节点,它的孩子可以是元素节点,也可能是一段文本或者评论节点。

    每个DOM节点对象都有一个nodeType属性,该属性包含标识节点类型的编码(数字)。元素的编码为Node.ELEMENT_NODE,该常量的真实值为数字1。 表示文档中一段文本的文本节点编码是3(Node.TEXT_NODE)。注释的编码为8(Node.COMMENT_NODE)。

    DOM的可视化:

    叶子是文本节点,箭头则表明了父子关系。

    DOM并非是专门为js设计的,它最开始是想成为xml一样中立的东西。所以它本身和js集成得并不是特别好。一个典型的例子是childNodes属性,它类似array,却又不是真正的array,而是NodeList类型,所以它并不支持slice和map。再比如,DOM并没有提供一次性创建节点并添加子节点、属性的方法,而是必须要首先创建它,然后一个一个地添加子节点和属性。

    当然,这些缺点都不是致命的,我们显然可以自定义函数来封装一些操作,以便更好地和DOM交互。(已经有许多现成的库做这件事情)

    3、在DOM树中移动

    如果属性对应的节点不存在,则为null。

    有个与childNodes类似的children属性,不过children属性只包含元素子女(编码为1的节点),当你对文本节点不感兴趣的时候这通常是有用的。可以通过递归来遍历DOM树,因为childNodes不是真正的数组,因此只能通过常规的循环来遍历它,不能用for/of循环。

    <!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>
        
        <script type="text/javascript">
            // 递归,深度优先搜索DOM树
            function talksAbout(node, string) {
              if (node.nodeType == Node.ELEMENT_NODE) {
                for (let i = 0; i < node.childNodes.length; i++) {
                  if (talksAbout(node.childNodes[i], string)) {
                    return true;
                  }
                }
                return false;
              } else if (node.nodeType == Node.TEXT_NODE) {
                  // text节点的nodeValue储存着显示文本
                return node.nodeValue.indexOf(string) > -1;
              }
            }
            
            console.log(talksAbout(document.body, "book"));
            // → true
        </script>
      </body>
    </html>

    4、在DOM中寻找元素

    根据子元素的标签类型:

    let link = document.body.getElementsByTagName("a")[0];
    console.log(link.href);

    根据id:

    <p>My ostrich Gertrude:</p>
    <p><img id="gertrude" src="img/ostrich.png"></p>
    
    <script>
      let ostrich = document.getElementById("gertrude");
      console.log(ostrich.src);
    </script>

    类似的还有getElementsByClassName

    5、改变Document 

    remove,移除元素:

            <p>One</p>
            <p>Two</p>
            <p>Three</p>
            
            <script>
              let paragraphs = document.body.getElementsByTagName("p");
              paragraphs[0].remove();
              // two/three
            </script>

    appendChild,用于插入元素到某个父元素的孩子元素的最后一位:

            <p>One</p>
            <p>Two</p>
            <p>Three</p>
            
            <script>
              let paragraphs = document.body.getElementsByTagName("p");
              document.body.appendChild(paragraphs[1]);
              // One/Three/Two
            </script>

    insertBefore,一个节点只能存在于文档中的一个地方,插入段落3会导致它在原来的位置移除,再被插入到相应位置:

            <p>One</p>
            <p>Two</p>
            <p>Three</p>
            
            <script>
              let paragraphs = document.body.getElementsByTagName("p");
              document.body.insertBefore(paragraphs[2], paragraphs[0]);
              // insertBefore插入到某某之前,这里是把three插到one之前
              // Three/One/Two
            </script>

    【All operations that insert a node somewhere will, as a side effect, cause it to be removed from its current position (if it has one).】

    类似的还有replaceChild :

            <p>One</p>
            <p>Two</p>
            <p>Three</p>
            
            <script>
              let paragraphs = document.body.getElementsByTagName("p");
              document.body.replaceChild(paragraphs[0], paragraphs[1]);
              // 第一个作为新元素,第二个是被淘汰的旧元素
              // One/Three
            </script>

    6、创建节点

    点击按钮后将所有图片替换成其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() {
                    let images = document.body.getElementsByTagName("img");
                    // image的长度内容都是随着document动态变化的
                    for(let i = images.length - 1; i >= 0; i--) {
                        // 所以才要从最后一个元素开始替换,这样移除元素
                        // 的时候才不会影响到访问其余兄弟元素的下标值
                        let image = images[i];
                        if(image.alt) {
                            let text = document.createTextNode(image.alt);
                            image.parentNode.replaceChild(text, image);
                            // 必须获得父元素的引用才能替换孩子元素
                        }
                    }
    
                    // 主题无关↓:从DOM获取一个静态的数组Array.from
                    let arrayish = {
                        0: "one",
                        1: "two",
                        length: 2
                    };
                    let array = Array.from(arrayish);
                    console.log(array.map(s => s.toUpperCase()));
                    // → ["ONE", "TWO"]
                }
            </script>

    document.createElement(tag name)返回一个相应类型的空元素,示例如下:

            <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>
                // 创建一个type类型的空元素
                // children是它的孩子节点,
                // 该函数可以接受n个参数
                function elt(type, ...children) {
                    let node = document.createElement(type);
                    for(let child of children) {
                        if(typeof child != "string") node.appendChild(child);
                        else node.appendChild(document.createTextNode(child));
                        // 如果是string类型就将其转换为一个文本节点再插入
                    }
                    return node; // 返回这个创建好的节点
                }
    
                document.getElementById("quote").appendChild(
                    elt("footer"/*type*/, "—"/*child-0*/,
                        elt("strong", "Karl Popper")/*child-1*/,
                        ", preface to the second editon of "/*child-2*/,
                        elt("em", "The Open Society and Its Enemies")/*child-3*/,
                        ", 1950"/*child-4*/));
            </script>

    7、html元素属性

    不仅可以在js中设置、获取元素的标准属性,还可以用getAttribute和setAttribute方法设置自定义的属性

    (诸如alt、href等标准属性,直接node.alt就可以访问)

            <p data-classified="secret">The launch code is 00000000.</p>
            <p data-classified="unclassified">I have two feet.</p>
    
            <script>
                let paras = document.body.getElementsByTagName("p");
                for(let para of Array.from(paras)) { // 将动态的nodelist转化为静态的数组
                    if(para.getAttribute("data-classified") == "secret") {
                        // 推荐用前缀修饰自定义属性,这样就不会与标准属性冲突了
                        para.remove();
                    }
                }
            </script>

    getAttribute和setAttribute和属于通用方法,也可以用来访问标准属性。

    8、布局

    访问元素所占空间大小:

            <p style="border: 3px solid red">
                I'm boxed in
            </p>
    
            <script>
                let para = document.body.getElementsByTagName("p")[0];
                console.log("clientHeight:", para.clientHeight); // 21 不包括边框
                console.log("offsetHeight:", para.offsetHeight); // 27 加上边框 2*3px        
            </script>

    访问元素位置最有效的方法:

            <p style="border: 3px solid red">
                I'm boxed in
            </p>
    
            <script>
                let para = document.body.getElementsByTagName("p")[0];
                let boundingClientRect = para.getBoundingClientRect();
                // html元素左上角相对于浏览器显示屏的准确位置
                console.log("top:", boundingClientRect.top); // 16
                console.log("buttom:", boundingClientRect.buttom); // undefined
                console.log("left:", boundingClientRect.left); // 8
                console.log("right:", boundingClientRect.right);    // 556
                
                // 相对于document的位置,需要加上滚动条位置
                console.log("pageXOffset:", window.pageXOffset);
                console.log("pageYOffset:", window.pageYOffset);
            </script>

    进行文档布局需要做很多工作。为了速度,浏览器引擎不会在每次更改文档时立即重新布局文档,而是尽可能长时间的等待。当更改文档的JavaScript程序完成运行时,浏览器才必须计算新的布局以将更改的文档绘制到屏幕上。当程序通过诸如offsetHeight或者getBoundingClientRect读取某个东西的大小或者位置时,提供正确的信息也需要计算布局。在读取DOM布局信息和更改DOM之间反复交替的程序会强制执行大量布局计算,因此运行速度非常慢↓

            <script>
                function time(name, action) {
                    let start = Date.now(); // Current time in milliseconds
                    action();
                    console.log(name, "took", Date.now() - start, "ms");
                }
    
                time("naive", () => {
                    let target = document.getElementById("one");
                    while(target.offsetWidth < 2000) {
                        target.appendChild(document.createTextNode("X"));
                    }
                });
                // → naive took 32 ms
    
                time("clever", function() {
                    let target = document.getElementById("two");
                    target.appendChild(document.createTextNode("XXXXX"));
                    let total = Math.ceil(2000 / (target.offsetWidth / 5));
                    target.firstChild.nodeValue = "X".repeat(total);
                });
                // → clever took 1 ms
            </script>

    9、style

            <p id="para" style="color: purple">
                Nice text
            </p>
    
            <script>
                let para = document.getElementById("para");
                console.log(para.style.color);
                para.style.color = "magenta";
                para.style['color'] = "red";
                para.style.fontFamily = "cursive";
            </script>

    10、CSS选择器

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

    querySelectorAll与querySelector后者只返回一个元素(第一个)。

    11、动画

    <p>_</p>
    <p>_</p>
    <p>_</p>
    <p>_</p>
    <p>_</p>
    <p style="text-align: center">
      <img src="img/cat.png" style="position: relative">
    </p>
    <script>
      let cat = document.querySelector("img");
      let angle = Math.PI / 2;
      function animate(time, lastTime) {
        if (lastTime != null) {
          angle += (time - lastTime) * 0.001;
        }
        cat.style.top = (Math.sin(angle) * 20) + "px";
        cat.style.left = (Math.cos(angle) * 200) + "px";
        requestAnimationFrame(newTime => animate(newTime, time));
      }
      debugger;
      requestAnimationFrame(animate);
    </script>

    window.requestAnimationFrame

    Exercises

    ① Build a table

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="UTF-8">
            <title></title>
        </head>
    
        <body>
            <h1>Mountains</h1>
            
            <div id="mountains"></div>
            
            <script>
                 const MOUNTAINS = [
                    {name: "Kilimanjaro", height: 5895, place: "Tanzania"},
                    {name: "Everest", height: 8848, place: "Nepal"},
                    {name: "Mount Fuji", height: 3776, place: "Japan"},
                    {name: "Vaalserberg", height: 323, place: "Netherlands"},
                    {name: "Denali", height: 6168, place: "United States"},
                    {name: "Popocatepetl", height: 5465, place: "Mexico"},
                    {name: "Mont Blanc", height: 4808, place: "Italy/France"}
                  ];
            
                  // Your code here
                function elt(type, ...children) {
                    let node = document.createElement(type);
                    for(let child of children) {            
                        if(typeof child != "string" && typeof child != "number") {
                            node.appendChild(child);
                        } else node.appendChild(document.createTextNode(child));
                        // 如果是string类型就将其转换为一个文本节点再插入
                    }
                    return node; // 返回这个创建好的节点
                }
                
                  let table = document.createElement("table");    
                  let ths = document.createElement("tr");
                  ths.appendChild(elt("th", "name"));
                  ths.appendChild(elt("th", "height"));
                  ths.appendChild(elt("th", "place"));
                  table.appendChild(ths);
                  for (let mountain of MOUNTAINS) {
                      let tds = document.createElement("tr");
                      tds.appendChild(elt("td", mountain['name']));
                      let tdHeight = elt("td", mountain['height']);
                      tdHeight.style.textAlign = "right";
                      tds.appendChild(tdHeight);
                      tds.appendChild(elt("td", mountain['place']));
                      table.appendChild(tds);
                  }
            
                  document.getElementById("mountains").appendChild(table);
            </script>
        </body>
    
    </html>

    ————--- -- -  ---- —— ——- -- --  - -- -  --   - -- - - - -

    ② Elements by tag name

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="UTF-8">
            <title></title>
        </head>
    
        <body>
            <h1>Heading with a <span>span</span> element.</h1>
            <p>A paragraph with <span>one</span>, <span>two</span> spans.
            </p>
    
            <script>
                function byTagName(node, tagName) {
                    // Your code here.
                    let result = [];
                    const byTagNameHelper = (node, tagName) => {
                        for (let child of Array.from(node.children)) {
                            if (child.nodeName == tagName.toUpperCase()) {
                                result.push(child);
                            }
                            byTagNameHelper(child, tagName);
                        }
                    };
                    byTagNameHelper(node, tagName);
                    return result;
                }
    
                console.log(byTagName(document.body, "h1").length);
                // → 1
                console.log(byTagName(document.body, "span").length);
                // → 3
                let para = document.querySelector("p");
                console.log(byTagName(para, "span").length);
                // → 2
            </script>
        </body>
    </html>

    ————--- -- -  ---- —— ——- -- --  - -- -  --   - -- - - - -

    ③ The cat’s hat

     略

  • 相关阅读:
    URL统一资源定位符的组成
    B/S与C/S的比较
    springmvc在处理请求过程中出现异常信息交由异常处理器进行处理,自定义异常处理器可以实现一个系统的异常处理逻辑。为了区别不同的异常通常根据异常类型自定义异常类,这里我们创建一个自定义系统异常,如果controller、service、dao抛出此类异常说明是系统预期处理的异常信息。
    springmvc中Controller方法的返回值
    springmvc中@RequestMapping的使用
    构造完全图---最小生成树
    poj
    叶子的颜色---经典树上dp
    花神游历各国
    CodeForces
  • 原文地址:https://www.cnblogs.com/xkxf/p/9607884.html
Copyright © 2020-2023  润新知