ES6学习笔记

简介

  • ECMAScript(ECMA或ES) 是规范, JavaScript的是其规范的实现
  • ECMAScript 2015 = ES2015 ≈ ES6

ECMAScript 的历史

时间 ECMA JS 解释
1996.11 ES 1.0 JS稳定 Netscape将JS提交给ECMA组织,ES正式出现
1998.06 ES 2.0 ES2正式发布
1999.12 ES 3.0 ES3被广泛支持
2007.10 ES 4.0 ES4过于激进,被废了
2008.07 ES 3.1 4.0退化为严重缩水版的3.1
因为吵得太厉害,所以ES 3.1代号为Harmony(和谐)
2009.12 ES 5.0 ES 5.0正式发布
同时公布了JavaScript.next也就是后来的ES 6.0
2011.06 ES 5.1 ES 5.1成为了ISO国际标准
2013.03 ES 6.0 ES 6.0草案定稿
2013.12 ES 6.0 ES 6.0草案发布
2015.06 ES 6.0 ES 6.0预计发布正式版
JavaScript.next开始指向ES 7.0

语法提案批准流程

Stage 0 - Strawman(展示阶段)
Stage 1 - Proposal(征求意见阶段)
Stage 2 - Draft(草案阶段)
Stage 3 - Candidate(候选人阶段)
Stage 4 - Finished(定案阶段)

由TC39 委员会批准

Let、const

  • 块级作用域
  • 不存在变量提升
    暂时性死区(temporal dead zone,简称 TDZ):在代码块内,声明变量之前,该变量都是不可用的。
  • 相同作用域内,不允许重复声明
  • 区别:
    • let声明变量
    • const声明只读常量,常量指向的那个内存地址不得改动,且一旦声明变量,就必须立即初始化。

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
适用于数组(这里泛指具有Iterator接口的数据结构)、对象、字符串、数值和布尔值,可设置默认值。

//数组
let [x, y = 'b', z='c'] = ['a', undefined, null];// x='a', y='b' z=null
//对象
let { bar, foo } = { foo: "aaa", bar: "bbb" };//foo = "aaa"; bar = "bbb"
//字符串,先转为数组
const [a, b, length : len] = 'ho';//a="h";b= "o";len = 2
//数值与布尔值,先转为对象
let {toString: s} = 123;//s = Number.prototype.toString
let {toString: s} = true;//s = Boolean.prototype.toString
  • 如果解构不成功,变量的值就等于undefined
  • 只有当成员严格等于undefined,默认值才会生效
  • 两边的结构要一致
  • 结构完立即赋值,结构和赋值不能分开进行
  • 使用于对象时,键对键,值对值;且大括号不能在行首(会理解成语法块),可用小括号包裹
  • 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象

字符串扩展

查找

  • includes(String):是否找到参数字符串。
  • startsWith(String):是否以参数字符串开头。
  • endsWith(String):是否以参数字符串结尾。
let s = 'Hello world!';
s.includes('o') // true
//均可接受第二个位置参数

重复

  • repeat(Number)
'x'.repeat(3) // "xxx"

模板字符串

  • 支持变量、表达式、函数调用,允许嵌套
  • 反引号需使用反斜杠转义
  • trim消除空格和换行
$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);
//trim
$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim());

数值的扩展

  • Number.isFinite()-只对数值返回true
  • Number.isNaN()-只对NaN返回true
  • Number.isInteger()-只对整数返回true
  • Number.isSafeInteger()-只对安全整数(-253到253,不含端点)返回true
  • Number.MAX_SAFE_INTEGER,Number.MIN_SAFE_INTEGER安全整数的上下限常量
Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger(25.1) // false

Math对象扩展:http://es6.ruanyifeng.com/#docs/number#Math-对象的扩展


函数的扩展

  • 参数默认值,建议用于尾参数,不可在函数内再次声明
function log(x, y = 'World') {}
  • length:从第一个参数到首个指定默认值参数间参数的个数
(function (a, b = 1, c) {}).length // 1
  • rest:获取函数的多余参数,须用于尾参数
function add(...values) {}
add(2, 5, 3)
  • name:函数的名字
function f() {}
//或者
var f = function () {};
f.name // "f"

箭头函数

  • 只有一个参数,可省略参数的小括号,无参数时不可省略
  • 如果只有一条语句且无return,可以省略语句块的大括号
  • 如果只有一条语句且有return,可以省略大括号和return,仅有return(即return后无参数)时不可省略
  • 直接返回对象,须使用小括号包裹
let getTempItem = id => ({ id: id, name: "Temp" });
  • 可以使用解构赋值,可以嵌套
