0%

学习笔记 2020 11 09

学习笔记 2020-11-09

JavaScript 高级程序设计(第4版) 阅读记录

DOM

节点层级

DocumentFragment 类型

在所有节点类型中,DocumentFragment 类型是唯一一个在标记中没有对应表示的类型。DOM 将文档片段定义为轻量级文档,能够包含和操作节点,却没有完整文档那样额外的消耗。

  • nodeType 等于 11 ;
  • nodeName 值为 “#document-fragment”;
  • nodeValue 值为 null ;
  • parentNode 值为 null ;
  • 子节点可以是 Element、ProcessingInstruction、Comment、Text、CDATASection 或 EntityReference 。

不能直接把文档片段添加到文档。相反,文档片段的作用是充当其他要被添加到文档的节点的仓库。

使用 document.createDocumentFragment() 方法创建文档片段。

文档片段从 Node 类型继承了所有文档类型具备的可以执行 DOM 操作的方法。如果文档中的一个节点被添加到一个文档片段,则该节点会从文档树中移除,不会再被浏览器渲染。添加到文档片段的新节点同样不属于文档树,不会被浏览器渲染。可以通过 appendChild() 或 insertBefore() 方法将文档片段的内容添加到文档。在把文档片段作为参数传给这些方法时,这个文档片段的所有子节点会被添加到文档中相应的位置。文档片段本身永远不会被添加到文档树。

Attr 类型

元素数据在 DOM 中通过 Attr 类型表示。 Attr 类型构造函数和原型在所有浏览器中都可以直接访问。技术上说,属性是存在于元素 attributes 属性中的节点。Attr 节点具有以下特征:

  • nodeType 等于 2 ;
  • nodeName 值为属性名;
  • nodeValue 值为属性值;
  • parentNode 值为 null ;
  • 在 HTML 中不支持子节点;
  • 在 XML 中子节点可以是 Text 或 EntityReference。

属性节点不被认为是 DOM 文档树的一部分。 Attr 节点很少直接被引用。

Attr 对象上有 3 个属性:name 、 value 和 specified 。其中, name 包含属性名 ( 与 nodeName 一样 ),value 包含属性值 ( 与 nodeValue一样 ),而 specified 是一个布尔值,表示属性使用的是默认值还是被指定的值。

可以使用 document.createAttribute() 方法创建新的 Attr 节点,参数为属性名。

DOM 编程

动态脚本

动态脚本指的是动态创建 script 标签,嵌入代码。引入外部代码则是设置标签的 src 属性,嵌入源码则是创建文本节点。

通过 innerHTML 属性创建的 script 元素永不执行。

动态样式

动态样式指的是页面初始加载时不存在,后续添加的样式。例如动态创建 link 标签,引入外部样式表。或是动态创建 style 标签来写入样式。

通过外部文件加载样式是一个异步过程。

操作表格

通过 DOM 编程创建 table 元素,需要涉及大量标签。

// 创建表格
let table = document.createElement('table');
table.border = 1;
table.width = '100%';
// 创建表体
let tbody = document.createElement('tbody');
table.appendChild(tbody);
// 创建第一行
let row1 = document.createElement('tr');
tbody.appendChild(row1);
let cell1_1 = document.createElement('td');
cell1_1.appendChild(document.createTextNode('Cell 1,1'));
row1.appendChild(cell1_1);
let cell2_1 = document.createElement('td');
cell2_1.appendChild(document.createTextNode('Cell 2,1'));
row1.appendChild(cell2_1);
// 创建第二行
let row2 = document.createElement('tr');
tbody.appendChild(row2);
let cell1_2 = document.createElement('td');
cell1_2.appendChild(document.createTextNode('Cell 1,2'));
row2.appendChild(cell1_2);
let cell2_2 = document.createElement('td');
cell2_2.appendChild(document.createTextNode('Cell 2,2'));
row2.appendChild(cell2_2);
// 把表格添加到文档主体
document.body.appendChild(table);

image-20201109082609637

因此,DOM 给表格添加了一些属性和方法:

<table> 元素的属性和方法:

  • caption,指向 <caption> 元素的指针;
  • tBodies,包含 <tbody> 元素的 HTMLCollection;
  • tFoot,指向 <tfoot>元素(如果存在);
  • tHead,指向 <thead> 元素(如果存在);
  • rows,包含表示所有行的 HTMLCollection;
  • createTHead(),创建 <thead>元素,放到表格中,返回引用;
  • createTFoot(),创建 <tfoot> 元素,放到表格中,返回引用;
  • createCaption(),创建 <caption> 元素,放到表格中,返回引用;
  • deleteTHead(),删除 <thead> 元素;
  • deleteTFoot(),删除 <tfoot> 元素;
  • deleteCaption(),删除 <caption> 元素;
  • deleteRow(pos),删除给定位置的行;
  • insertRow(pos),在行集合中给定位置插入一行。

