入门ES6最佳选择,读完收获颇多。但关注点主要还是放在一些新的编程模式,比如 Promise 和 Generator 带来的对前端编程逻辑的巨大改变(甚至我认为, ES6中 Promise和 Generator 才是主菜,其他不过是饭前甜点罢了)。除此以外,很多以前在 ES5 中蛋疼的实现有了更优雅的实现,比如块作用域和变量提升,函数中万变的 this 等。很多东西碍于实现,在前端中可能并不会全部用上,但是至少,配合 polyfill 和打包工具,让我们在工程化中有了更进一步的统一

ECMAScript6入门

let 和 const 命令 !

  • 不存在变量提升
  • 存在块级作用域
  • 暂时性死区 => (typeof 操作符不再百分百安全)
  • 不允许重复声明
  • 安全起见,不在作用域块中声明函数(浏览器实现)
  • const 声明后不允许改变(对于引用类型,指地址不变)
  • ES6 中六种声明变量语法(varletconstfunctionimportclass
  • letconst 声明的全局变量不再是顶级对象的属性

变量的解构赋值 !

  • 数组:let [a, b, c] = [1, 2, 3]
  • 对象:let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; // foo => "aaa"
  • 等号邮编必须具备迭代器接口(如数组、对象)
  • 允许指定默认值:let [x, y = 'b'] = ['a']; // x => 'a', y => 'b'
  • 默认值的判断使用严格等于 undefined,如果默认值是一个表达式或函数,则进行惰性求值
  • 字符串的解构赋值:const [a, b, c, d, e] = 'hello'
  • 如果等号右边是不是对象或数组,则先转为对象,而 undefinednull 无法转为对象导致报错

字符串的扩展 !

  • 直接遍历方法 for...of

    1
    2
    3
    for (let key of 'foo') {
    console.log(key) // 'f','o','o'
    }
  • includes()startsWith()endsWith()

    • includes():返回布尔值,表示是否找到了参数字符串
    • startsWith():返回布尔值,表示参数字符串是否在源字符串的头部
    • endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部
  • repeat(n) 返回一个新字符串,表示将原字符串重复n次

  • padStart()padEnd() 字符串不够指定长度,会在头部或尾部补全
  • 模板字符串 foo ${fn()} bar-${name}

数值的扩展

  • Number.isFinite()Number.isNaN() 只对数值有效,不转换
  • 全局方法 parseInt()parseFloat() 移植到了 Number 对象上
  • Number.isInteger() 用来判断一个值是否为整数
  • 新增一个极小的常量 Number.EPSILON,一般用来判断误差精度
  • Math.trunc(number) 方法用于去除一个数的小数部分,返回整数部分
  • Math.sign(number) 方法用来判断一个数到底是正数、负数、还是零
  • 指数运算符
    1
    2
    3
    2 ** 2 // 4
    2 ** 3 // 8
    a **= 2 // 等同于 a = a * a;

数组的扩展

  • Array.from(arrayLike, fn, scope) 将类数组(具备 Iterator 接口,即具有 length 属性)对象转为真正的数组,类似 Array.prototype.slice.call(arrayLike)
  • Array.of 方法用于将一组值,转换为数组 => 弥补数组构造函数 Array() 的不足。因为参数个数的不同,会导致 Array() 的行为有差异
  • copyWithin 方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组
    [NaN].indexOf(NaN) // -1,而 find()findIndex() 则可以找到 NaN
  • fill(times, start, end) 方法使用给定值,填充一个数组
  • entries()keys()values() 分别遍历键值对、键名、键值
  • ES6 明确将稀疏数组的空位转为 undefined(ES5的迭代器方法则忽略空位)

函数的扩展 !

默认值

  • 默认值 => function log(x, y = 'World') {}(参数变量是默认声明的,所以不能用 letconst 再次声明,也不能有同名参数)
  • 如果参数默认值是变量,那么参数就不是传值的,而是每次都重新计算默认值表达式的值
  • 双重默认值配合解构赋值

    1
    2
    3
    4
    function fetch (url, { body = '', method = 'GET' } = {}) {
    console.log(method)
    }
    fetch('http://example.com') // 可以不传入第二个参数同时有默认值
  • 定义默认值的参数应该是尾参数(不然没法省略,自然用不上默认值)

  • 函数的 length 属性不包含有默认值的形参(也不包含rest参数)
    1
    2
    3
    // 如果设置了默认值的参数不是尾参数,那么 length 属性也不再计入后面的参数了
    (function (a = 0, b, c) {}).length // 0
    (function (a, b = 1, c) {}).length // 1

rest参数

  • ...rest 参数可以替代 arguments 对象,是数组对象,可以直接使用数组方法
  • ...rest 参数后面不能有其他参数,否则报错
  • 函数的 length 属性不包含 ...rest 参数

扩展运算符

  • ... 扩展运算符 => 将数组转化为逗号分离的参数,类似 ...rest 参数的逆运算
  • 合并数组:[1, 2, ...array]
  • 将字符串转成数组:[...'hello'] // [ "h", "e", "l", "l", "o" ]

箭头函数

1
2
3
4
5
6
7
var f = v => v;
var g = () => 5;
// 上面的箭头函数等同于
var f = function (v) {
return v;
};
var g = function () { return 5 };
  • 如果箭头函数直接返回一个对象,必须在对象外面加上括号 var getTempItem = id => ({ id: id, name: "Temp" })
  • this 对象指向父级,不可以用 new 调用,没有 arguments 对象,不能使用 yield,(实际上并没有自己的 this
  • 箭头函数没有自己的 this,所以当然也就不能用 call()apply()bind() 这些方法去改变 this 的指向
  • 多重嵌套函数(难点)
  • 尾调用优化(ES6 的尾调用优化只在严格模式下开启)

    尾调用(Tail Call)指某个函数的最后一步是调用另一个函数
    function f(x) { return g(x) }
    闭包一般是没有做到尾调用优化的,因为定义上不允许

  • 尾递归 => 尾调用自身,只要使用尾递归,永远不会发生“栈溢出”(stack overflow),函数编程可用递归实现循环(递归本质上是一种循环操作)

    • 阶乘函数优化
    • 斐波那契数列优化
    • 柯里化和参数默认值
  • 手动实现尾递归优化(核心是减少调用栈) => 使用循环代替递归
    • 蹦床函数

其他

  • 只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错
  • 如果将一个匿名函数赋值给一个变量,ES5 的 name 属性,会返回空字符串,而 ES6 的 name 属性会返回实际的函数名
  • 尾调用 => 某个函数的最后一步是调用另一个函数
  • 函数调用自身,称为递归。如果尾调用自身,就称为尾递归

对象的扩展 !

  • 属性的简洁表示法 => 简洁写法的属性名总是字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var birth = '2000/01/01';
    var Person = {
    name: '张三',
    //等同于 birth: birth
    birth,
    // 等同于 hello: function ()...
    hello() {
    console.log('我的名字是', this.name);
    }
    };
  • Object.is() 类似全等符 === ,但 +0 不等于 -0NaN 等于自身 => Object.is('foo', 'foo')

  • Object.assign 方法用于对象的合并,将源对象的所有自身可枚举属性,复制到目标对象 => Object.assign(target, source1, source2),浅复制
  • 属性的遍历
    • for...in 循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
    • Object.keys 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)
    • Object.getOwnPropertyNames 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)
    • Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性。
    • Reflect.ownKeys 返回一个数组,包含对象自身的所有属性,不管是属性名是 Symbol 或字符串,也不管是否可枚举