const full = ({ first, last }) => first + ' ' + last;
const pipeline = (...funcs) =>
  val => funcs.reduce((a, b) => b(a), val);
  • 注意:
    • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
    • 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
    • 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
    • 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

数组的扩展

扩展运算符

用于数组赋值须放在参数的最后一位。

console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5
//复制数组
const a1 = [1, 2];
const a2 = [...a1];// 写法一
const [...a2] = a1;// 写法二
//合并数组
const a2 = [3, 4];
[...a1, ...a2] //[1,2,3,4]
//配合解构赋值
const [first, ...rest] = [1, 2, 3, 4, 5];// first= 1;rest= [2, 3, 4, 5]
//字符串
[...'hello'] // [ "h", "e", "l", "l", "o" ]

新增方法

  • Array.from():将数组对象(有length属性)或可遍历的对象(包括 ES6 新增的数据结构 Set 和 Map)转为真正的数组,接受第二个参数(对每个元素进行处理,处理值返回数组)。
let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
  • Array.of():将一组值转换为数组。
Array.of(3, 11, 8) // [3,11,8]
  • copyWithin(target[必需,替换开始位置,负值为倒数], start = 0[可选,读取开始位置,负值为倒数], end = this.length[可选,读取结束位置,负值为倒数]):在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。
[1, 2, 3, 4, 5].copyWithin(0, 3) // [4, 5, 3, 4, 5]
  • find(fn)findIndex(fn):找出第一个符合条件的数组成员。如果无符合条件的成员,返回undefined。
//find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
[1, 5, 10, 15].find(function(value, index, arr) {
  return value > 9;
}) // 10
  • fill(text[必需,填充内容],start = 0[可选,开始位置,负值为倒数], end = this.length[可选,结束位置,负值为倒数]): 使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
  • includes(text[必需,内容],start = 0[可选,搜索开始位置,负值为倒数], end = this.length[可选,搜索结束位置,负值为倒数]):搜索数组是否包含给定的值。
[1, 2, 3].includes(3, -1); // true

对象的扩展

  • 简洁表示:对象的key和value一样时,可以省略key,方法可省略function
const foo = 'bar';
const baz = {
	foo,
   method() {
    return "Hello!";
  }
}
  • 属性名表达式: 不能使用简洁表示
let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
}
  • Object.is(): 判断两值是否相等
//和===的区别
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
  • Object.assign(target[目标对象], source1, source2,...): 将源对象所有可枚举属性复制合并到目标对象。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
  • Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
  • 遇到同名属性,Object.assign的处理方法是后面替换前面的。
  • Object.assign可以用来处理数组,但是会把数组视为对象。
  • Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

Set和Map数据结构

Set(集合)

集合:集合是由一组无序且唯一(即不能重复)的项组成的,可以想象成集合是一个既没有重复元素,也没有顺序概念的数组

基本用法

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成 Set 数据结构。

const s = new Set();

[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));

for (let i of s) {
  console.log(i);
}
// 2 3 5 4
// 去除数组的重复成员
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]

向 Set 加入值的时候,不会发生类型转换。

属性和方法

Set 结构的实例有以下属性。

  • size:返回Set实例的成员总数。
实例操作方法(用于操作数据)
  • add(value):添加某个值,返回 Set 结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。
s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2);
s.has(2) // false
遍历方法(用于遍历成员)

Set 结构的实例有四个遍历方法,可以用于遍历成员。

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。

let set = new Set(['red', 'green', 'blue']);

for (let item of set.keys()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.values()) {
  console.log(item);
}
// red
// green
// blue

for (let item of set.entries()) {
  console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

WeakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。

首先,WeakSet 的成员只能是对象,而不能是其他类型的值;

WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存。

语法

WeakSet 是一个构造函数,可以使用new命令,创建 WeakSet 数据结构。

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}

方法

WeakSet 结构有以下三个方法。

  • add(value):向 WeakSet 实例添加一个新成员。
  • delete(value):清除 WeakSet 实例的指定成员。
  • has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。

Map(字典)

类似对象,唯一的区别是key的范围不限于字符串,各种类型的值(包括对象)都可以当作键。主要用于数据存储

const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"

属性和方法

  • size:属性返回 Map 结构的成员总数。
  • set(key, value):
    set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
const m = new Map();

m.set('edition', 6)        // 键是字符串
m.set(262, 'standard')     // 键是数值
m.set(undefined, 'nah')    // 键是 undefined
//或者
let map = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');
  • get(key):
    get方法读取key对应的键值,如果找不到key,返回undefined。
  • has(key):
    has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
  • delete(key):
    delete方法删除某个键,返回true。如果删除失败,返回false。
  • clear():
    clear方法清除所有成员,没有返回值。

遍历:

提供三个遍历器生成函数和一个遍历方法。Map 的遍历顺序就是插入顺序

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历 Map 的所有成员。

