【读书笔记】你不知道的JS中卷

163 阅读20分钟

前言

  • 本文仅为笔者的读书笔记,加入笔者自己理解的大纲提炼,若有错误恳请指出。系统学习推荐阅读原书。
  • 推荐指数: 🌟🌟🌟🌟

JavaScript非常特殊,只学习一小部分的话非常简单,但是想要完整的学习会很难(就算学到够用也不容易)。当开发者感到迷惑时,他们通常会责怪语言本身,而不是怪自己对语言缺乏了解。希望你能打心眼里欣赏这门语言。

思维导图

第一部分 | 类型和语法

第一章 | 类型

对于JavaScript,类型是值的内部特征,它定义了值的行为。

1.1 内置类型
  • null
  • undefined
  • boolean
  • number
  • string
  • object
  • symbol

除了对象都是基本类型

使用复合条件检测null值的类型

var a = null
(!a && typeof a === 'object'); // true

函数的情况比较特殊

typeof function foo(){} === 'function' // true

但是函数其实只是object的一个“子类型”,是一个“可执行对象”,内部有一个[[call]]属性,使其可以被调用

1.1 值和类型

JavaScript中的变量是没有类型的,值才有。变量可以随时持有任何类型。

语言引擎不要求变量持有与初始值相同类型的值。

在对变量执行typeof操作的时候,其实判断的是该变量持有的值的类型,而不是变量的类型,变量没有类型

  • 1.3.1 undefined和undeclared

    var a;
    a; // undefined
    b; // ReferenceError: bis not defined
    

    其实b报错成undeclared更加准确

    typeof对这两个的处理人更让人抓狂

    var a;
    typeof a; // "undefined"
    typeof b; // "undefined"
    

    这其实是一种安全机制,在做polyfill的时候可以检查变量是否存在而不会报错ReferenceError

当然我们希望全局命名空间不应该有变量存在,所有东西都应该被封装到模块或者私有/独立的命名空间中。

第二章 | 值

2.1 数组

在JavaScript中数组可以容纳任何类型的值

delete 删除数组单元不会改变数组长度,单元值为undefined

类数组(DOM元素列表,arguments对象)转化方法

// 方法一
Array.prototype.slice.call(auguments);
// 方法二(ES6)
Array.from(auguments);
2.2 字符串

JavaScript中字符串不是字符组,尽管看上去很像。他们都有length, indexOf()和concant()方法。

字符串是不可变的,数组是可变的

字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。

借用数组方法处理字符串

var str = "hello";
console.log(Array.prototype.join.call(str, '-')); // h-e-l-l-o
var result = Array.prototype.map.call(str, item => {
    return item.toUpperCase() + '.';
}).join("");
console.log(result); // H.E.L.L.O.

字符串也可以通过构造函数产生

var str = new String('abc');
var str1 = 'abc';
console.log(typeof str); // object
console.log(str == str1); // true
console.log(str === str1); // false

但是即使是构造函数产生的str的原始值也是不可修改的。

2.3 数字

JavaScript使用的是“双精度”格式(64位二进制)

Javascript只有一种数值类型:number,包括“整数”和带小数的十进制。

  1. 语法

默认情况下小数最后面的的0被省略

var a = 50.00000;
var b = 50.00;
console.log(a === b); // true

toFixed输出为字符串

var num = 100;
console.log(typeof num.toFixed(3)); // string

对于.运算符需要特别注意。数字后面的点会被解析会小数点

// 无效的语法
42.toFixed(3)
// 下面的有效
(42).toFixed(3)
42..toFixed(3)

数字字面量还可以用其他格式表示,二进制,八进制和十六进制

0xf3 // 十六进制 
0o56 // 八进制
0b0101 // 二进制
  1. 较小的数值

所有遵循IEEE754规范都有以下问题

0.1 + 0.2 === 0.3 // false

常见的比较方法是设置一个误差范围,通常称为“机器精度”

通过ES6常量Number.EPSLION比较两个数是否相等

