0%

学习笔记 2020 10 28

学习笔记 2020-10-28

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

函数

立即调用的函数表达式

立即调用的匿名函数又被称作立即调用的函数表达式 ( IIFEImmediately Invoked Function Expression )。它类似于函数声明,但由于被包含在括号中,会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。

使用 IIFE 可以模拟块级作用域。即在一个函数表达式内部声明变量,然后立即调用这个函数。

使用立即调用的函数表达式可以解决 var 带来的问题。

let divs = document.querySelectorAll('div');
for (var i = 0; i < divs.length; ++i) {
  divs[i].addEventListener(
    'click',
    (function (frozenCounter) {
      return function () {
        console.log(frozenCounter);
      };
    })(i)
  );
}

ES6 ,有了块级作用域的概念,就可以规避这个问题。

私有变量

JS 没有私有成员的概念,所有对象属性都是公有的。有私有变量的概念。任何定义在函数或块中的变量,都可以认为是私有的。

特权方法是能够访问函数私有变量及私有函数的公有方法。在对象上有两种方式创建特权方法。第一种是在构造函数中实现。

function MyObject() {
  // 私有变量和私有函数
  let privateVariable = 10;
  function privateFunction() {
    return false;
  }
  // 特权方法
  this.publicMethod = function () {
    privateVariable++;
    return privateFunction();
  };
}

这个模式是把所有私有变量和私有函数都定义在构造函数中。然后,再创建一个能够访问这些私有成员的特权方法。因为形成了闭包,所以可行。

静态私有变量

特权方法可以通过使用私有作用域定义私有变量和函数来实现。

(function () {
  // 私有变量和私有函数
  let privateVariable = 10;
  function privateFunction() {
    return false;
  }
  // 构造函数
  MyObject = function () {};
  // 公有和特权方法
  MyObject.prototype.publicMethod = function () {
    privateVariable++;
    return privateFunction();
  };
})();

使用这种方法,避免了所有方法都在对象实例上重复定义。特权方法作为一个闭包,始终引用着包含它的作用域。但该种方法的每个实例没有了自己的私有变量。

模块模式

模块模式在一个单例对象上实现了相同的隔离和封装。

单例对象就是只有一个实例的对象。

let singleton = (function () {
  // 私有变量和私有函数
  let privateVariable = 10;
  function privateFunction() {
    return false;
  }
  // 特权/公有方法和属性
  return {
    publicProperty: true,
    publicMethod() {
      privateVariable++;
      return privateFunction();
    }
  };
})();

模块模式是在单例对象基础上加以拓展,使其通过作用域链来关联私有变量和特权方法。

模块模式使用了匿名函数返回一个对象。在匿名函数内部,首先定义私有变量和私有函数。之后,创建一个要通过匿名函数返回的对象字面量。这个对象字面量中只包含可以公开访问的属性和方法。本质上,对象字面量定义了单例对象的公共接口。如果单例对象需要进行某种初始化,并且需要访问私有变量时,就可以采用这个模式。

let application = (function () {
  // 私有变量和私有函数
  let components = new Array();
  // 初始化
  components.push(new BaseComponent());
  // 公共接口
  return {
    getComponentCount() {
      return components.length;
    },
    registerComponent(component) {
      if (typeof component == 'object') {
        components.push(component);
      }
    }
  };
})();
模块增强模式

在返回对象之前先对其进行增强,适合单例对象需要是某个特定类型的实例,但又必须给它添加额外属性和方法的场景。

let singleton = (function () {
  // 私有变量和私有函数
  let privateVariable = 10;
  function privateFunction() {
    return false;
  }
  // 创建对象
  let object = new CustomType();
  // 添加特权/公有属性和方法
  object.publicProperty = true;
  object.publicMethod = function () {
    privateVariable++;
    return privateFunction();
  };
  // 返回对象
  return object;
})();

期约与异步函数

ES6 新增了正式的 Promise 引用类型,支持优雅地定义和组织异步逻辑。

异步编程

同步行为和异步行为的对立统一是计算机科学的一个基本概念。特别是在 JavaScript 这种单线程事件循环模型中,同步操作与异步操作更是代码所要依赖的核心机制。异步行为是为了优化因计算量大而时间长的操作。但不一定必须如此,不想为等待某个操作而阻塞线程执行,就可以使用异步操作。

同步与异步

同步行为对应内存中顺序执行的处理器指令。每条指令都会严格按照它们出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地的信息。这样的执行流程容易分析程序在执行到代码任意位置时的状态。

在程序执行的每一步,都可以推断出程序的状态。

异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的。如果代码要访问一些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待。但异步操作何时完成,异步操作中涉及到的数据何时可获取可操作,是不可知的。

以往的异步编程模式

异步行为是 JavaScript 的基础。在早期,只支持定义回调函数来表明异步操作完成。串联多个异步操作是一个常见的问题,通常需要深度嵌套的回调函数。

function double(value) {
  setTimeout(() => setTimeout(console.log, 0, value * 2), 1000);
}
double(3);
// 6(大约 1000 毫秒之后)

在这个例子中, setTimtout 定义了一个在指定时间后会被调度执行的回调函数。1000 毫秒之后, JavaScript 执行时会把回调函数推到自己的消息队列上去等待执行。推到队列之后,回调什么时候出列被执行对 JavaScript 代码就完全不可见了。 double 函数会在 setTimeout 成功调度异步操作之后立即退出。

  1. 异步返回值

    假设 setTimeout 操作会返回一个有用的值。如何把这个值进行传递?一个方法是给异步操作提供一个回调。

    function double(value, callback) {
      setTimeout(() => callback(value * 2), 1000);
    }
    double(3, x => console.log(`I was given: ${x}`));
    // I was given: 6(大约 1000 毫秒之后)
  2. 失败处理

    异步操作的失败处理在回调模型中也要考虑。因此出现了成功回调与失败回调。

    function double(value, success, failure) {
      setTimeout(() => {
        try {
          if (typeof value !== 'number') {
            throw 'Must provide number as first argument';
          }
          success(2 * value);
        } catch (e) {
          failure(e);
        }
      }, 1000);
    }
    const successCallback = x => console.log(`Success: ${x}`);
    const failureCallback = e => console.log(`Failure: ${e}`);
    double(3, successCallback, failureCallback);
    double('b', successCallback, failureCallback);

    这种模式已经不可取了,因为必须在初始化异步操作时定义回调。异步函数的返回值只在短时间内存在,只有预备好将这个短时间存在的值作为参数的回调才能接收到它。

现代 JavaScript 教程

Symbol 类型

根据规范,对象的属性值只能是字符串类型或者 Symbol 类型。

Symbol 不会被隐式转换为字符串。显式转换需要调用 toString() 。或者使用 description 属性来显示描述。

Symbol 会在 for-in 中被跳过。

Object.assign 会同时复制字符串和 Symbol 属性。

全局 symbol

根据相同的名字创建出来的 symbol 都是不一样的。如果想要名字相同的 Symbol 具有相同的实体,可以使用 全局 Symbol 注册表

从注册表读取( 不存在则创建 ) Symbol ,使用 Symbol.for(key)

使用 Symbol.keyFor(sym) ,根据一个全局 Symbol 来返回一个名字。

系统 Symbol

JavaScript 内部有很多系统 Symbol。

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive