X

曜彤.手记

随记,关于互联网技术、产品与创业

吉 ICP 备10004938号

《JavaScript 高级程序设计(第四版)》读书笔记(第 1-7 章)


旧书清理系列。正好趁着第四版的推出,花点时间完整过一下。注:由于读的是电子版书籍,因此标注的页码会与实体书有所不同

前言

  1. (Page:62)JavaScript 这门语言与宿主关系密切。宿主为 JavaScript 定义了与外界交互所需的全部 API,而这些 API 中的一部分则由 W3C 的相关规范定义(如 localStorage)。这就意味着,如果没有这些宿主提供的 API,JavaScript 本身的应用场景便会十分尴尬。并且不同于 Java,JavaScript 宿主环境的实现细节并非标准的一部分,因此也存在着很多专门用于“优化”某一浏览器引擎的 “hack-code”。

第 1 章 - 什么是 JavaScript

  1. (Page:71)1995年 JavaScript 问世时,其主要用途是用来代替 Perl 等服务器端语言来处理网页中输入内容的验证
  2. (Page:72)Mocha -> LiveScript -> JavaScript。
  3. (Page:73)完整的浏览器宿主(宿主环境提供 ECMAScript 的基准实现和与环境自身交互必需的扩展,扩展使用 ECMAScript 的核心类型和语法,提供特定于环境的额外功能)端 JavaScript 实现包含多个组成部分:
  1. (Page:78)一个合格的 ECMAScript 实现必须满足下列条件:
  1. (Page:84)HTMLDOM(Document Object Model,文档对象模型)规范标准由 W3C 与 WHATWG 共同负责制定和维护。DOM 标准(DOM Core 与 DOM HTML)中包含有对应该如何访问和操作文档(基于 XML)任意部分的具体方式。几个版本:
  1. (Page:88)HTML5 的出现以“正式规范的形式”涵盖了尽可能多的 BOM(Browser Object Model)特性。而在此之前,BOM 并没有相关的标准。

第 2 章 - HTML 中的 JavaScript

  1. (Page:93)<script> 元素的几个特殊属性:

- async & defer

- 其他属性

  1. (Page:95)包含在 <script> 内的代码会被从上到下解释。在其内部的代码被计算完成之前,页面的其余内容不会被加载,也不会被显示。同样的,在解释外部 JavaScript 文件时,页面也会阻塞。
  2. (Page:96)在 XHTML 中可以使用 <script src=”foo.js”\> 的方式来引用外部 JavaScript 脚本。而在 HTML 中,该方式是无效的,有些浏览器无法正常处理。
  3. (Page:99)为了解决初始 JavaScript 代码执行带来的页面其他资源的下载延迟,现代 Web 应用通常将所有 JavaScript 引用放在 <body> 元素中的页面内容的后面(对于旧浏览器的最佳实践)。
  4. (Page:102)动态加载脚本:
// 会默认使用 “async” 的方式(加载不阻塞,执行阻塞);
let script = document.createElement('script');
script.src = 'foo.js';
document.head.appendChild(script);
  1. (Page:106)XHTML 模式下 <script> 内联代码的“防御式”写法:
// 防止 “<” 被解析为标签;
<script type="text/javascript">
// <![CDATA[
  function max(x, y) {
    if (x <= y) {
      return y;
    } else {
      return x;
    }
  }
// ]]>
  1. (Page:114)<noscript> 标签可用于在浏览器不支持(或被关闭)JavaScript 时显示位于其内部被特殊准备的内容。

第 3 章 - 语言基础

  1. (Page:119)函数范围内的“严格模式”:
function foo() {
  "use strict";
  // ...
}

- “严格模式”下的约束

  1. (Page:126)varlet
function foo() {
  console.log(age);
  var age = 27;
  // let age = 27;  // Uncaught ReferenceError: Cannot access 'age' before initialization.
}
foo();  // undefined.

- let 与闭包

for (let i = 1; i < 6; i++) {
  setTimeout(() => console.log(i));
}

  1. (Page:135)const
for (const key in {x: 1, y: 2}) {
  console.log(key);
}
for (const value of [1, 2, 3, 4]) {
  console.log(value);
}
  1. (Page:137)ECMAScript 的七种数据类型