数据结构转换

  • Map 转为数组
const myMap = new Map()
  .set(true, 7)
  .set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  • 数组 转为 Map
new Map([
  [true, 7],
  [{foo: 3}, ['abc']]
])
// Map {
//   true => 7,
//   Object {foo: 3} => ['abc']
// }
  • Map 转为对象
function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

const myMap = new Map()
  .set('yes', true)
  .set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
  • 对象转为 Map
function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
  • Map 转为 JSON

Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。

function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。

function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}

let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
  • JSON 转为 Map

JSON 转为 Map,正常情况下,所有键名都是字符串。

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}

但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是数组转为 JSON 的逆操作。

function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}

WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合。
区别:

  • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
  • WeakMap的键名所指向的对象,不计入垃圾回收机制。

区别

集合又和字典有什么区别呢:

  • 共同点:集合、字典可以存储不重复的值
  • 不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储

Promise对象

Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件),更合理和更强大。

  • 特点:
    • 对象的状态[pending(进行中)、fulfilled(已成功)和rejected(已失败)]不受外界影响
    • 一旦状态改变,就不会再变,任何时候得到的都是这个结果
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value); //将Promise对象的状态从“未完成”变为“成功”,并将异步操作的结果,作为参数传递出去
  } else {
    reject(error); //将Promise对象的状态从“未完成”变为“失败”,并将异步操作报出的错误,作为参数传递出去
  }
});
//调用
promise.then(value=> {
  // success
}, error=> { //可选
  // failure
});
//或者
promise.then(value => {
  // success
}).catch(error => {
  // failure
});
  • Promise.all([p1, p2, ...]): 将多个 Promise 包装成一个新的 Promise ,状态和值取决于里面的实例(fulfilled:需均成功,返回包含每个实例返回值的数组;rejected:至少一个失败,返回第一个失败的实例返回值)
  • Promise.race([p1, p2, ...]): 将多个 Promise 包装成一个新的 Promise,状态和参数均由里面第一个改变的状态的实例决定
  • Promise.resolve():简写,将现有对象转为 状态为fulfilled的Promise 对象。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
  • Promise.reject():简写,将现有对象转为 状态为rejected的Promise 对象。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

Iterator(遍历器)

概念

数据集合:主要是Array,Object,Map,Set。
遍历器(Iterator)为上述数据集合提供了统一的访问机制。

Iterator 的作用有三个:
  • 一是为各种数据结构,提供一个统一的、简便的访问接口;
  • 二是使得数据结构的成员能够按某种次序排列;
  • 三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。

for...of 循环

const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}
区别:
  • for...in遍历键名,for...of遍历键值
var arr = ['a', 'b', 'c', 'd'];

for (let a in arr) {
  console.log(a); // 0 1 2 3
}

for (let a of arr) {
  console.log(a); // a b c d
}
  • for...of可以配合break、continue和return使用
for (var n of fibonacci) {
  if (n > 1000)
    break;
  console.log(n);
}

Generator

概念

Generator 函数是 ES6 提供的一种异步编程解决方案。
Generator 函数是一个状态机,封装了多个内部状态。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,必须调用next()才会执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(yield)。

yield 表达式

