一,JS部分
1,Array(9).fill(null) --定义一个为9的数组,默认填充null
2,stringObject.slice(start,end)
arrayObject.slice(start,end),与string对象的用法相似,返回一个新的数组。
- start :要抽取的片断的起始下标。如果是负数,则该参数规定的是从字符串的尾部开始算起的位置。也就是说,-1 指字符串的最后一个字符,-2 指倒数第二个字符,以此类推。
- var str=“Hello happy world!”
document.write(str.slice(6))
happy world! - var str=“Hello happy world!”
document.write(str.slice(6,11))
–happy
推荐资源
MDN (Mozilla Developer Network)
你所不知道的JS (书)
ES6 新特性和例子
WesBos 的博客 (ES6)
Reddit (JavaScript)
用 Google 来查找特定的博客和资源
StackOverflow
3,Object.assign()
var nObj = Object.assign({},obj,obj1);//花括号叫目标对象,后面的obj、obj1是源对象。对象合并是指:将源对象里面的属性添加到目标对象中去,若两者的属性名有冲突,后面的将会覆盖前面的
let obj = {name: '二月', age: {c: 12}}
let age = {c: 88}
let o2 = Object.assign({}, obj, {age})
o2.age.c = 66
console.log(obj, o2)
用ES6的扩展运算符来替换Object.assign
let obj = {name: '二月', age: {c: 12}}
let o1 = {...obj, age: {c: 88}}
o1.age.c = 99
console.log(obj, o1)
Object.assign(target, …sources),第一个参数target决定了你的对象被拷贝到哪个目标对象上面,如果你不想对原始对象产生影响,就定义一个空对象{}
4,push(),concat()方法的区别
var array=[1,2,3,4,5];
console.log(array); //[1, 2, 3, 4, 5]
array.push(6,7); //两个参数
console.log(array); //[1, 2, 3, 4, 5, 6, 7]
array.push([6,7]); //参数为数组
console.log(array); //[1, 2, 3, 4, 5, Array(2)]
var array=[1,2,3,4,5];
console.log(array); //[1, 2, 3, 4, 5]
var array2=array.concat(6,7); //两个参数
console.log(array); //[1, 2, 3, 4, 5]
console.log(array2); //[1, 2, 3, 4, 5, 6,7]
var array2=array.concat([6,7]); //参数为数组
console.log(array); //[1, 2, 3, 4, 5]
console.log(array2); //[1, 2, 3, 4, 5, 6, 7]
push()是在原数组的基础上修改的,执行push()方法后原数组的值也会变;concat()是先把原数组复制到一个新的数组,然后在新数组上进行操作,所以不会改变原数组的值。
如果参数不是数组,不管参数个数有多少个,push()和concat()都会直接把参数添加到数组后;如果参数是一个数组,push()就会直接把数组添加到原数组后,而concat()会把数组里的值取出来添加到原数组后
5,map
原数组被“映射”成对应新数组。
var data = [1, 2, 3, 4];
var arrayOfSquares = data.map(function (item) {
return item * item;
});
alert(arrayOfSquares); // 1, 4, 9, 16
可以利用map方法方便获得对象数组中的特定属性值们
var users = [
{name: "张含韵", "email": "zhang@email.com"},
{name: "江一燕", "email": "jiang@email.com"},
{name: "李小璐", "email": "li@email.com"}
];
var emails = users.map(function (user) { return user.email; });
console.log(emails.join(", ")); // zhang@email.com, jiang@email.com, li@email.com
6,filter()
filter()方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
var arr = ['1','2',undefined, '3.jpg',undefined]
var newArr = arr.filter(item => item)
console.log(newArr)
var arr = ['1','2',null, '3.jpg',null]
var newArr = arr.filter(item => item)
console.log(newArr)
var arr = ['1','2','', '3.jpg','']
var newArr = arr.filter(item => item)
console.log(newArr)
var arr = ['10','12','23','44','42']
var newArr = arr.filter(item => item.indexOf('2')<0)
console.log(newArr)
数组去重
var arr = [1, 2, 2, 3, 4, 5, 5, 6, 7, 7,8,8,0,8,6,3,4,56,2];
var arr2 = arr.filter((x, index,self)=>self.indexOf(x)===index)
console.log(arr2); //[1, 2, 3, 4, 5, 6, 7, 8, 0, 56]
map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身
7,reduce()
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
注意: reduce() 对于空数组是不会执行回调函数的。
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
initialValue 可选。传递给函数的初始值
function(total,currentValue, index,arr) 必需。用于执行每个数组元素的函数。
total 必需。初始值, 或者计算结束后的返回值。
currentValue 必需。当前元素
currentIndex 可选。当前元素的索引
arr 可选。当前元素所属的数组对象。
var items = [10, 120, 1000];
var reducer = function add(sumSoFar, item) { return sumSoFar + item; };
var total = items.reduce(reducer, 0);
console.log(total); // 1130
reduce函数的返回结果类型和传入的初始值相同,上个实例中初始值为number类型,同理,初始值也可为object类型。
var items = [10, 120, 1000];
var reducer = function add(sumSoFar, item) {
sumSoFar.sum = sumSoFar.sum + item;
return sumSoFar;
};
var total = items.reduce(reducer, {sum: 0});
console.log(total); // {sum:1130}
使用reduce方法可以完成多维度的数据叠加。
var reducers = {
totalInEuros : function(state, item) {
return state.euros += item.price * 0.897424392;
},
totalInYen : function(state, item) {
return state.yens += item.price * 113.852;
}
};
var manageReducers = function(reducers) {
return function(state, item) {
return Object.keys(reducers).reduce(
function(nextState, key) {
reducers[key](state, item);
return state;
},
{}
);
}
};
var bigTotalPriceReducer = manageReducers(reducers);
var initialState = {euros:0, yens: 0};
var items = [{price: 10}, {price: 120}, {price: 1000}];
var totals = items.reduce(bigTotalPriceReducer, initialState);
console.log(totals);
再看一个例子,如何知道一串字符串中每个字母出现的次数?
var arrString = 'abcdaabc';
const aa=arrString.split('').reduce(function(res, cur) {
res[cur] ? res[cur] ++ : res[cur] = 1
return res;
}, {})
console.log(aa) //{a: 3, b: 2, c: 2, d: 1}
由于可以通过第二参数设置叠加结果的类型初始值,因此这个时候reduce就不再仅仅只是做一个加法了,我们可以灵活的运用它来进行各种各样的类型转换,比如将数组按照一定规则转换为对象,也可以将一种形式的数组转换为另一种形式的数组,
[1, 2].reduce(function(res, cur) {
res.push(cur + 1);
return res;
}, [])
koa的源码中,有一个only模块,整个模块就一个简单的返回reduce方法操作的对象:
var only = function(obj, keys){
obj = obj || {};
if ('string' == typeof keys) keys = keys.split(/ +/);
return keys.reduce(function(ret, key){
if (null == obj[key]) return ret;
ret[key] = obj[key];
return ret;
}, {});
};
通过对reduce概念的理解,这个模块主要是想新建并返回一个obj对象中存在的keys的object对象。
var a = {
env : 'development',
proxy : false,
subdomainOffset : 2
}
only(a,['env','proxy']) // {env:'development',proxy : false}
map、filter、reduce
const students = [
{ name: "Nick", grade: 10 },
{ name: "John", grade: 15 },
{ name: "Julia", grade: 19 },
{ name: "Nathalie", grade: 9 },
];
const aboveTenSum = students
.map(student => student.grade) // 我们将学生数组映射到他们成绩的数组中
.filter(grade => grade >= 10) // 我们过滤成绩数组以保持10分以上的元素
.reduce((prev, next) => prev + next, 0); // 我们将合计所有10分以上的成绩
console.log(aboveTenSum) // 44
Understanding map / filter / reduce in JS
8,在 JavaScript 中,有 3 个关键字可用于声明一个变量, var ,let 和 const
function myFunction() {
var myVar = "Nick";
if (true) {
var myVar = "John";
console.log(myVar); // "John"
// 实际上,myVar是函数作用域,我们只是将之前 myVar 的值 "Nick" 抹去,重新声明为 "John"
}
console.log(myVar); // "John" - 看看 if 语句块中的指令是如何影响这个值的
}
console.log(myVar); // 抛出错误 ReferenceError, 在函数之外 myVar 则无法访问
在执行过程中,var声明的变量被移动到范围的顶部
console.log(myVar) // undefined -- 不会报错
var myVar = 2;
在执行时,被解析为:
JavaScript 代码:
var myVar;
console.log(myVar) // undefined -- 不会报错
var 和 let 大致相同,但是用 let 声明的变量时有以下几个特性:
块级作用域( block scoped )
在被分配之前, 无法 访问使用
在同一个作用域之下,不能重新声明
function myFunction() {
let myVar = "Nick";
if (true) {
let myVar = "John";
console.log(myVar); // "John"
// 实际上 myVar 在块级作用域中,(在 if 语句块中)我们只是创建了一个新变量 myVar。
// 此变量在 if 语句块之外不可访问,
// 完全独立于创建的第一个 myVar 变量!
}
console.log(myVar); // "Nick", 可以看到 if 语句块中的指令不会影响这个值
}
console.log(myVar); // 抛出错误 ReferenceError,myVar 无法在函数外部被访问。
let 和 const 变量声明时也存在提升,但并不代表它们的赋值也会被提升。但由于它被设计成了赋值之前无法使用,
JavaScript 中的 Hoisting (变量提升和函数声明提升)
暂存死区(Temporal dead zone)
变量提升
扩展阅读
How let and const are scoped in JavaScript – WesBos
Temporal Dead Zone (TDZ) Demystified
9,箭头函数
在一个箭头函数中,this 等同于封闭执行上下文的 this 值。这意味着,一个箭头函数并不会创造一个新的 this,而是从它的外围作用域中抓取的。
Arrow functions introduction – WesBos
JavaScript arrow function – MDN
10,解构对象和数组
const person = {
firstName: "Nick",
lastName: "Anderson",
age: 35,
sex: "M"
}
const { firstName: first, age, city = "Paris" } = person; // 就是这么简单!
console.log(age) // 35 -- 一个新变量 age 被创建,并且其值等同于 person.age
console.log(first) // "Nick" -- 一个新变量 first 被创建,并且其值等同于 person.firstName A new variable first is created and is equal to person.firstName
console.log(firstName) // Undefined -- person.firstName 虽然存在,但是新变量名为 first
console.log(city) // "Paris" -- 一个新变量 city 被创建,并且因为 person.city 为 undefined(未定义) ,所以 city 将等同于默认值也就是 "Paris"。
注意: 在 const { age } = person; 中, const 关键字后的括号不是用于声明对象或代码块,而是 解构(structuring) 语法。
解构(structuring) 经常被用来解构函数中的对象参数。
不使用解构:
function joinFirstLastName(person) {
const firstName = person.firstName;
const lastName = person.lastName;
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson"
在解构对象参数 person 这个参数时,我们可以得到一个更简洁的函数:
function joinFirstLastName({ firstName, lastName }) { // 通过解构 person 参数,我们分别创建了 firstName 和 lastName 这两个变数
return firstName + '-' + lastName;
}
joinFirstLastName(person); // "Nick-Anderson
箭头函数中使用解构
const joinFirstLastName = ({ firstName, lastName }) => firstName + '-' + lastName;
joinFirstLastName(person); // "Nick-Anderson"
Array
const myArray = ["a", "b", "c"];
不使用解构:
const x = myArray[0];
const y = myArray[1];
使用解构:
JavaScript 代码:
const [x, y] = myArray; // 就是这么简单!
console.log(x) // "a"
console.log(y) // "b"
Destructuring Objects – WesBos
ExploringJS – Destructuring
函数式编程
JavaScript 中的 Currying(柯里化) 和 Partial Application(偏函数应用)
一步一步教你 JavaScript 函数式编程(第一部分)
一步一步教你 JavaScript 函数式编程(第二部分)
一步一步教你 JavaScript 函数式编程(第三部分)
JavaScript 函数式编程术语大全
11,展开操作符 “…”
const arr1 = ["a", "b", "c"];
const arr2 = [...arr1, "d", "e", "f"]; // ["a", "b", "c", "d", "e", "f"]
function createStudent(firstName, lastName, ...grades) {
// firstName = "Nick"
// lastName = "Anderson"
// [10, 12, 6] -- "..." 将传递所有剩余参数,并创建一个包含它们的 "grades" 数组变量
const avgGrade = grades.reduce((acc, curr) => acc + curr, 0) / grades.length; // 根据 grade 计算平均成绩
return {
firstName: firstName,
lastName: lastName,
grades: grades,
avgGrade: avgGrade
}
}
const student = createStudent("Nick", "Anderson", 10, 12, 6);
console.log(student);
在这个示例中, createStudent 函数并没有去检查 grades.length 是否存在又或者它等于 0 的情况
const myObj = { x: 1, y: 2, a: 3, b: 4 };
const { x, y, ...z } = myObj; // 这里是对象被解构
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
// z是对象解构后的剩余部分:myObj 对象除去 x 和 y 属性后剩余部分被解构
const n = { x, y, ...z };
console.log(n); // { x: 1, y: 2, a: 3, b: 4 }
TC39 – Object rest/spread
Spread operator introduction – WesBos
JavaScript & the spread operator
6 Great uses of the spread operator
Template literals
es6之template literals
一等公民(first-class citizens)
12,对象属性简写
const x = 10;
const y = 20;
const myObj = {
x: x, // 将 x 分配给 myObj.x
y: y // 将 y 变量给 myObj.y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20
当变量名与属性名称相同时,可以进行以下简写
const x = 10;
const y = 20;
const myObj = {
x,
y
};
console.log(myObj.x) // 10
console.log(myObj.y) // 20
13,模板字符串
const name = "Nick";
`Hello ${name}, the following expression is equal to four : ${2+2}`;
// Hello Nick, the following expression is equal to four: 4
14,带标签(tag)的模板字符串
模板标签是可以作为模板字符串(template literal)的前缀函数。当一个函数被这钟方式调用时,第一个参数是出现在模板插值变量之间的字符串数组,并且随后的参数是插值变量的值。可以使用展开运算符 … 捕获
function highlight(strings, ...values) {
console.log(strings);//(3) ["I like ", " on ", ".", raw: Array(3)]
console.log(values);//(2) ["jam", "toast"]
const interpolation = strings.reduce((prev, current) => {
return prev + current + (values.length ? "<mark>" + values.shift() + "</mark>" : "");
}, "");
return interpolation;
}
const condiment = "jam";
const meal = "toast";
highlight`I like ${condiment} on ${meal}.`;
// "I like <mark>jam</mark> on <mark>toast</mark>."
如果我们这样调用 highlight`I like ${condiment} on ${meal}` (注意最后面没”.”),
那么第一个参数还是一个 3 个元素的数组:[“I like “, ” on “, “”],特别注意最后一个元素是的空字符串””
strings 确实是一个数组,但是它还有一个 raw 属性。其属性值 strings.raw 也是一个数组,其元素分别表示 strings 中对应的,经过转义之前在 模板字符串(Template literals) 中由用户输入的字符串
const myTag = (strs, ...exprs) => {
console.log(strs); //(3) ["x", "y", "", raw: Array(3)]
console.log(strs.raw); //(3) ["x", "\y", ""]
console.log(exprs); //[1, 2]
};
const obj = { a: 1, b: 2 };
const result = myTag`x${obj.a}\y${obj.b}`;
后面剩余参数
function comma(strings, ...values) {
return strings.reduce((prev, next) => {
let value = values.shift() || [];
value = value.join(", ");
return prev + next + value;
}, "");
}
const snacks = ['apples', 'bananas', 'cherries'];
comma`I like ${snacks} to snack on.`;
// "I like apples, bananas, cherries to snack on."
shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
arrayObject.shift()
返回值
数组原来的第一个元素的值
如果数组是空的,那么 shift() 方法将不进行任何操作,返回 undefined 值。请注意,该方法不创建新数组,而是直接修改原有的 arrayObject。
var arr = new Array(3)
arr[0] = "George"
arr[1] = "John"
arr[2] = "Thomas"
document.write(arr + "<br />") //George,John,Thomas
document.write(arr.shift() + "<br />") //George
document.write(arr) //John,Thomas
要删除并返回数组的最后一个元素,请使用 pop() 方法。
注意: 名为styled-components的著名库很大程度上依赖于此功能
15,导入 / 导出 (imports/ export)
// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;
// -------------
// myFile.js
import { pi, exp } from './mathConstants.js'; // 命名导入 -- 类似于解构语法
console.log(pi) // 3.14
console.log(exp) // 2.7
// -------------
// mySecondFile.js
import * as constants from './mathConstants.js'; // 将所有导出的值注入到 constants 变量中
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7
可以使用别名
import { foo as bar } from 'myFile.js'; // foo 被导入并注入到一个新的 bar 变量中
默认导出,每个模块只能有一个默认导出。默认导出可以是函数,类,对象或其他任何东西。这个值被认为是“主要”的导出值
// coolNumber.js
const ultimateNumber = 42;
export default ultimateNumber;
// myFile.js
import number from './coolNumber.js';
// 默认导出,将独立于其名称,自动注入到 number 这个变量;
console.log(number) // 42
// sum.js
export default function sum(x, y) {
return x + y;
}
// -------------
// myFile.js
import sum from './sum.js';
const result = sum(1, 2);
console.log(result) // 3
ECMAScript 6 Modules(模块)系统及语法详解
ES6 Modules in bulletpoints
Export – MDN
Import – MDN
Understanding ES6 Modules
Destructuring special case – import statements
Misunderstanding ES6 Modules – Kent C. Dodds
Modules in JavaScript
16,Promises的三种状态
等待中 (Pending)
达成 (Fulfilled)
拒绝 (Rejected)
const xFetcherPromise = new Promise( // 使用 "new" 关键字创建promise,并把它保存至一个变量
function(resolve, reject) { // Promise 构造函数需要一个带有着 resolve 和 reject 这两个参数的函数作为参数
$.get("X") // 执行 Ajax 请求
.done(function(X) { // 一旦请求完成...
resolve(X); // ... 把 X 值做为参数去 resolve promise
})
.fail(function(error) { // 如果请求失败...
reject(error); // ... 把 error 做为参数去 reject promise
});
}
)
Promise 对象需要一个带有两个参数 ( resolve 和 reject ) 的执行函数。这两个参数会把 pending 状态的 promise 分别进行 fulfilled 和 rejected 的处理。
Promise 在实例创建后处于待处理状态,并且它的执行器函数立即执行。一旦在执行函数中调用了 resolve 或 reject 函数,Promise 将调用相关的处理程序。
为了获得 promise 结果(或错误),我们必须通过执行以下操作来附加处理程序
xFetcherPromise
.then(function(X) {
console.log(X);
})
.catch(function(err) {
console.log(err)
})
如果 promise 成功,则执行 resolve ,并执行 .then 参数所传递的函数。
如果失败,则执行 reject ,并执行 .catch 参数所传递的函数。
回调地狱 (callback hell)
JavaScript Promises for dummies – Jecelyn Yeen
JavaScript Promise API – David Walsh
Using promises – MDN
What is a promise – Eric Elliott
JavaScript Promises: an Introduction – Jake Archibald
17,异步和等待(Async Await)
async / await 函数的目的是简化同步使用 promise 的行为,并对一组 promises 执行一些处理。
依赖于 promises
await 必须在async函数中使用
async function getGithubUser(username) {
// async 关键字允许在函数中使用 await ,意味着函数返回一个 promise
const response = await fetch(`https://api.github.com/users/${username}`);
// 执行在这里暂停,直到fetch返回的 Promise 被 resolved
return response.json();
}
getGithubUser('mbeaudru')
.then(user => console.log(user)) // 记录用户响应 - 不能使用 await 语法,因为此代码不在 async 函数中
.catch(err => console.log(err)); // 如果在我们的异步函数中抛出一个错误,我们将在这里捕获它
async 操作符将一个函数标记为异步,并将始终返回一个 Promise 。你可以在 async 函数中使用 await 操作符来暂停该行代码的执行,直到表达式中返回的 Promise resolves 或 rejects 。
当异步函数运行到 return 语句时,将会使用返回的值来 fulfilled Promise。 如果在 async 函数中抛出错误,则 Promise 状态将转为 rejected 。 如果没有从 async 函数返回任何值,则在执行 async 函数完成时,仍然会返回 Promise 并 resolves 为无值。
await 操作符用于等待 Promise fulfilled ,只能在 async 函数体内使用。 遇到这种情况时,代码执行将暂停,直到 promise fulfilled。
注意: fetch 是一个允许执行 AJAX 请求,返回一个 Promise 的函数
function getGithubUser(username) {
return fetch(`https://api.github.com/users/${username}`).then(response => response.json());
}
getGithubUser('mbeaudru')
.then(user => console.log(user))
.catch(err => console.log(err));
当你需要链接(chain)相互依赖的 promises 时,async / await 语法特别方便 。
例如,如果您需要获取一个令牌(token) ,以便能够在数据库上获取博客文章,然后获取作者信息:
async function fetchPostById(postId) {
const token = (await fetch('token_url')).json().token;
const post = (await fetch(`/posts/${postId}?token=${token}`)).json();
const author = (await fetch(`/users/${post.authorId}`)).json();
post.author = author;
return post;
}
fetchPostById('gzIrzeo64')
.then(post => console.log(post))
.catch(err => console.log(err));
使用 ES2017 中的 Async(异步) 函数 和 Await(等待)
ES2017 新特性:Async Functions (异步函数)
Async/Await – JavaScript.Info
ES7 Async/Await
JavaScript awaits
Async Function
Await
Using async / await in express with node 8
18,真值/假值(Truthy / Falsy)
在 JavaScript 中,truthy 或 falsy 值是在布尔上下文中求值时被转换为布尔值的值。布尔上下文的一个最常见的例子是求值 if 条件:
每个值将被转换为true,除非它们等于以下值:
- false
- 0
- “” (空字符串)
- null
- undefined
- NaN
if (myVar) {}
myVar 可以是任何 一等公民(first-class citizen) (变量, 函数, 布尔值) ,但它会被转换成一个布尔值,因为它会在布尔上下文中进行就值。
逻辑非 !
!0 // true -- 0 是 falsy(假) 值,所以它返回 true
!!0 // false -- 0 是 falsy(假) 值, 所以 !0 返回 true , 所以 !(!0) 返回 false
!!"" // false -- 空字符串是 falsy(假) 值, 所以 NOT (NOT false) 等于 false
new Boolean(0) // false
new Boolean(1) // true
Truthy (MDN)
Falsy (MDN)
Truthy and Falsy values in JS – Josh Clanton
19,静态方法
class Repo{
static getName() {
return "Repo name is modern-js-cheatsheet"
}
useName(){
//作为构造函数的属性来调用静态方法
return this.constructor.getName() + ' and it contains some really important stuff'
}
}
// 我们需要实例化这个 class(类),以使用非静态方法
let r = new Repo()
console.log(r.useName()) //Repo name is modern-js-cheatsheet and it contains some really important stuff
更好的理解原型:
Understanding Prototypes in JS – Yehuda Katz
A plain English guide to JS prototypes – Sebastian Porto
Inheritance and the prototype chain – MDN
更好的理解类:
ES6 Classes in Depth – Nicolas Bevacqua
ES6 Features – Classes
JavaScript Classes – MDN
ECMAScript 2015(ES6)的十大特征
JavaScript ES6(ES2015)入门-核心特性概述
ES6 新特性范例大全
现在就可以使用的5个 ES6 特性
面向对象的 JavaScript – 深入了解 ES6 类
使用 ES2017 中的 Async(异步) 函数 和 Await(等待)
JavaScript ECMAScript 2015 (ES6) 和 ECMAScript 2016 (ES7) 新特性速查表
ECMAScript 6 Modules(模块)系统及语法详解
学习 ES2015 新特性
JavaScript ES2015 中对象继承的模式
JavaScript 新书:探索 ES2016 与 ES2017