- 一些特殊的 Case

  1. (Page:166)模板字面量标签函数(Tagged Templates):可以自定义模板插值行为
function decoCurrency(templateArr, ...templateVal) {
  return templateArr.reduce((prev, item, index) => {
    prev += (index === 0 ? item : (`¥${templateVal[index - 1]}${item}`))
    return prev
  }, '');
}
let cost = 100;
decoCurrency`This apple costs me ${cost}.`; // "This apple costs me ¥100.
  1. (Page:171)符号 Symbol
let genericSym = Symbol();
let fooSym = Symbol('foo');

/* 在“全局符号注册表”中定义和重用符号 */
let fooGlobalSym = Symbol.for('foo');  // 新建;
let otherFooGlobalSym = Symbol.for('foo');  // 提取;
console.log(fooGlobalSym === otherFooGlobalSym);  // true.

/* 查询全局符号的键名 */
console.log(Symbol.keyFor(fooGlobalSym));  // 'foo'.

/* 使用符号作为属性 */
let o = {
  [genericSym]: 'Symbol genericSym',
};
Object.defineProperty(o, fooSym, {
  value: 'fooSym',
});
Object.getOwnPropertySymbols(o);  // (2) [Symbol(), Symbol(foo)].
Object.getOwnPropertyDescriptors(o);  // {Symbol(): {…}, Symbol(foo): {…}}.
Reflect.ownKeys(o);  // 返回所有成员的键(包括不可枚举类型);
  1. (Page:203)运算符与操作符:
  1. (Page:245)标签语句:用于给语句加标签,典型应用场景是“嵌套循环”。
start: for (let i = 0; i < 10; ++i) {
  inner: for (let j = 0; j < 10; ++j) {
    if (i === 2) break start
    console.log(i) 
  }
}
  1. (Page:253)“switch…case” 语句在比较每个条件的值时会使用全等操作。不同于 C/C++,case 子句使用的变量或值不再有“只能是整型常量”的限制,在 ECMAScript 中可以使用任意的表达式。

第 4 章 - 变量、作用域与内存

  1. (Page:262)引用值(Object)的实体被存放在“堆内存”中。
  2. (Page:266)ECMAScript 中函数的参数是“按值传递”的(对于引用类型,可以理解为直接传递的是指针值)。
  3. (Page:268)每个上下文都有一个关联的“变量对象”,包含了当前上下文中定义的所有变量和函数。上下文中的代码在执行时,会创建变量对象的一个作用域链,代码正在执行的当前上下文的变量对象位于链的最前端。位于链前端的上下文可以访问后面上下文内的变量,反之则不行。以下两种情况会在作用域链前端临时添加一个上下文
  1. (Page:278)暂时性死区(TDZ):The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.
let x = 'outer x'; 
(function() {
  // enter TDZ (x is hoisted, but inaccessible).
    console.log(x);  // Uncaught ReferenceError: Cannot access 'x' before initialization.
    let x = 'inner x';
}());
  1. (Page:280)冻结封存对象:
  1. (Page:297)对象的频繁创建和销毁会导致浏览器引擎 GC 的频繁调用

第 5 章 - 基本引用类型

  1. (Page:312)通过 new Date().getTime() 获得的时间戳是基于 UTC 的,因此是时区独立的。
  2. (Page:327)为了能够让原始值拥有对象的行为(具有方法),需要借助“原始值包装类型(Boolean \ Number \ String)”。JS 引擎会在“以原始值形式访问其上的某个属性或方法”时,自动隐式创建该原始值的一个临时包装类对象。因此对该临时对象的任何“修改”操作,都不会生效。
