0%

学习笔记 2020 11 12

学习笔记 2020-11-12

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

DOM 扩展

HTML5

插入标记
  1. 内存与性能问题

    如果被移除的子树元素中之前有关联的事件处理程序或其他 JavaScript 对象。那它们之间的绑定关系会滞留在内存中。在使用 innerHTML 、outerHTML 和 insertAdjacentHTML() 之前,最好手动删除要被替换的元素上关联的事件处理程序和 JavaScript 对象。

    HTML 解析器会解析设置给 innerHTML 的值,解析器在浏览器中是底层代码,比 JavaScript 快得多。

  2. 跨站点脚本

    innerHTML 不会执行自己创建的 <script> 标签,但仍然向恶意用户暴露了很大的攻击面。可以通过它创建元素并执行 onclick 之类的属性。

scrollIntoView()

scrollIntoView() 方法存在于所有 HTML 元素上,可以滚动浏览器窗口或容器元素以便包含元素进入视口。方法参数如下:

  • alignToTop ,布尔值。
    • true 表示窗口滚动后元素顶部与视口顶部对齐。
    • false 表示窗口滚动后元素底部与视口底部对齐。
  • scrollIntoViewOptions,可选对象。
    • behavior,定义过渡动画,可取的值为 smooth 和 auto,默认为 auto 。
    • block,定义垂直方向的对齐,可取的值为 start、center、end 和 nearest,默认为 start。
    • inline,定义水平方向的对齐,可取的值为 start、center、end 和 nearest,默认为 nearest。
  • 不传参数等同于第一个参数为 true。

专有扩展

children 属性

IE9 之前的版本与其他浏览器在处理空白文本节点上的差异导致了 children 属性的出现。children 属性是一个 HTMLCollection ,只包含元素的 Element 类型的子节点。

contains() 方法

IE 首先引入了 contains() 方法,在要搜索的祖先元素上调用,参数是待确定的目标节点。

使用 DOM Level 3 的 compareDocumentPosition() 方法也可以确定节点间的关系。这个方法会返回表示两个节点关系的位掩码。

掩 码 节点关系
0x1 断开(传入的节点不在文档中)
0x2 领先(传入的节点在 DOM 树中位于参考节点之前)
0x4 随后(传入的节点在 DOM 树中位于参考节点之后)
0x8 包含(传入的节点是参考节点的祖先)
0x10 被包含(传入的节点是参考节点的后代)
插入标记
  1. innerText

    该属性对应元素中包含的所有文本内容,无论文本在子树中哪个层级。在用于读取值时,innerText 会按照深度优先的顺序将子树中所有文本节点的值拼接起来。在用于写入值时,innerText 会移除元素的所有后代并插入一个包含该值的文本节点。

  2. outerText

    类似于 innerText,只不过作用范围包含调用它的节点。在读取值时,两者表现相似。在写入值时,outerText 不止会移除所有后代节点,还会替换整个元素。

DOM2 和 DOM3

DOM1 主要定义了 HTML 和 XML 文档的底层结构。DOM2 和 DOM3 在这些结构之上加入更多交互能力,提供了更高级的 XML 特性。

  • DOM Core::在 DOM1 核心部分的基础上,为节点增加方法和属性。
  • DOM Views:定义基于样式信息的不同试图。
  • DOM Events:定义通过事件实现 DOM 文档交互。
  • DOM Style:定义以编程方式访问和修改 CSS 样式的接口。
  • DOM Traversal and Range:新增遍历 DOM 文档及选择文档内容的接口。
  • DOM HTML:在 DOM1 HTML 部分的基础上,增加属性、方法和新接口。
  • DOM Mutation Observers:定义基于 DOM 变化触发回调的接口。这个模块是 DOM4 级模块,用于取代 Mutation Events。

DOM 的演进

DOM2 和 DOM3 Core 模块的目标是扩展 DOM API,满足 XML 的所有需求并提供更好的错误处理和特性检测。

XML 命名空间

现代 JavaScript 教程

递归和堆栈