console.log(Math.abs(0.1+0.2-0.3) < Number.EPSILON) // true
  1. 整数的安全范围

能被安全呈现最大整数是2^53 - 1。ES6中被定义为Number.MAX_SAFE_INTEGER,对应的最小值为Number.MIN_SAFE_INTEGER

  1. 整数检测

ES6方法

console.log(Number.isInteger(42)) // true
console.log(Number.isInteger(42.000)) // true
console.log(Number.isInteger(42.3)) // false

polypill

function isInteger(num) {
    return typeof num === 'number' && num % 1 === 0
}
2.4 特殊数值
  • undefined不是关键字可以被赋值(但是不要要这样做)

  • void运算符返回undefined(习惯是使用void 0)

  • NaN是一个警戒值,指出错误“执行数学运算没有成功,这是失败后的返回结果”

    • 与自身不相等
    • Number.isNaN检测
  • 无穷数

    console.log(1/0); // Infinity
    console.log(-1/0); // -Infinity
    
2.5 值和引用

在许多编程语言中,赋值和参数传递可以通过值复制(value-copy)或者引用复制(reference-copy )来完成。

JavaScript中简单类型总是通过值复制方式来赋值/传递;而复杂类型(对象)总是通过引用复制来赋值/传递。

第三章 | 原生函数

3.1 内部属性[[class]]

这个属性无法直接访问,一般通过Object.prototype.toString查看。

var arr = [1, 2, 3]
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(Object.prototype.toString.call(/[0-9]/g)) // [object RegExp]
console.log(arr.toString()) // 1,2,3 这是调用的Array对象上的toString方法

但是对于null和undefined也会返回同样的情况

console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]

对于基本类型有所不同,它们会被包装

3.2 封装对象包装

一般不推荐直接使用封装对象

var a = new Boolean(false)
if (a) {
    console.log("Oops") // Oops
}

封装的对象是真值(truthy)

3.3 拆封

想要得到基本类型值一般可以使用valueof()函数

var a = new String('abc')
var b = new Number(39)
var c = new Boolean(true)

console.log(a.valueOf()) // abc
console.log(b.valueOf()) // 39
console.log(c.valueOf()) // true

var arr = [1,2,3]
var obj = {
    a: 2
}
var regexp = /[1-9]/g
console.log(obj.toString()) // [object Object]
console.log(obj.valueOf() === obj) // true
console.log(arr.toString()) // 1,2,3
console.log(arr.valueOf() === arr) // true
console.log(regexp.toString()) // /[1-9]/g
console.log(arr.valueOf() === arr) // false

复杂对象略有不同

在需要用到基本值的地方会发生隐式拆封,也就是强制类型转换

var a = new String('abc')
var b = a + 'd' // 'abcd'
3.4 原声函数作为构造函数

对于数组,对象,函数和正则表达式我们喜欢以字面量的形式来创建它们。实际上使用字面量和构造函数的效果是一样的(创建的值都是通过封装对象来包装),我们应该尽量使用字面量模式

  1. Array()

不用创建和使用空单元数组。

  1. Object()、Funtion()、RegExp()

尽量避免使用

  1. Date()和Error()

这两个没有字面量模式,所以更加常用

创建日期对象必须使用new Date()。可以传入一个时间戳构造Date对象,不传就是当前时间的Date对象。不带new会得到当前时间的字符串。

创建错误对象主要是为了获得当前运行栈上下文。错误对象通常和throw一起使用。并且通常至少包含一条message属性。对于原生的错误类型,构造很少使用

  1. Symbol
  • 定义不使用new

  • 属于基本类型

  • 无法访问确切的值

  1. 原生原型
  • 包含各自类型的行为特征。

  • 不全是纯对象,是子类型。

    console.log(typeof Function.prototype) // function
    

第四章 | 强制类型转换

4.1 值的类型转换

将值从一种类型转换为另一种类型通常称为类型转换(type casting),这是显式的情况;隐式的情况称为强制类型转换(coercion)。

