0%

学习笔记 2020 11 03

学习笔记 2020-11-03

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

BOM

已经成为客户端标识浏览器的标准。只要浏览器启用 JavaScript, navigator 对象就一定存在。

属性/方法 说明
activeVrDisplays 返回数组,包含 ispresenting 属性为 true 的VRDisplay 实例。
appCodeName 即使在非 Mozilla 浏览器中也会返回 Mozilla 。
appName 浏览器全名。
appVersion 浏览器版本。通常与实际的浏览器版本不一致。
battery 返回暴露 Battery Status API 的 BatteryManager 对象。
buildId 浏览器的构建编号。
connection 返回暴露 Network Information API 的 NetworkInformation 对象。
cookieEnabled 返回布尔值,表示是否启用了 cookie 。
credentials 返回暴露 Credentials Management API 的 CredentialsContainer 对象。
deviceMemory 返回单位为 GB 的设备内存容量。
doNotTrack 返回用户的 不跟踪 设置。
gelocation 返回暴露 Geolocation API 的 Geolocation 对象。
getVRDisplays() 返回数组,包含可用的每个 VRDisplay 实例。
getUserMedia() 返回与可用媒体设备硬件关联的流。
hardwareConcurrency 返回设备的处理器核心数量。
javaEnabled 返回布尔值,表示浏览器是否启用了 Java 。
language 返回浏览器的主语言。
languages 返回浏览器偏好的语言数组。
locks 返回暴露 Web Locks API 的 LockManager 对象。
mediaCapabilities 返回暴露 Media Capabilities API 的 MediaCapabilities 对象。
mediaDevices 返回可用的媒体设备。
maxTouchPoints 返回设备触摸屏支持的最大触点数。
mimeTypes 返回浏览器中注册的 MIME 类型数组。
onLine 返回布尔值,表示浏览器是否联网。
oscpu 返回浏览器运行设备的操作系统和 CPU 。
permissions 返回暴露 Permissions API 的 Persimissions 对象。
platform 抱回浏览器运行的系统平台。
plugins 返回浏览器安装的插件数组。在 IE 中,这个数组包含页面中所有 <embed> 元素。
product 返回产品名称。
productSub 返回产品的额外信息。
registerProtocolHandler() 将一个网站注册为特定协议的处理程序。
requestMediaKeySystemAccess() 返回一个期约,解决为 MediaKeySystemAccess 对象。
sendBeacon() 异步传输一些小数据。
serviceWorker 返回用来与 ServiceWorker 实例交互的 ServiceWorkerContainer 。
share() 返回当前平台的原生共享机制。
storage 返回暴露 Storage API 的 StorageManager 对象。
userAgent 返回浏览器的用户代理字符串。
vendor 返回浏览器的厂商名称。
vendorSub 返回浏览器厂商的更多信息。
vibrate() 触发设备振动。
webdriver 返回浏览器当前是否被自动化程序控制。
  1. 检测插件

    除 IE10 及更低版本外的浏览器,都可以通过 plugins 数组来检测浏览器是否安装了某个插件。这个数组的每一项都包含:

    • name:插件名称。
    • description:插件介绍。
    • filename:插件的文件名。
    • length:由当前插件处理的 MIME 类型数量。

    旧版本 IE 中检测插件使用 ActiveXObject

  2. 注册处理程序

    现代浏览器支持 navigator 上的 registerProtocolHandler() 方法。可以把一个网站注册为处理某种特定类型信息用用程序。接收三个参数,要处理的协议、处理该协议的 URL 、以及应用名称。

    navigator.registerProtocolHandler(
      'mailto',
      'http://www.somemailclient.com?cmd=%s',
      'Some Mail Client'
    );

screen 对象

这个对象中保存的是客户端能力信息,也就是浏览器窗口外面的客户端显示器的信息。

