一、let+const

暂时性死区

块级作用域优点

  • 避免内层变量覆盖外层;
  • 用来计数的循环变量泄露为全局变量。
  • 立即执行函数表达式(IIFE)不再必要了

块级作用域与函数声明

  • ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

  • ES6 引入了块级作用域,明确允许在块级作用域之中声明函数

  • 考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

const

  • const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动
  • 将对象冻结,应该使用Object.freeze方法。
    1
    2
    3
    4
    const foo = Object.freeze({});
    // 常规模式时,下面一行不起作用;
    // 严格模式时,该行会报错
    foo.prop = 123;

6种变量声明

  • var+function
  • let+const
  • import+class

顶层对象的属性

  • 顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象
  • 顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一
  • let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩

global 对象
很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
垫片库可以;

二、解构

1. 数组的解构赋值

本身就不具备 Iterator 接口:报错;

惰性求值
如果默认值是一个表达式,那么这个表达式是惰性求值的

1
2
3
4
function f() {
console.log('aaa');
}
let [x = f()] = [1];
2. 对象的解构赋值
  • 如果变量名与属性名不一致,必须写成下面这样。
    1
    2
    3
    4
    let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
    baz // "aaa"

    foo是匹配的模式,baz才是变量
  • 如果要将一个已经声明的变量用于解构赋值,必须非常小心。
    1
    2
    3
    4
    5
    6
    7
    8
    // 错误的写法
    let x;
    {x} = {x: 1};
    // SyntaxError: syntax error
    因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误
    只有不将大括号写在行首
    let x;
    ({x} = {x: 1});
3. 数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

1
2
3
4
5
let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true
4. 圆括号问题

对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。
只要有可能导致解构的歧义,就不得使用圆括号。
议只要有可能,就不要在模式中放置圆括号。
可以使用圆括号的情况只有一种:赋值语句非模式部分,可以使用圆括号。

1
2
3
[(b)] = [3]; // 正确
({ p: (d) } = {}); // 正确
[(parseInt.prop)] = [3]; // 正确
5. 用途
  • 交换变量的值
    [x, y] = [y, x];
  • 从函数返回多个值
  • 函数参数的定义
  • 函数参数的默认值
  • 提取 JSON 数据
  • 遍历 Map 结构

三、字符串的扩展

1. 字符的 Unicode 表示法
  • ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
2. codePointAt()+String.fromCodePoint()

JavaScript 内部,字符以 UTF-16的格式储存,每个字符固定为2个字节

1
2
3
4
5
6
7
8
9
10
11
let s = '𠮷a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}

function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}

is32Bit("𠮷") // true
is32Bit("a") // false

String.fromCharCode不能识别 32 位的 UTF-16 字符
String.fromCodePoint()
字符串的遍历器接口
遍历器最大的优点是可以识别大于0xFFFF的码点
at()
识别 Unicode 编号大于0xFFFF的字符
'𠮷'.at(0) // “𠮷”

normalize()

1
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
3. includes(), startsWith(), endsWith()

使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。

1
2
3
4
5
6
7
8
9
10
let s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true


s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false

repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。
padStart(),padEnd()
ES2017 引入了字符串补全长度的功能

4. 模板字符串
1
2
3
4
5
6
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());
5. 标签模板

用到时再看。

1
2
3
4
5
6
let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);