遍历器对象的next方法的运行逻辑如下。

  • (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
  • (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
  • (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
  • (4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
  • yield其實是兩個動作的合體:丟東西出去->等東西進來;
    每次next()都會跑到yield丟東西出來的那個步驟
//输入输出:yield右边为输出(next返回值的value,最后一次由return返回),左边为输入(接受的是next的参数,第一次由函数参数传入),
function * input(){
    let array = [], i = 4;
    while(i) {
        array.push(yield array);
        i --;
    }
}

var gen = input();
console.log(gen.next("西")) // { value: [], done: false }
console.log(gen.next("部")) // { value: [ '部' ], done: false }
console.log(gen.next("世")) // { value: [ '部', '世' ], done: false }
console.log(gen.next("界")) // { value: [ '部', '世', '界' ], done: false }

next 方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

for...of 循环

for...of循环可以自动遍历 Generator 函数时生成的Iterator对象,且此时不再需要调用next方法。

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6;
}

for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

next()、throw()、return() 的共同点

next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

  • next()是将yield表达式替换成一个值。
const g = function* (x, y) {
  let result = yield x + y;
  return result;
};

const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}

gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
  • throw()是将yield表达式替换成一个throw语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
  • return()是将yield表达式替换成一个return语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;

yield* 表达式

在 Generator 函数内部,调用另一个 Generator 函数

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

作为对象属性的 Generator 函数

let obj = {
  * myGeneratorMethod() {
    ···
  }
};
//等价于
let obj = {
  myGeneratorMethod: function* () {
    // ···
  }
};

应用

  • ajax
function* main() {
  var result = yield request("http://some.url");
  var resp = JSON.parse(result);
    console.log(resp.value);
}

function request(url) {
  makeAjaxCall(url, function(response){
    it.next(response);
  });
}

var it = main();
it.next();

更多Generator 函数的异步应用:http://es6.ruanyifeng.com/#docs/generator-async

http://jsfiddle.net/Yoghurts/xnqofbep/4/
ES7引入 async await,使其更加好用

利用ES6中的Generator实现并发编程

参考资料

ES6实现

// 暂停
function sleep(numberMillis) {
    var now = new Date();
    var exitTime = now.getTime() + numberMillis;
    while (true) {
        now = new Date();
        if (now.getTime() > exitTime){
            return;
        }
    }
}

// 消费者
function* consumer(name) {
    console.log(`${name}准备吃包子啦!`);
    while (true) {
        var baozi = yield;
        baozi += 1;
        console.log(`第${baozi}个包子来了,被分成了两份,一份被${name}吃了!`);
    }
}

// 生产者
function producer(name) {
    c1 = consumer('A');
    c2 = consumer('B');
    console.log(`${name}:我开始准备做包子了!`);
    c1.next();
    c2.next();
    for (let i = 0; i < 10; i++) {
        sleep(1000);
        c1.next(i);
        c2.next(i);
    }
}

// 小明开始生产包子,A和B同时开始吃包子
producer('小明')

运行结果

小明:我开始准备做包子了!
A准备吃包子啦!
B准备吃包子啦!
第1个包子来了,被分成了两份,一份被A吃了!
第1个包子来了,被分成了两份,一份被B吃了!
第2个包子来了,被分成了两份,一份被A吃了!
第2个包子来了,被分成了两份,一份被B吃了!
第3个包子来了,被分成了两份,一份被A吃了!
第3个包子来了,被分成了两份,一份被B吃了!
第4个包子来了,被分成了两份,一份被A吃了!
第4个包子来了,被分成了两份,一份被B吃了!
第5个包子来了,被分成了两份,一份被A吃了!
第5个包子来了,被分成了两份,一份被B吃了!
第6个包子来了,被分成了两份,一份被A吃了!
第6个包子来了,被分成了两份,一份被B吃了!
第7个包子来了,被分成了两份,一份被A吃了!
第7个包子来了,被分成了两份,一份被B吃了!
第8个包子来了,被分成了两份,一份被A吃了!
第8个包子来了,被分成了两份,一份被B吃了!
第9个包子来了,被分成了两份,一份被A吃了!
第9个包子来了,被分成了两份,一份被B吃了!
第10个包子来了,被分成了两份,一份被A吃了!
第10个包子来了,被分成了两份,一份被B吃了!

Class

定义类

//以前
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

//es6
class Point {
//构造方法
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}
var p = new Point(1, 2);

继承类

class Point {
}

class ColorPoint extends Point {
}

module

简介

在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

严格模式

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";

严格模式主要有以下限制:

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • eval和arguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.caller和fn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protected、static和interface)

export 命令

定义模块的对外接口。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
以下是几种用法:

//------输出变量------
export var firstName = 'Michael';
export var lastName = 'Jackson';
//等价于
var firstName = 'Michael';
export {firstName}; //推荐,能清除知道输出了哪些变量
//------输出函数或类------
export function multiply(x, y) {
  return x * y;
};
//------输出并as重命名------
var v1 = 'Michael';
function v2() { ... }
export {
  v1 as streamV1,
  v2 as streamV2
};
//------输出default------
export default function () { ... }

注意:export default在一个模块中只能有一个。

import 命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
以下是几种用法,必须和上面的export对应:

//------加载变量、函数或类------
import {firstName, lastName} from './profile.js';
//------加载并as重命名------
import { lastName as surname } from './profile.js';
//------加载有default输出的模块------
import v1 from './profile.js';
//------执行所加载的模块------
import 'lodash';
//------加载模块所有输出------
import  * as surname from './profile.js';

复合写法

如果在一个模块之中,先输入后输出同一个模块,import语句可以与export语句写在一起。

export { foo, bar } from 'my_module';

// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };

Symbol

原始数据类型Symbol,表示独一无二的值。

let s = Symbol();

typeof s
// "symbol"

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

let s1 = Symbol('foo');
let s2 = Symbol('bar');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(foo)"
s2.toString() // "Symbol(bar)"

Proxy

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

var proxy = new Proxy(target, handler);
  • target参数表示所要拦截的目标对象
  • handler参数也是一个对象,用来定制拦截行为。
var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return target.key;
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return target.key=value;
  }
});

愿你走出半生,归来仍是少年