属性 说明
availHeight 屏幕像素高度减去系统组件高度( 只读 )
availLeft 没有被系统组件占用的屏幕的最左侧像素 ( 只读 )
availTop 没有被系统组件占用的屏幕的最顶端像素 ( 只读 )
availWidth 屏幕像素宽度减去系统组件宽度 ( 只读 )
colorDepth 表示屏幕颜色的位数;多数系统是 32 ( 只读 )
height 屏幕像素高度。
left 当前屏幕左边的像素距离。
pixelDepth 屏幕的位深 ( 只读 )
top 当前屏幕顶端的像素距离。
width 屏幕像素宽度。
orientation 返回 Screen Orientation API 中屏幕的朝向。

history 对象

history 对象表示当前窗口首次使用以来用户的导航历史记录。因为 history 是 window 的属性,所以每个 window 都有自己的 history 对象。出于安全考虑,这个对象不会暴露用户访问过的 URL,但可以通过它在不知道实际 URL 的情况下前进和后退。

  1. 导航

    go() 方法可以在用户历史记录中沿任何方向导航,可以前进也可以后退。这个方法只接收一个参数,可以是一个整数,表示前进或后退多少步。

    在旧版本的一些浏览器中,go() 方法的参数也可以是一个字符串,浏览器会导航到历史中包含该字符串的第一个位置或什么也不做。

    go() 有两个简写方法:back()forward()

    history 对象还有一个 length 属性,表示历史记录中有多个条目。

  2. 历史状态管理

    hashchange 会在页面 URL 的散列变化时被触发,开发者可以在此时执行某些操作。状态管理 API 可以让开发者改变浏览器 URL 而不会加载新页面。可以使用 history.pushState() 方法。接收三个参数,一个 state 对象,一个新状态 的标题和一个可选的相对 URL 。

    pushState 方法执行后,状态信息就会被推到历史记录中,浏览器地址栏也会改变以反映新的相对 URL 。浏览器页不会向服务器发送请求。此时单击后退按钮,就会触发 window 对象上的 popstate 事件。popstate 事件的事件对象有一个 state 属性,包含通过 pushState 第一个参数传入的 state 对象。

    基于这个状态,应该把页面重置为状态对象所表示的状态。浏览器不会自动做这件事。页面初次加载时没有状态。

    可以通过 history.state 获取当前的状态对象,也可以使用 replaceState() 并传入与 pushState() 同样的前两个参数来更新状态。此时只会覆盖当前状态。

    传给 pushState()replaceState() 的 state 对象应该只包含可被序列化的信息。因此, DOM 元素之类并不适合放到状态对象里保存。

客户端检测

因为浏览器实现不一,所以 Web 开发者需要面对跨平台浏览器开发的统一性问题,需要使用各种方法来检测客户端,以实现功能。

能力检测

能力检测指在 JS 运行时使用一套简单的逻辑检测,测试浏览器是否支持某种特性。只需要检测自己关心的能力是否存在即可。

例如, IE5 之前的版本没有 document.getElementById() 这个方法,可以通过 document.all 属性实现相同的功能。