任务

  1. 对数字求和到给定值

    编写一个函数 sumTo(n) 计算 1 + 2 + ... + n 的和。

    举个例子:

    sumTo(1) = 1
    sumTo(2) = 2 + 1 = 3
    sumTo(3) = 3 + 2 + 1 = 6
    sumTo(4) = 4 + 3 + 2 + 1 = 10
    ...
    sumTo(100) = 100 + 99 + ... + 2 + 1 = 5050

    用三种方式实现:

    1. 使用循环。
    2. 使用递归,对 n > 1 执行 sumTo(n) = n + sumTo(n-1)
    3. 使用 等差数列 求和公式.

    结果示例:

    function sumTo(n) { /*... 你的代码 ... */ }
    
    alert( sumTo(100) ); // 5050

    P.S. 哪种解决方式最快?哪种最慢?为什么?

    P.P.S. 我们可以使用递归来计算 sumTo(100000) 吗?

    function sumTo(n) {
      let sum = 0;
      while (n) {
        sum += n;
        n--;
      }
      return sum;
    }
    function sumTo(n) {
      if (n === 0) return n;
      return n + sumTo(n - 1);
    }
    function sumTo(n) {
      return ((n + 1) * n) / 2;
    }
  2. 计算阶乘

    自然数的 阶乘 是指,一个数乘以 数字减去 1,然后乘以 数字减去 2,以此类推直到乘以 1n 的阶乘被记作 n!

    我们可以将阶乘的定义写成这样:

    n! = n * (n - 1) * (n - 2) * ...*1

    不同 n 的阶乘的值:

    1! = 1
    2! = 2 * 1 = 2
    3! = 3 * 2 * 1 = 6
    4! = 4 * 3 * 2 * 1 = 24
    5! = 5 * 4 * 3 * 2 * 1 = 120

    任务是编写一个函数 factorial(n) 使用递归调用计算 n!

    alert( factorial(5) ); // 120

    P.S. 提示:n! 可以被写成 n * (n-1)!,比如 3! = 3*2! = 3*2*1! = 6

    function factorial(n) {
      if (n === 1) return n;
      return n * factorial(n - 1);
    }
  3. 斐波那契数

    斐波那契数 序列有这样的公式: Fn = Fn-1 + Fn-2。换句话说,下一个数字是前两个数字的和。

    前两个数字是 1,然后是 2(1+1),然后 3(1+2)5(2+3) 等:1, 1, 2, 3, 5, 8, 13, 21...

    斐波那契数与 黄金比例 以及我们周围的许多自然现象有关。

    编写一个函数 fib(n) 返回第 n 个斐波那契数。

    工作示例:

    function fib(n) { /* 你的代码 */ }
    
    alert(fib(3)); // 2
    alert(fib(7)); // 13
    alert(fib(77)); // 5527939700884757

    P.S. 函数运行速度要快,对 fib(77) 的调用不应该超过几分之一秒。

    // 参考解法
    function fib(n) {
      let a = 1;
      let b = 1;
      for (let i = 3; i <= n; i++) {
        let c = a + b;
        a = b;
        b = c;
      }
      return b;
    }
  4. 输出一个单链表

    假设我们有一个单链表(在 递归和堆栈 那章有讲过):

    let list = {
      value: 1,
      next: {
        value: 2,
        next: {
          value: 3,
          next: {
            value: 4,
            next: null
          }
        }
      }
    };

    编写一个可以逐个输出链表元素的函数 printList(list)

    使用两种方式实现:循环和递归。

    哪个更好:用递归还是不用递归的?

    function printList(list) {
      let tmp = list;
      while (tmp) {
        console.log(tmp.value);
        tmp = tmp.next;
      }
    }
    function printList(list) {
      if (list) {
        console.log(list.value);
        printList(list.next);
      }
    }
  5. 反向输出单链表

    反向输出前一个任务 输出一个单链表 中的单链表。

    使用两种解法:循环和递归。

    function printList(list) {
      if (list) {
        printList(list.next);
        console.log(list.value);
      }
    }
    function printList(list) {
      let tmp = list;
      const arr = [];
      while (tmp) {
        arr.push(tmp.value);
        tmp = tmp.next;
      }
      while (arr.length) {
        console.log(arr.pop());
      }
    }