JavaScript中的强制类型转换总是返回标量基本类型值。

也可以这样区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换发生在动态类型语言的运行时(runtime)。

JavaScript中统称为强制类型转换,又可以分为隐式强制类型转换和显式强制类型转换。

var a = 42
var b = a + '' // 隐式
var c = String(a) // 显式
4.2 抽象值操作

ES5规范定义了一些抽象操作(仅供内部使用的操作)和转换规则。

4.2.1 ToString

负责处理非字符串到字符串的强制类型转换。

基本类型值的规则:null ==> 'null',undefined ==> 'undefined',true ==> 'true'。数字遵循通用规则,极大极小数使用指数形式。

对于普通对象来说,除非自定义,否则toString()Object.prototype.toString)返回内部[[class]]属性的值。

如果对象有自己的toString方法,字符串化的时候就会调用该方法并使用返回值。

var arr = [1, 2, 4]
console.log(arr.toString()) // 1,2,4

JSON字符串化

工具函数JSON.stringify()在将JSON对象序列化时也用到了ToString。

所有JSON-safe的值都可以使用JSON.stringify字符串化。

不安全的的JSON值:undefined、function、symbol和包含循环引用的对象。

JSON.stringify在对象中遇到undefined、function和symbol会自动忽略;数组中会返回null,保证单元位置不受影响

var obj = {
    a: 123,
    b: undefined,
    c: function() {},
    d: Symbol('test')
}
var arr = [1, undefined, function() {}, Symbol('test')]

console.log(JSON.stringify(obj)) // {"a":123}
console.log(JSON.stringify(arr)) // [1,null,null,null]

对于对象中无法被序列化的值,可以自定义toJSON方法

var a = {}
console.log('toJSON' in a); // false
a.toJSON = function() {
    console.log(222)
  	return 'something'
}
JSON.stringify(a) // 222
console.log('toJSON' in a); // true

注意其返回的是JSON-safe的值而不是一个字符串。

JSON.stringify可以接受第二个参数replacer,指定需要被序列化的属性。

replacer可以是字符串数组。

var obj = {
    a: 123,
    b: 'Neil',
    c: [1, 3, 4]
}

console.log(JSON.stringify(obj, ['a', 'b'])) // {"a":123,"b":"Neil"}

也可以是一个函数

var obj = {
    a: 123,
    b: 'Neil',
    c: [1, 3, 4]
}

console.log(JSON.stringify(obj, function(key, value) {
    if (key !== 'c') {
        return value
    }
})) // {"a":123,"b":"Neil"}

JSON.stringify不是强制转换,只是涉及ToString的强制转换。如果被序列化对象定义了toJSON()方法,会先调用转换为JSON-safe的值。

4.2.2 ToNumber

将非数字转换为数字

true ==> 1,false ==> 0, undefined ==> NaN,null ==> 0

对象会首先被转换为相应的基本类型值,如果是非数字类型再强制转换为数字。

为了将值转换为相应的基本类型,抽象操作ToPrimitive会首先检查该值有没有valueOf(),如果有且返回基本类型就使用该值进行强制转换,如果没有就使用toString()的返回值进行强制转换。如果均不返回基本类型就会产生TypeError。比如Object.create(null)创建的对象。

Number("")  // 0
Number([]) // 0 数组toString返回空字符串
Number({}) // NaN 对象toString返回字符串"[object Object]"
4.2.3 ToBoolean
  1. 假值(falsy value)

JavaScript中的值可以分为两类:

  • 可以被强制转换为false的值
    • undefined
    • null
    • false
    • +0, -0, NaN
    • ""(字符串中唯一假值)
  • 其他被强制转换为true的值
  1. 假值对象(false value)

指封装了假值的对象

var a = new String("")
var b = new Boolean(false)
var c = new Number(0)
var d = Boolean(a && b && c) // true
4.3 显示强制类型转换

我们在编码时应该尽可能地将类型转换表达清楚。类型转换越清晰,代码可读性越高,更容易理解。