function getElement(id) {
  if (document.getElementById) {
    return document.getElementById(id);
  } else if (document.all) {
    return document.all[id];
  } else {
    throw new Error('No way to retrieve element!');
  }
}
  1. 安全能力检测

    能力检测最有效的场景是检测能力是否存在的同时,验证其是否能够展现出预期的行为。

    比如想要检测 sort 方法,不仅仅是检测这个方法是否存在,而是要检测这个属性是不是一个函数。

  2. 基于能力检测进行浏览器分析

    恰当地使用能力检测可以精准地分析运行代码的浏览器。使用能力检测而非用户代理检测的优点在于,伪造用户代理字符串很简单,而伪造能够欺骗能力检测的浏览器特性却很难。

    • 检测特性

      // 检测浏览器是否支持 Netscape 式的插件
      let hasNSPlugins = !!(navigator.plugins && navigator.plugins.length);
      // 检测浏览器是否具有 DOM Level 1 能力
      let hasDOM1 = !!(
        document.getElementById &&
        document.createElement &&
        document.getElementsByTagName
      );
    • 检测浏览器

      class BrowserDetector {
        constructor() {
          // 测试条件编译
          // IE6~10 支持
          this.isIE_Gte6Lte10 = /*@cc_on!@*/ false;
          // 测试 documentMode
          // IE7~11 支持
          this.isIE_Gte7Lte11 = !!document.documentMode;
          // 测试 StyleMedia 构造函数
          // Edge 20 及以上版本支持
          this.isEdge_Gte20 = !!window.StyleMedia;
          // 测试 Firefox 专有扩展安装 API
          // 所有版本的 Firefox 都支持
          this.isFirefox_Gte1 = typeof InstallTrigger !== 'undefined';
          // 测试 chrome 对象及其 webstore 属性
          // Opera 的某些版本有 window.chrome,但没有 window.chrome.webstore
          // 所有版本的 Chrome 都支持
          this.isChrome_Gte1 = !!window.chrome && !!window.chrome.webstore;
          // Safari 早期版本会给构造函数的标签符追加"Constructor"字样,如:
          // window.Element.toString(); // [object ElementConstructor]
          // Safari 3~9.1 支持
          this.isSafari_Gte3Lte9_1 = /constructor/i.test(window.Element);
          // 推送通知 API 暴露在 window 对象上
          // 使用默认参数值以避免对 undefined 调用 toString()
          // Safari 7.1 及以上版本支持
          this.isSafari_Gte7_1 = (({ pushNotification = {} } = {}) =>
            pushNotification.toString() == '[object SafariRemoteNotification]')(window.safari);
          // 测试 addons 属性
          // Opera 20 及以上版本支持
          this.isOpera_Gte20 = !!window.opr && !!window.opr.addons;
        }
        isIE() {
          return this.isIE_Gte6Lte10 || this.isIE_Gte7Lte11;
        }
        isEdge() {
          return this.isEdge_Gte20 && !this.isIE();
        }
        isFirefox() {
          return this.isFirefox_Gte1;
        }
        isChrome() {
          return this.isChrome_Gte1;
        }
        isSafari() {
          return this.isSafari_Gte3Lte9_1 || this.isSafari_Gte7_1;
        }
        isOpera() {
          return this.isOpera_Gte20;
        }
      }

      这个类暴露的通用浏览器检测方法使用了检测浏览器范围的能力测试。随着浏览器的变迁及发展,可以不断调整底层检测逻辑,但主要的 API 可以保持不变。

    • 能力检测的局限

      通过检测一种或一组能力,并不总能确定使用的是哪种浏览器。

现代 JavaScript 教程

