0%

学习笔记 2020 11 06

学习笔记 2020-11-06

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

客户端检测

软件与硬件检测

浏览器元数据
  1. Battery Status API

    浏览器可以访问设备电池及充电状态的信息。navigator.getBattery() 方法会返回一个期约实例,解决为一个 BatteryManager 对象,包含四个只读属性:

    • charging 布尔值,表示设备当前是否正接入电源充电,如果设备没有电池,则返回 true
    • chargingTime 整数,表示预计离电池充满还有多少秒。如果电池已充满或设备没有电池,则返回 0 。
    • dischargingTime 整数,表示预计离电量耗尽还有多少秒,如果设备没有电池,则返回 Infinity 。
    • level 浮点数,表示电量百分比。电量完全耗尽返回 0.0 ,电池充满或设备没有电池则返回 1.0

    这个 API 还提供了 4 个事件属性,可用于设置在相应的电池事件发生时调用的回调函数。

    • onchargingchange
    • onchargingtimechange
    • ondischargingtimechange
    • onlevelchange
    navigator.getBattery().then(battery => {
      // 添加充电状态变化时的处理程序
      const chargingChangeHandler = () => console.log('chargingchange');
      battery.onchargingchange = chargingChangeHandler;
      // 或
      battery.addEventListener('chargingchange', chargingChangeHandler);
      // 添加充电时间变化时的处理程序
      const chargingTimeChangeHandler = () => console.log('chargingtimechange');
      battery.onchargingtimechange = chargingTimeChangeHandler;
      // 或
      battery.addEventListener('chargingtimechange', chargingTimeChangeHandler);
      // 添加放电时间变化时的处理程序
      const dischargingTimeChangeHandler = () => console.log('dischargingtimechange');
      battery.ondischargingtimechange = dischargingTimeChangeHandler;
      // 或
      battery.addEventListener('dischargingtimechange', dischargingTimeChangeHandler);
      // 添加电量百分比变化时的处理程序
      const levelChangeHandler = () => console.log('levelchange');
      battery.onlevelchange = levelChangeHandler;
      // 或
      battery.addEventListener('levelchange', levelChangeHandler);
    });
硬件
  1. 处理器核心数

    navigator.hardwareConcurrency 属性返回浏览器支持的逻辑处理器核心数量,包含表示核心数的一个整数值。这个值表示浏览器可以并行执行的最大工作线程数量,不一定是实际的 CPU 核心数。

  2. 设备内存大小、

    navigator.deviceMemory 属性返回设备大致的系统内存大小,包含单位为 GB 的浮点数。

  3. 最大触点数

    navigator.maxTouchPoints 属性返回触摸屏支持的最大关联触点数量,包含一个整数值。

DOM

HTMl 中的每段标记都可以表示为一个节点。DOM 中总共有 12 种节点类型,这些类型都继承一种基本类型。

节点层级

Node 类型

DOM Level 1 描述了名为 Node 的接口,这个接口是所有 DOM 节点类型都必须实现的。 Node 接口在 JavaScript 中被实现为 Node 类型,在除 IE 之外的所有浏览器中都可以直接访问这个类型。在 JavaScript 中,所有节点类型都继承 Node 类型,因此所有类型都共享相同的基本属性和方法。

每个节点都有 nodeType 属性,表示该节点的类型。节点类型由定义在 Node 类型上的 12 个数值常量表示:

  • Node.ELEMENT_NODE ( 1 )
  • Node.ATTRIBUTE_NODE ( 2 )
  • Node.TEXT_NODE ( 3 )
  • Node.CDATA_SECTION_NODE ( 4 )
  • Node.ENTITY_PEFERENCE_NODE ( 5 )
  • Node.ENTITY_NODE ( 6 )
  • Node.PROCESSING_INSTRUCTION_NODE ( 7 )
  • Node.COMMENT_NODE ( 8 )
  • Node.DOCUMENT_NODE ( 9 )
  • Node.DOCUMENT_TYPE_NODE ( 10 )
  • Node.DOCUMENT_FRAGMENT_NODE ( 11 )
  • Node.NOTATION_NODE ( 12 )

节点类型可以通过与这些常量比较来确定:

if (someNode.nodeType == Node.ELEMENT_NODE) {
  console.log('Node is an element.');
}

浏览器并不支持所有节点类型。

  1. nodeName 与 nodeValue

    nodeName 与 nodeValue 保存着有关节点的信息。这两个属性的值完全取决于节点类型。在使用这两个属性前,最好先检测节点类型。

    if (someNode.nodeType == 1) {
      value = someNode.nodeName; // 会显示元素的标签名
    }

    对元素而言,nodeName 始终等于元素的标签名,而 nodeValue 则始终为 null 。

  2. 节点关系

    每个节点有一个 childNodes 属性,其中包含一个 NodeList 的实例。NodeList 是一个类数组对象,用于存储可以按位置存取的有序节点。NodeList 对象独特的地方在于,它其实是一个对 DOM 结构的查询,因此 DOM 结构的变化会自动地在 NodeList 中反映出来。NodeList 是实时的活动对象,而不是第一次访问时所获的内容的快照。

    let firstChild = someNode.childNodes[0];
    let secondChild = someNode.childNodes.item(1);
    let count = someNode.childNodes.length;

    每个节点有一个 parentNode 属性,指向其 DOM 树中的父元素。

    childNodes 列表中的每个节点都是同一列表中其他节点的同胞节点,使用 previousSibling 和 nextSibling 可以在这个列表的节点间导航。列表中第一个节点和最后一个节点的同胞属性为 null 。

    父节点和它的第一个及最后一个子节点也有专门属性:

    • firstChild
    • lastChild

    还有一个方法是 hasChildNodes() ,可以判断节点是否有一个或多个子节点。

    所有节点共享一个关系: ownerDocument 属性是一个指向代表整个文档的文档节点的指针。所有节点都被创建它们的文档所拥有。

现代 JavaScript 教程

任务

  1. 过滤数组中的唯一元素

    定义 arr 为一个数组。

    创建一个函数 unique(arr),该函数返回一个由 arr 中所有唯一元素所组成的数组。

    例如:

    function unique(arr) {
      /* 你的代码 */
    }
    
    let values = ["Hare", "Krishna", "Hare", "Krishna",
      "Krishna", "Krishna", "Hare", "Hare", ":-O"
    ];
    
    alert( unique(values) ); // Hare, Krishna, :-O

    P.S. 这里用到了 string 类型,但其实可以是任何类型的值。

    P.S. 使用 Set 来存储唯一值。

    function unique(arr) {
      return Array.from(new Set(arr));
    }
  2. 过滤字谜

    Anagrams 是具有相同数量相同字母但是顺序不同的单词。

    例如:

    nap - pan
    ear - are - era
    cheaters - hectares - teachers

    写一个函数 aclean(arr),它返回被清除了字谜(anagrams)的数组。

    例如:

    let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
    
    alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era"

    对于所有的字谜(anagram)组,都应该保留其中一个词,但保留的具体是哪一个并不重要。

    function aclean(arr) {
      let set = new Set();
      let newArr = [];
      arr.forEach(item => {
        let tmp = item.toLowerCase().split('').sort().join('');
        if (!set.has(tmp)) {
          newArr.push(item);
          set.add(tmp);
        }
      });
      return newArr;
    }
    // 参考解决方案
    function aclean(arr) {
      let map = new Map();
    
      for (let word of arr) {
        // 将单词 split 成字母,对字母进行排序,之后再 join 回来
        let sorted = word.toLowerCase().split('').sort().join(''); // (*)
        map.set(sorted, word);
      }
    
      return Array.from(map.values());
    }
  3. 迭代键

    我们期望使用 map.keys() 得到一个数组,然后使用特定的方法例如 .push 等,对其进行处理。

    但是运行不了:

    let map = new Map();
    
    map.set("name", "John");
    
    let keys = map.keys();
    
    // Error: keys.push is not a function
    keys.push("more");

    为什么?我们应该如何修改代码让 keys.push 工作?

    map.keys() 返回值是一个可迭代对象,不是数组。

    let map = new Map();
    
    map.set('name', 'John');
    
    let keys = Array.from(map.keys());
    keys.push('more');
    console.log(keys); // ["name", "more"]