4.3.1 字符串与数字之间的显示转换

基本遵循前面的规则

在JavaScript开源社区一元运算符被认为是显式强制类型转换。不过最好不要用。

~位操作符(返回二进制的补位),大致等于~42 // -(42 + 1) ==> -43

-1是个哨位值,大部分查找遵循这一惯例(indexOf)。所以当~-1等于0(falsy value)

var str = 'hello'

console.log(str.indexOf('lo'));

if (str.indexOf('lo') > -1) {
    console.log('find!');
}

if (~str.indexOf('lo')) {
    console.log('find!');
}
4.3.2 显式解析数字字符串

NumberparseInt()

Number()不允许出现非数字字符,parseInt()可以。

对于浮点数可以使用parseFloat()

4.3.3 显式转换为布尔值

Boolean()

4.4 隐式强制类型转换
4.4.2 字符串与数字

常见的会使用a + ""。对于对象而言不同于String()方法的是会先调用valueOf()方法,再调用toString()

4.4.3 布尔值到数字

判断只有一个真值时

function onlyOne (a,b,c) {
    var sum = 0
    for(var i = 0; i < arguments.length; i++) {
        if (arguments[i]) {
            sum ++
        }
    }
    return sum === 1
}
4.4.4 隐式强制转换布尔值

下面情况会发生

  1. if()
  2. for()
  3. while()和do{}while()
  4. ?:三元表达式
  5. 逻辑运算符
4.4.5 || 和 &&

与其他语言有差异,叫“逻辑运算符”不太准确,“选择器运算符”更合适。它们返回的不是布尔值

它们的返回值时两个操作数中的一个(有且仅有一个)。

console.log(43||32) // 43
console.log(null||32) // 32
console.log(null && 32); // null
console.log(42 && 32); // 32

常见用法:设置参数默认值

function foo (a, b) {
    a = a || 'abc'
    b = b || 'dce'
}
4.4.6 symbol的强制转换

ES6允许对symbol的显式强制类型转换,不允许隐式。

var sym = Symbol('cool');
console.log(String(sym)); // Symbol(cool)
console.log(sym + ""); // TypeError
4.5 宽松相等和严格相等
4.5.1 性能

===有更好的性能(不用隐式转换)

4.5.2 抽象相等 ==
  1. 比较对象的时候=====一样

  2. 字符串和数字比较字符串发生ToNumber操作

  3. 不要和布尔类型做比较

  4. null和undefined在==中相等,除此之外不和其他值相等

  5. 对象和非对象比较

    1. 对象会先发生ToPrimitive操作

    2. 非对象中布尔值会转化为数字类型

      console.log({} == false); // false
      console.log([] == false); // true
      console.log([0] == false); // true
      
4.5.3 少见的情况
[] == ![]
var i = 2

Number.prototype.valueOf = function() {
    return i++
}

var a = new Number(2)

if (a == 2 && a == 3) {
    console.log('crazy') // crazy
}

第五章 | 语法

本章节主要讨论语法(grammer),区别于词法(syntax)

5.1 语句和表达

语句(statement)和表达式(expression)是有区别的。语句相当于句子,表达式相当于短语。

var a = 3 * 6;
var b = a;
b;

其中var a = 3 * 6;var b = a;称为声明语句(declaration statement)。

a = 3 *6称为赋值表达式

5.1.1 语句的结果值

语句都有一个结果值(statement completion value, undefined也算)

在控制台中分别执行以下语句可以获得返回值

a = 18; // 18
var a = 19; // undefined

var关键字声明变量返回值会被屏蔽掉,所以返回undefined

代码块也会返回值

var b;
if (true) {
  b = 4 + 38 // 
}

理论上返回42,但是语法不允许将语句结果值赋值给另一个变量(可以用eval实现)

5.1.2 表达式的副作用

大部分表达式没有副作用

常见的副作用表达式有函数调用

function foo () {
  a = a + 1;
}
var a = 42;
foo(); // 结果值undefined 副作用:a的值被改变

其他