Symbol

新的原始数据类型,表示独一无二的值,常用来作为对象属性名避免冲突

  • Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述
  • 可显示转换成字符串或者布尔值
  • 作为对象属性名或在对象的内部定义属性时,Symbol 值必须放在方括号之中

    1
    2
    3
    4
    5
    6
    7
    // 第一种写法
    var a = {};
    a[mySymbol] = 'Hello!';
    // 第二种写法
    var a = {
    [mySymbol]: 'Hello!'
    };
  • Symbol 作为属性名,该属性不会出现在 for...infor...of 循环中,也不会被 Object.keys()Object.getOwnPropertyNames()JSON.stringify() 返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。

  • 重用 Symbol 使用 Symbol.for,该方法生成的变量会登记在全局变量中

Set 和 Map 数据结构

Set

set 类似于数组,但是成员的值都是唯一的,没有重复的值

1
2
3
4
5
6
7
8
// 定义方法 1
const set1 = new Set([1, 2, 3])
// 定义方法 2
const set2 = new Set()
set.add(1)
set.add(2)
set.add(3)
  • Set 结构没有键名,只有键值(或者说键名和键值是同一个值)
  • Array.from 方法可以将 Set 结构转为数组

    1
    2
    3
    4
    5
    6
    // 数组去重新方法1
    function dedupe(array) {
    return Array.from(new Set(array));
    }
    // 数组去重新方法2
    let unique = [...new Set(arr)]
  • WeakSet 结构与 Set 类似,也是不重复的值的集合

    • WeakSet 的成员只能是对象,而不能是其他类型的值
    • WeakSet 中的对象都是弱引用,不计入垃圾回收机制,也是不可遍历的

Map

Map 类似于对象,但属性名不限制为字符串(可是是任何数据结构,包括对象),由此实现更强大的hash结构

1
2
3
4
5
6
7
8
9
10
// 定义方法 1
const map1 = new Map([
['name', '张三'],
['title', 'Author']
])
// 定义方法 2
const map2 = new Map()
const o = {p: 'Hello World'}
m.set(o, 'content')
  • 注意,只有对同一个对象的引用,Map 结构才将其视为同一个键,同样的值的两个实例,在 Map 结构中被视为两个键
  • WeakMap结构与Map结构类似,也是用于生成键值对
    • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名
    • WeakMap的键名所指向的对象,不计入垃圾回收机制

Proxy和Reflect

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,属于一种“元编程”

  • var proxy = new Proxy(target, handler);

Reflect

与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API,而且它与 Proxy 对象的方法是一一对应的
将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty),放到 Reflect 对象上
修改某些 Object 方法的返回结果,让其变得更合理

编程风格

  • 优先使用 const、然后是 let,不用 var,避免变量泄露和提升
  • 使用解构赋值声明多个变量 => const [a, b, c] = [1, 2, 3]
  • 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
  • 单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾
  • 使用扩展运算符 ... 拷贝数组
  • 如果模块默认输出一个函数,函数名的首字母应该小写,如果模块默认输出一个对象,对象名的首字母应该大写