任务

  1. 将 border-left-width 转换成 borderLeftWidth

    编写函数 camelize(str) 将诸如 “my-short-string” 之类的由短划线分隔的单词变成骆驼式的 “myShortString”。

    即:删除所有短横线,并将短横线后的每一个单词的首字母变为大写。

    示例:

    camelize("background-color") == 'backgroundColor';
    camelize("list-style-image") == 'listStyleImage';
    camelize("-webkit-transition") == 'WebkitTransition';

    提示:使用 split 将字符串拆分成数组,对其进行转换之后再 join 回来。

    function camelize(str) {
      let arr = str.split('-');
      for (let i in arr) {
        if (i === '0') continue;
        arr[i] = arr[i][0].toUpperCase() + arr[i].slice(1);
      }
      return arr.join('');
    }
  2. 过滤范围

    写一个函数 filterRange(arr, a, b),该函数获取一个数组 arr,在其中查找数值大小在 ab 之间的元素,并返回它们的数组。

    该函数不应该修改原数组。它应该返回新的数组。

    例如:

    let arr = [5, 3, 8, 1];
    
    let filtered = filterRange(arr, 1, 4);
    
    alert( filtered ); // 3,1(匹配值)
    
    alert( arr ); // 5,3,8,1(未修改)
    function filterRange(arr, a, b) {
      return arr.filter(item => item <= b && item >= a);
    }
  3. 原位 ( in place ) 过滤范围

    写一个函数 filterRangeInPlace(arr, a, b),该函数获取一个数组 arr,并删除其中介于 ab 区间以外的所有值。检查:a ≤ arr[i] ≤ b

    该函数应该只修改数组。它不应该返回任何东西。

    例如:

    let arr = [5, 3, 8, 1];
    
    filterRangeInPlace(arr, 1, 4); // 删除了范围在 1 到 4 之外的所有值
    
    alert( arr ); // [3, 1]
    function filterRangeInPlace(arr, a, b) {
      arr.forEach((item, index) => {
        if (item > b || item < a) {
          arr.splice(index, 1);
        }
      });
    }
  4. 降序排列

    let arr = [5, 2, 1, -10, 8];
    
    // ……你的代码以降序对其进行排序
    
    alert( arr ); // 8, 5, 2, 1, -10
    let arr = [5, 2, 1, -10, 8];
    
    arr.sort((a, b) => b - a);
    
    console.log(arr); // 8, 5, 2, 1, -10
  5. 复制和排序数组

    我们有一个字符串数组 arr。我们希望有一个排序过的副本,但保持 arr 不变。

    创建一个函数 copySorted(arr) 返回这样一个副本。

    let arr = ["HTML", "JavaScript", "CSS"];
    
    let sorted = copySorted(arr);
    
    alert( sorted ); // CSS, HTML, JavaScript
    alert( arr ); // HTML, JavaScript, CSS (no changes)
    function copySorted(arr) {
      return arr.map(item => item).sort();
    }
  6. 创建一个可扩展的 calculator

    创建一个构造函数 Calculator,以创建“可扩展”的 calculator 对象。

    该任务由两部分组成。

    1. 首先,实现 calculate(str) 方法,该方法接受像 "1 + 2" 这样格式为“数字 运算符 数字”(以空格分隔)的字符串,并返回结果。该方法需要能够理解加号 + 和减号 -

      用法示例:

      let calc = new Calculator;
      
      alert( calc.calculate("3 + 7") ); // 10
    2. 然后添加方法 addMethod(name, func),该方法教 calculator 进行新操作。它需要运算符 name 和实现它的双参数函数 func(a,b)

      例如,我们添加乘法 *,除法 / 和求幂 **

      let powerCalc = new Calculator;
      powerCalc.addMethod("*", (a, b) => a * b);
      powerCalc.addMethod("/", (a, b) => a / b);
      powerCalc.addMethod("**", (a, b) => a ** b);
      
      let result = powerCalc.calculate("2 ** 3");
      alert( result ); // 8
    • 此任务中没有括号或复杂的表达式。
    • 数字和运算符之间只有一个空格。
    • 你可以自行选择是否添加错误处理功能。
    function Calculator() {
      this.method = {
        '+': (a, b) => a + b,
        '-': (a, b) => a - b
      };
      this.calculate = function (str) {
        let arr = str.split(' ');
        let op = arr[1];
        let result = this.method[op](+arr[0], +arr[2]);
        return result;
      };
      this.addMethod = function (name, func) {
        this.method[name] = func;
      };
    }
    // 参考解决方案
    function Calculator() {
    
      this.methods = {
        "-": (a, b) => a - b,
        "+": (a, b) => a + b
      };
    
      this.calculate = function(str) {
    
        let split = str.split(' '),
          a = +split[0],
          op = split[1],
          b = +split[2]
    
        if (!this.methods[op] || isNaN(a) || isNaN(b)) {
          return NaN;
        }
    
        return this.methods[op](a, b);
      }
    
      this.addMethod = function(name, func) {
        this.methods[name] = func;
      };
    }
  7. 映射到 names

    你有一个 user 对象数组,每个对象都有 user.name。编写将其转换为 names 数组的代码。

    例如:

    let john = { name: "John", age: 25 };
    let pete = { name: "Pete", age: 30 };
    let mary = { name: "Mary", age: 28 };
    
    let users = [ john, pete, mary ];
    
    let names = /* ... your code */
    
    alert( names ); // John, Pete, Mary
    let names = users.map(item => item.name);
  8. 映射到对象

    你有一个 user 对象数组,每个对象都有 namesurnameid

    编写代码以该数组为基础,创建另一个具有 idfullName 的对象数组,其中 fullNamenamesurname 生成。

    例如:

    let john = { name: "John", surname: "Smith", id: 1 };
    let pete = { name: "Pete", surname: "Hunt", id: 2 };
    let mary = { name: "Mary", surname: "Key", id: 3 };
    
    let users = [ john, pete, mary ];
    
    let usersMapped = /* ... your code ... */
    
    /*
    usersMapped = [
      { fullName: "John Smith", id: 1 },
      { fullName: "Pete Hunt", id: 2 },
      { fullName: "Mary Key", id: 3 }
    ]
    */
    
    alert( usersMapped[0].id ) // 1
    alert( usersMapped[0].fullName ) // John Smith

    所以,实际上你需要将一个对象数组映射到另一个对象数组。在这儿尝试使用箭头函数 => 来编写。

    let usersMapped = users.map(item => ({
      id: item.id,
      fullName: item.name + ' ' + item.surname
    }));
  9. 按年龄对用户排序

    编写函数 sortByAge(users) 获得对象数组的 age 属性,并根据 age 对这些对象数组进行排序。

    例如:

    let john = { name: "John", age: 25 };
    let pete = { name: "Pete", age: 30 };
    let mary = { name: "Mary", age: 28 };
    
    let arr = [ pete, john, mary ];
    
    sortByAge(arr);
    
    // now: [john, mary, pete]
    alert(arr[0].name); // John
    alert(arr[1].name); // Mary
    alert(arr[2].name); // Pete
    function sortByAge(arr) {
      arr.sort((a, b) => a.age - b.age);
    }
  10. 随机排列数组

    编写函数 shuffle(array) 来随机排列数组的元素。

    多次运行 shuffle 可能导致元素顺序的不同。例如:

    let arr = [1, 2, 3];
    
    shuffle(arr);
    // arr = [3, 2, 1]
    
    shuffle(arr);
    // arr = [2, 1, 3]
    
    shuffle(arr);
    // arr = [3, 1, 2]
    // ...

    所有元素顺序应该具有相等的概率。例如,可以将 [1,2,3] 重新排序为 [1,2,3][1,3,2][3,1,2] 等,每种情况的概率相等。

    function shuffle(arr) {
      arr.sort((a, b) => (Math.random() > 0.5 ? 1 : -1));
    }
  11. 获取平均年龄

    编写 getAverageAge(users) 函数,该函数获取一个具有 age 属性的对象数组,并返回平均年龄。

    平均值的计算公式是 (age1 + age2 + ... + ageN) / N

    例如:

    let john = { name: "John", age: 25 };
    let pete = { name: "Pete", age: 30 };
    let mary = { name: "Mary", age: 29 };
    
    let arr = [ john, pete, mary ];
    
    alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28
    function getAverageAge(arr) {
      return arr.reduce((prev, item) => prev + item.age, 0) / arr.length;
    }
  12. 数组去重

    arr 是一个数组。

    创建一个函数 unique(arr),返回去除重复元素后的数组 arr

    例如:

    function unique(arr) {
      /* your code */
    }
    
    let strings = ["Hare", "Krishna", "Hare", "Krishna",
      "Krishna", "Krishna", "Hare", "Hare", ":-O"
    ];
    
    alert( unique(strings) ); // Hare, Krishna, :-O
    function unique(arr) {
      return Array.from(new Set(arr));
    }
  13. 从数组创建键 ( 值 ) 对象

    假设我们收到了一个用户数组,形式为:{id:..., name:..., age... }

    创建一个函数 groupById(arr) 从该数组创建对象,以 id 为键(key),数组项为值。

    例如:

    let users = [
      {id: 'john', name: "John Smith", age: 20},
      {id: 'ann', name: "Ann Smith", age: 24},
      {id: 'pete', name: "Pete Peterson", age: 31},
    ];
    
    let usersById = groupById(users);
    
    /*
    // 调用函数后,我们应该得到:
    
    usersById = {
      john: {id: 'john', name: "John Smith", age: 20},
      ann: {id: 'ann', name: "Ann Smith", age: 24},
      pete: {id: 'pete', name: "Pete Peterson", age: 31},
    }
    */

    处理服务端数据时,这个函数很有用。

    在这个任务里我们假设 id 是唯一的。没有两个具有相同 id 的数组项。

    请在解决方案中使用数组的 .reduce 方法。

    function groupById(users) {
      return users.reduce((obj, item) => {
        obj[item.id] = item;
        return obj;
      }, {});
    }