var a = 42;
var b = a++; // a的值被改变(b等于42)

括号也无法改变

var a = 42;
var b = (a++); // b还是等于42,逗号语句可以实现目标 b = (a++, a)

delete语句也有返回值

var obj = {
  a: 1
};
delete obj.a; // true

链式赋值

var a, b, c;
a = b = c = 42;
5.1.3 上下文规则

大括号

  1. 对象字面量

  2. 代码块

    [] + {} // [Object object]
    {} + [] // 0
    

    第一行的{}会被强制转换为[Object object],第二行的{}是个空代码块(不执行任何操作)+ []被强制转化

  3. 对象解构

  4. if else

    事实上JavaScript没有else if语句,只是else包含单条语句时省略了{}。

5.2 运算符优先级

&& 高于 || 高于三元表达式? :

console.log(true || false && false); // true
5.3 暂时性死区
  • let const 赋值

  • 函数默认参数

5.4 try..finally

finally 中 return会覆盖try中return


第二部分 | 异步与性能

第一章 | 异步:现在与将来

概述了异步的概念以及异步中的各种问题。

  • JavaScript执行基于调用栈:现在调用和将来调用。
  • 事件循环模式。
  • 异步函数的并行与并发的传统处理方式。

第二章 | 回调

JavaScript中的回调的缺陷:

  • 逻辑上难以理解
  • 回调嵌套
  • 信任问题
    • 多次调用
    • 过早或者过晚调用

第三章 | Promise

3.1 基础概念

Promise是一种范式,希望获得反转控制的能力。

Promise一旦决议则不可更改。

类似于事件监听,思考下面伪代码

foo(x){
  // do a long time job
}
foo(42);
on(foo 'completion'){
  // next step
}
on(foo 'error') {
  // catch error
}

很好的实现了控制反转和关注点分离

3.2 识别Promise对象

鸭子类型(duck typing)

3.3 Promise信任问题
  • 解决了调用过早:因为即使是立即决议,也无法被同步观察到,.then()方法是异步调用

  • 解决了调用过晚:

    var p = Promise.resolve()
    p.then(function() {
        p.then(function() {
            console.log('C');
        })
        console.log('A');
    })
    p.then(function(){
        console.log('B');
    })
    // A B C
    
  • 回调未调用

    function timeoutPromise(delay) {
        return new Promise(function(resolve, reject){
            setTimeout(function(){
                reject('timeout');
            }, delay)
        })
    }
    Promise.race([
        foo(),
        timeoutPromise(3000)
    ]).then(
        function(){
            // foo completed
        },
        function(err) {
            // err or timeout
        }
    );
    
  • 不会出现多次调用

  • 参数传递

    • Promise.resolve()只接受一个参数,其他会被忽略
    • 传入一个非Promise,非thenable的立即值会得到一个用这个值充填的Promise。下面两种行为一致
    var p1 = new Promise(function(resolve, reject) {
    	resolve(42);
    })
    var p2 = Promise.resolve(42);
    
    • 传入一个Promsie会返回同一个Promise
    var p1 = Promise.resolve(42);
    
    var p2 = Promise.resolve(p1);
    
    console.log(p1 === p2); // true
    
    • 传入一个thenable得到的也是一个真正的Promise
  • 错误捕获

  • 决议过程中的错误会被拒绝函数捕获,.then()回调本身返回一个Promise,其中的错误会被拒绝处理函数捕获。

3.4 链式流

基于Promise特性

  • .then()返回一个Promise

链式流中的错误捕获

对于设置了拒绝处理函数的流会继续执行

Promise.resolve(42)
    .then(()=>{
        console.log(111); // 111
    })
    .then(() => {
        throw new Error('Oops')
    })
    .then(() => {
        console.log(222); // 不会执行
    },err => {
        console.log(err); // Error: Oops
    })
    .then(() => {
        console.log(333); // 333
    })

对于未捕获的错误会有一个默认的拒绝处理函数顶替上来,抛出错误终中断程序