let obj = new Object("Hello, world!");
console.log(obj instanceof String);  // true.
  1. (Page:336)JavaScript 字符串使用了两种 Unicode 编码混合的策略:UCS-2UTF-16。对于 U+0000~U+FFFF 范围内的 65536 个字符,统称为 Unicode 的基本多语言平面(BMP)。
  2. (Page:362)encodeURI 用于编码整个 URL(比如“空格”),而 encodeURIComponent 仅用于编码 URL 中的单独组件(比如“#”、“?” 等)。
  3. (Page:376)可以通过全局属性 globalThis获得全局的 this 值

第 6 章 - 集合引用类型

  1. (Page:380)在定义对象时,键只能为字符串(其他类型会被自动转换)或 Symbol 类型
  2. (Page:384)数组
  1. (Page:440)MapSet
let map = new Map({
  [Symbol.iterator]: function*() {
    yield ['key', 1];  // WeakMap 只能以“对象”作为键;
  }
});
// 利用 WeakMap 键的不可迭代性,且 WeakMap 中键值对的生命周期与对象绑定;
const User = (() => {
  const wm = new WeakMap();
  class User {
    constructor(id) {
      this.idKey = Symbol('id');
      this.setPrivateId('id', id);
    }
    setPrivateId(key, value) {
      const privateMembers = wm.get(this) || {};
      privateMembers[key] = value;
      wm.set(this, privateMembers);
    }
  };
  return User;
})();
// 当 DOM 节点被移除后,WeakMap 中的保存 DOM 对象的键值对会被自动清除(不会阻碍 GC);
const wm = new WeakMap();
const loginButton = document.querySelector('body');
wm.set(loginButton, { disabled: true });
  1. (Page:475)以下四种原生集合类型拥有默认迭代器(支持顺序迭代,可以应用 for-of;都兼容“扩展操作符”,可以用于浅复制):

第 7 章 - 迭代器与生成器

  1. (Page:482)迭代器
class Counter {
  constructor(limit) {
    this.limit = limit;
  }
  [Symbol.iterator]() {
    // use closure to produce new "count" for each iteration.
    let count = 1, limit = this.limit;
    return {
      next() {
        if (count <= limit) {
          return { done: false, value: count++ };
        } else {
          return { done: true, value: undefined };
        }
      },
      return() {  // will be fired if iteration exits early.
        console.log('Exiting early!');
        return { done: true };
      }
    };
  }
};
let counter = new Counter(3);
for (let i of counter) console.log(i);
  1. (Page:499)生成器

- 用来自定义可迭代对象

function* nTimes(n) {
  while(n--) {
    yield;
  }
}
// this will iterates for 3 times.
for (let _ of nTimes(3)) 
  console.log('foo');

- 借助 yield 实现“输入输出”:(*第一次调用 next 传入的值不会被使用,仅用于开始执行生成器函数)

// 第一次执行时,会先求 yield 子表达式,返回值,停止;
// 第二次执行时,yield 子表达式收到通过 next 传入的参数,返回值,停止;
function* generatorFn() {
  return yield 'foo';
}
let generatorObj = generatorFn();
console.log(generatorObj.next());  // {value: "foo", done: false}.
console.log(generatorObj.next('bar'));  // {value: "bar", done: true}.

- 产生可迭代对象:(使用“*”增强 yield 的行为,让其能够迭代一个可迭代对象,从而一次产出一个值)

// yield* 将一个可迭代对象序列化为一连串可单独产出的值;
function* generatorFnA() {
  yield* [1, 2, 3];
  // for (const x of [1, 2, 3]) {  // 等价;
  //   yield x;
  // }
}
for (const x of generatorFnA())
  console.log(x);

// yield* 的值为关联迭代器返回 “done: true” 时的 value 属性的值;
function* generatorFnB() {
  console.log('iter value:', yield* [1, 2, 3]);
}

// 可以用自定义生成器来设置 yield* 的返回值;
function* generatorFnC() {
  console.log(yield* (function*() {
    console.log(yield 'iter value');  // yield 可以接收从 next 传入的值;
    return 'yield* value';
  })());
}

- 使用 yield 实现递归算法

function* nTimes(n) {
  if (n > 0) {
    yield* nTimes(n - 1);
    yield (n - 1);
  }
}
for (const x of nTimes(3)) 
  console.log(x);

- 其他:所有的生成器对象都有 return 方法,借助于它和 throw 方法可用于强制生成器进入关闭状态(两者均需要主动调用)。其中,若生成器还没有开始执行,那么调用 throw 抛出的错误不会在生成器函数内部被捕获,这相当于在函数外部抛出了错误。

const g = generatorFnA();
for (const x of g) {
  if (x > 1) {
    g.return(4);  // stop and return (done: true).
    // g.throw('error msg');
  }
  console.log(x);
}


这是文章底线,下面是评论
  暂无评论,欢迎勾搭 :)