<tbody> 元素添加了以下属性和方法:

  • rows,包含 <tbody> 元素中所有行的 HTMLCollection;
  • deleteRow(pos),删除给定位置的行;
  • insertRow(pos),在行集合中给定位置插入一行,返回该行的引用。

<tr> 元素添加了以下属性和方法:

  • cells,包含 <tr> 元素所有表元的 HTMLCollection;
  • deleteCell(pos),删除给定位置的表元;
  • insertCell(pos),在表元集合给定位置插入一个表元,返回该表元的引用。
// 创建表格
let table = document.createElement('table');
table.border = 1;
table.width = '100%';
// 创建表体
let tbody = document.createElement('tbody');
table.appendChild(tbody);
// 创建第一行
tbody.insertRow(0);
tbody.rows[0].insertCell(0);
tbody.rows[0].cells[0].appendChild(document.createTextNode('Cell 1,1'));
tbody.rows[0].insertCell(1);
tbody.rows[0].cells[1].appendChild(document.createTextNode('Cell 2,1'));
// 创建第二行
tbody.insertRow(1);
tbody.rows[1].insertCell(0);
tbody.rows[1].cells[0].appendChild(document.createTextNode('Cell 1,2'));
tbody.rows[1].insertCell(1);
tbody.rows[1].cells[1].appendChild(document.createTextNode('Cell 2,2'));
// 把表格添加到文档主体
document.body.appendChild(table);
使用 NodeList

理解 NodeList 对象和相关的 NamedNodeMap、HTMLCollection,是理解 DOM 编程的关键。这 3 个集合类型都是实时的。

NodeList 是基于 DOM 文档的实时查询。

MutationObserver 接口

该接口可以在 DOM 被修改时异步执行回调。使用 MutationObserver 可以观察整个文档、 DOM 树的一部分,或某个元素,还可以观察元素属性、子节点、文本或前三者任意组合的变化。

基本用法

MutationObserver 的实例要通过调用 MutationObserver 构造函数并传入一个回调参数来创建:

let observer = new MutationObserver(() => console.log('DOM was mutated!'));
  1. observe() 方法

    新创建的实例不会关联 DOM 。使用 observe() 方法把这个 observer 与 DOM 关联,接收两个必需参数:要观察其变化的 DOM 节点,一个 MutationObserverInit 对象。

    MutationObserverInit 对象用于控制观察哪些方面的变化,是一个键/值对形式配置选项的字典。

    let observer = new MutationObserver(() => console.log('<body> attributes changed'));
    observer.observe(document.body, { attributes: true });
    document.body.className = 'foo';
    console.log('Changed body class');

    该代码会创建一个观察者来观察 body 元素上的属性变化。

现代 JavaScript 教程

任务

  1. 解构赋值

    我们有一个对象:

    let user = {
      name: "John",
      years: 30
    };

    写一个解构赋值语句使得:

    • name 属性赋值给变量 name
    • years 属性赋值给变量 age
    • isAdmin 属性赋值给变量 isAdmin(如果属性缺失则取默认值 false)。

    下面是赋值完成后的值的情况:

    let user = { name: "John", years: 30 };
    
    // 等号左侧是你的代码
    // ... = user
    
    alert( name ); // John
    alert( age ); // 30
    alert( isAdmin ); // false
    let { name, years: age, isAdmin = false } = user;
  2. 最高薪资

    这儿有一个 salaries 对象:

    let salaries = {
      "John": 100,
      "Pete": 300,
      "Mary": 250
    };

    新建一个函数 topSalary(salaries),返回收入最高的人的姓名。

    • 如果 salaries 是空的,函数应该返回 null
    • 如果有多个收入最高的人,返回其中任意一个即可。

    P.S. 使用 Object.entries 和解构语法来遍历键/值对。

    function topSalary(salaries) {
      let value = Object.entries(salaries);
      if (value.length === 0) return null;
      let max = 0;
      let maxName = '';
      for (let [name, salary] of value) {
        if (salary > max) {
          maxName = name;
          max = salary;
        }
      }
      return maxName;
    }