function (err) {
	throw err
}
3.5 错误处理

回调中的error-first风格

function foo(cb) {
    setTimeout(function() {
        try {
            var x = baz.bar()
            cb(null, x)
        } catch (err) {
            cb(err)
        }
    } ,100)
}
foo( function(err, value) {
    if (err) {
        console.log(err);
    }
    else {
        console.log(value);
    }
})

通常情况下开发者在链式流末尾使用catch捕获错误,但是catch本身返回一个Promise,它的错误(虽然几率很小)就会被忽略掉。

3.6 Promise模式
3.6.1 Promise.all([...])
  • 实现了门(gate)机制。
  • 任何一个返回拒绝Promise.all()就会拒绝,抛弃其他所有promis返回结果
  • 返回指定顺序(与完成顺序无关)数组。
3.6.2 Promise.race([...])
  • 门闩模式,Promise中称为竞态。
  • 超时竞赛
Promise.race([
    foo(),
    timeoutPromise(3000)
]).then(
    function() {
        // foo 按时完成
    },
    function(err) {
        // foo被拒绝或者超时
    }
)
  • finally收尾函数
let isLoading = true
Promise.resolve(42)
.then(res => {
    // do something
})
.catch(err => {
    // deal err
})
.finally(() => {
    isLoading = false
})

finally也会返回一个promise对象,存在未处理拒绝问题。需要构造一个辅助工具查看决议。

3.6.3 并发迭代
var p1 = Promise.resolve(21)
var p2 = Promise.resolve(42)
var p3 = Promise.reject('good')
console.log('map' in Promise);

Promise.map = function (vals, func) {
  return Promise.all(
    vals.map(val => {
      return new Promise(resolve => {
        func(val, resolve)
      })
    })
  )
}

Promise.map([p1, p2, p3], function (val, done) {
  Promise.resolve(val)
    .then(
      res => {
        return done(res * 2)
      },
      done
    )
}).then(res => {
  console.log(res); // [42 84 'good']
})
3.8 Promise的局限性
  • 顺序错误处理,即Promise中的错误很容易被忽略。
  • 无法取消

第四章 | 生成器

  • Promise解决了控制反转的问题
  • generator将解决异步不符合大脑逻辑问题
4.1 打破完整运行

ES6中指示暂停点的语法yield,礼貌的表达了一种合作式的控制放弃。

var x = 1;
function* foo() {
    x++
    console.log(x);
    yield;
    console.log(x);
}
function bar () {
    x++
}

var iter = foo()
iter.next() // 2
bar()
iter.next() // 3

foo()运算并没有执行生成器,只是构造了一个迭代器

4.1.1 输入与输出
  • 调用.next()会返回一个对象{value: value, done: Boolean}

  • yield是双向通信。暂停并返回一个值,重新启动时接受.next(args)的参数

4.1.2 多个迭代器

每次构建一个迭代器,实际上就隐式构建了生成器的一个实例,通过这个迭代器来控制的是这个生成器实例。

表达式中暂停后对前面已读取值的变量重新复制不会改变读取结果

var a = 1
function *foo() {
    var b = a + (yield);
    console.log(b)
}

var ite = foo()
ite.next()
a = 12
ite.next(1) // 2
4.2 生成器产生值

假如要产生一系列值,每个值都与前面一个有特定的关系。

闭包实现

var gimme = (function () {
    var val;
    return function () {
        if (val === undefined) {
            val = 1
        } else {
            val = val * 3 + 6
        }
        debugger
        return val
    }
})()

console.log(gimme()); // 1
console.log(gimme()); // 9
console.log(gimme()); // 33

构造迭代器实现

var gimme = (function () {
    var val;
    return {
        [Symbol.iterator]: function () {return this},
        next: function() {
            if (val === undefined) {
                val = 1
            } else {
                val = val *3 + 6
            }
            return { value: val, done: false}
        }
    }
})()

console.log(gimme.next().value)
console.log(gimme.next().value)
console.log(gimme.next().value)

生产器迭代器

function *gimme() {
    var val;
    while(true) {
        if (val === undefined) {
            val = 1
        }
        else {
            val = val * 3 + 6
        }
        yield val
    }
}

var ite = gimme()
console.log(ite.next()) 
console.log(ite.next())
console.log(ite.next())

JavaScript中while(true)是糟糕的做法,yield让它变得可用。这里我们不再需要闭包保存变量。也不需要构造迭代器。代码更加清晰。

停止生成器

生成器的“异常结束”通常由break、return、或者未捕获异常引起。

如果生成器内有try...finally,它将总是运行(扫尾函数)。

4.3 异步迭代生成器
function request() {
    setTimeout(() => {
        ite.next(123)
    }, 500)
}

function *main() {
    try {
        var text = yield request();
        console.log(text);
    }
    catch (err) {
        console.log(err);
    }
}

var ite = main()

ite.next() // 123

同步错误处理

try {
  ite.throw(err)
} catch (err) {
  console.log(err)
}
4.4 生成器 + Promsie

构成了async/await语法

4.4.1 run()函数

自动运行传入的生成器,具体代码见书。

4.4.2 并发
function *foo() {
    var p1 = request(url1)
    var p2 = request(url2)
    var r1 = yield p1
    var r2 = yield p2
    var r3 = yield request(url3 + r1 + r2)
    console.log(r3)
}

实际上是实现了Promise.all([p1, p2])

很多时候抽象并不总是好事,很多时候会增加复杂度换取简洁性。

4.5 生成器委托

从一个生成器内部调用另一个生成器(也可以是迭代器),委托/转移控制权。

等于隐式的从调用者内部调用了run(generator)

4.5.1 委托的作用

主要目的是是代码组织,以达到与普通函数调用的对称。

4.5.2 消息委托

可以通过生成器迭代器实现双向通信。

4.5.3 异步委托
function *foo() {
    var r2 = yield request("http://some.url.2");
    var r3 = yield request("http://some.url.3/?v=" + r2);
    return r3
}
function *bar() {
    var r1 = yield request("http://some.url.1");
    var r3 = yield *foo();
}
4.5.4 递归委托
function *foo(val) {
    if(val > 1) {
        val = yield *foo(val -1)
    }
    return val
}
function *bar () {
    var a = yield *foo(3)
    return a 
}

var ite = bar()
console.log(ite.next()) // 1
4.6 生成器并发

runAll()

4.7 形实转换程序(thunk)

举例说明

function foo(x, y) {
    return x + y
}
function fooThunk() {
    return foo(3, 4)
}
// 将来调用
console.log(fooThunk()) // 7

与Promise构造对比(具体参见书)

4.8 手工变化(具体参见书)

第五章 | 程序性能

5.1 Web Worker

多线程架构的问题

  • 副线程会不会影响主线程
  • 能够访问共享的作用域和资源
  • 如何通信

首先Worker的能力是宿主环境提供的,可以开启多个JavaScript引擎实例各自运行在自己的线程上。

Worker和主程序之间不会共享任何作用域或资源。

以下是如何通信

var wx = new Worker("http://some.url.1/mycoolworker.js")
// 侦听时间
w1.addEventlistener('message', function(evt) {
    // evt.data
})
// 发送消息
w1.postMessage('something cool to say')
5.1.1 Worker环境

这是一个完全独立的线程。除了主程序的作用域无法共享,其他web提供的API基本都可以调用。

worker内部加载其他JavaScript文件是同步的,会阻塞该线程。

应用:

  • 处理密集型数学计算
  • 大数据集排序
  • 数据处理(压缩、音频分析、图像处理
  • 高流量网络通信
5.1.2 数据传递
  • 结构化克隆算法
  • Transferable对象
5.1.3 共享Worker

对于SPA应用不同组件间共享同一个Worker

var w1 = new ShareWork([path])

第六章 性能测试与调优

主要简单介绍了针对性能检测的几个库。

ES6建议尾部调用优化对支持TCO引擎友好。