Appearance
let const var
es6新增了两个用于定义变量的关键字
let和const定义的变量不会像var一样作用域在整个函数内,其作用域只在当前块中,如函数,判断,循环等这些语法块中。
js
function blockVar() {
if (true) var v = 'v';
console.log(v);
}
function blockLet() {
if (true) let v = 'v';
console.log(v);
}
blockVar();
blockLet();
由于let绑定了块级作用域,所以下面代码的行为会有不同
js
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 100);
} // 10 个 10
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 100);
} // 0..10
let和const定义的变量在同级语法块中不允许声明多次,但在不同级别的语法块中可以声明相同的变量名,这称为变量遮蔽。
js
function blockVar() {
var v = 'v';
var v = 'v1';
if(true) {
var v = 'v1';
console.log(v);
}
console.log(v);
}
function blockLet() {
let v = 'v';
// let v = 'v1'; // 会报错
if(true) {
let v = 'v1';
console.log(v);
}
console.log(v);
}
blockVar();
blockLet();
const用于定义一个常量,这个常量和其他语言中的常量稍有不同,其他语言定义的常量是不允许重新赋值和修改其内容的,而js中const定义的常量不允许重新赋值但可以修改其内容
js
const v = 1;
// v = 2; // 会报错
const obj = { v: 1 };
obj.v = 2;
console.log(obj);
解构
方便变量的赋值
js
const { v, v1 } = { v: 0, v1: 1 };
console.log(v, v1);
function fn({ v, v1}){
console.log(v, v1);
}
fn({ v: 0, v1: 1 });
let [ a, b, c ] = [ 1, 2, 3 ];
console.log(a, b, c);
// 还有这种玩法
let { length : len } = 'hello';
console.log(len); // 5
// 结构的花样有很多,查看下面更详细的文档,在开发中使用结构可以减少代码量,有助于你写出优雅的
// 逻辑
函数默认值
定义函数时更优雅地初始化参数
js
function fn(x = 1, y = 2){
console.log(x, y);
}
fn(3);
// 解构并初始化
function fn(x = 1, { y = 2 }){
console.log(x, y);
}
fn(3, {});
fn(3, { y: 4 });
箭头函数
回调函数更优雅的写法,最重要的,箭头函数可以绑定this到当前作用域对象
js
let obj = {
call(fn){
fn('call', 'fn');
}
}
obj.call(a => console.log(a));
obj.call((a,b) => {
console.log(a);
console.log(b);
});
// 绑定当前作用域的this
function obj(fn) {
this.fn = fn;
this.fn();
}
function call(){
new obj(function(){
console.log(this); // 输出 obj
});
new obj(() => {
console.log(this); // 输出 call
});
}
new call();
类
js的继承是基于原型链的,es6新特性中的类同样也是这样的方式,只是将之前的写法变得更优雅了
js
class Father {
static yell() { // 静态方法
console.log(`who is your dad`);
}
constructor(name) { // 构造方法,用来初始化对象,可以没有
this.name = name;
}
callMe() { // 定义父类方法
console.log(`${this.constructor.name} name is ${this.name}`);
}
calm() {
console.log('father calm down');
}
}
class BearSon extends Father { // extends 只是表示第三人称复数,js并不支持一次进行多继承
constructor(name, sex) {
super(name); // 调用父类构造器
this.sex = sex;
}
calm() { // 覆盖父类同名方法
console.log(`bear son ${this.sex} calm down`);
}
}
Father.yell();
let father = new Father('Anakin Skywalker');
father.callMe();
father.calm();
BearSon.yell();
let bearSon = new BearSon('Luke Skywalker', 'girl');
bearSon.callMe();
bearSon.calm();
// 等同于下面基于原型链的方式
function Father(name) {
this.name = name
}
Father.yell = function() {
console.log(`who is your dad`);
}
Father.prototype.callMe = function() {
console.log(`${this.constructor.name} name is ${this.name}`);
}
Father.prototype.calm = function() {
console.log('father calm down');
}
function BearSon(name, sex) {
Father.call(this, name);
this.sex = sex;
}
function extendsStatic(son, father) {
// 静态方法是附属于某个对象的,因此原型链的继承方式并不会继承静态方法,需要使用工具来处理
for (let k in father) {
if (father.hasOwnProperty(k)) {
son[k] = father[k];
}
}
}
extendsStatic(BearSon, Father)
BearSon.prototype = new Father();
BearSon.prototype.constructor = BearSon;
BearSon.prototype.calm = function() {
console.log(`bear son ${this.sex} calm down`);
}
Father.yell();
let father = new Father('Anakin Skywalker');
father.callMe();
father.calm();
BearSon.yell();
let bearSon = new BearSon('Luke Skywalker', 'girl');
bearSon.callMe();
bearSon.calm();
对象的getter/setter
定义对象属性的赋值和取值的行为,像vuejs之所以能在变量数据修改后执行某些操作就是使用到这个特性
js
let obj = {
_value: 4,
get v () {
return this._value;
},
set v (v) {
this._value = v + 1;
}
}
console.log(obj.v) // 4
obj.v = 4;
console.log(obj.v) // 5;
模板字符串
用于方便拼接字符串,并保留格式,自动转义换行,模板变量里支持任意的JavaScript表达式
js
let count = 1;
let str = 'count is ' + (count + 1) + '\n next text';
console.log(str);
let strTpl = `count is ${count + 1}
next text`
console.log(strTpl);
symbol
symbol对象用来定义一个唯一的值,详细用法可以查看es6语法手册,最常用的方式是用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值,这样私有方法将不会被访问到。
js
const bar = Symbol('bar');
const snaf = Symbol('snaf');
export default class myClass{
// 公有方法
foo(baz) {
this[bar](baz);
}
// 私有方法
[bar](baz) {
return this[snaf] = baz;
}
};
// 上面代码中,bar和snaf都是Symbol值,导致第三方无法获取到它们,因此达到了私有方法和私有属性
// 的效果。
for of
es6引入了遍历器Iterator的概念,任何可遍历对象都按照Iterator的统一标准实现,即调用对象的某个固定方法,返回的对象有一个next方法,此方法每调用一次,返回一个对象,对象的value表示当前值,done一个boolean值表示遍历是否结束,而这个固定方法es6规定是对象中的Symbol.iterator,iterator是es6规范中预定义的一个Symbol。在es6中,有三类数据结构原生具备Iterator接口:数组、某些类似数组的对象、Set和Map结构。此外Generator函数和字符串对象也实现了Iterator接口。
js
const obj = { // 原生对象不支持Iterator,所以不能被for of使用,但可以把对象模拟成一个Iterator
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
let iterObj = obj[Symbol.iterator]();
iterObj.next(); // { value: 1, done: true }
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
不同于for in遍历数组的键for of直接直接数组的值,Iterator便是供for of消费使用的
js
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
}
import/export
es6引入了模块的感念,import用来导入其他模块中的对象到当前模块,erport用来将当前模块中的对象导出供其它模块使用。v8现在(2017-8)还不支持这两个关键字。import需要放在模块文件开头,export可以放在任意位置,但必须是模块顶层。
js
// export.js
export { a: 'a', b: 'b' }; // 导出对象,可以是任意对象
// 直接导出时要注意
const str = 'hello world'; export str; // 报错
export const str = 'hello world'; // 正确
function f() {} export f; // 报错
export function f() {}; // 正确
function f() {} export {f}; // 正确
function f() {} export default {f}; // 正确
// import.js
import { a, b as bstr } from './export.js'; // as 给变量取个别名
console.log(a, bstr)
// export.js
export default { a: 'a', b: 'b' }; // 导出默认对象,此对象将作为导入时的默认对象
// import.js
import obj from './export.js'; // 将默认对象直接命名使用
console.log(obj)
yield generator
generator函数在es6中称为生成器,调用generator函数返回一个迭代器对象,调用它的next方法,返回一个对象,value对应值,done表示是否结束。意味着它可以在for of中进行遍历。与其它迭代器不同在generator函数中要返回迭代数据必须配合yield关键字。调用生成器函数后,函数并不会立即执行,而会暂停在那里,当然变量已经传过去了。调用迭代器的next方法后,函数会执行到下一个yield,再调用next再往下执行,直到结束。
js
function* gen() { // 迭代器的写法,函数function后面加*,可以是 function* / function *,在对象
// 中的写法 let obj = { * gen(){} }
console.log('gen');
yield 'a'; // 可以yield任意对象
let n = yield { a: 'str' }; // yield出一个值后会暂停,要与生成器内部通讯,调用next时将值通
// 过参数传过去,函数恢复执行后,传递的值将会被yield前的变量所
// 接收,由于这种机制,生成器函数内部的状态会一直变化。
yield function b(){ console.log(n); }
return true; // 最后一个next函数即done为true时的值,若没有返回值或函数已经结束在调用next都
// 会返回 {value: undefined, done: true}
}
let g = gen();
g.next(); // 'gen' { value: 'a', done: false }
g.next(3); // { value: { a: 'str' }, done: false }
let f = g.next(); { value: f(){...}, done: false }
f(); // 3;
g.next(); // { value: true, done: true }
g.next(); // { value: undefined, done: true }
玩完上面那一把,是不是有一个大胆的想法?可以通过yield返回任意对象,那么是否可以yield一个生成器呢?答案是肯定的,但产出一个生成器还要再对其调用并调用next方法,在实际使用中会嵌套多层生成器,每次都去判断再调用太麻烦,我想要一个简单粗暴的方法,正好,生成器提供了这种机制,可以将next调用委托下去:
js
function* gen() {
yield 'gen start';
yield 'gen end';
}
function* ger() {
yield 'ger start';
yield* gen();
yield 'ger end'
}
let g = ger();
g.next(); // ger start
g.next(); // gen start
g.next(); // gen end
g.next(); // ger end
Promise
Promise刚开始是作为一种处理回调参数数据的规范,后面被纳入es6,es6原生支持了Promise对象。MDN上对Promise的介绍,Promise超详细的介绍,Promise规范的目的是避免异步函数在书写时嵌套过深的情况,后来与 yield/generator 配合后,可以将异步方法写地更优雅。Promise的基本用法:
js
var fs = require('fs');
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
function writeFile(file, data) {
return new Promise((resolve, reject) => {
fs.writeFile(file, data, (err) => {
if (err) reject(err);
else resolve(file, data);
});
});
}
readFile('./file')
.then((data) => {
console.log('read ./file: ', data.toString());
return writeFile('./newfile', data);
})
.then((file, data) => {
console.log('write ', file);
})
.catch((err) => {
console.log('err: ', err);
});
实现一个简单的Promise来透析Promise的原理:
js
class myPromise{
constructor(init) {
init(this.resolve.bind(this), this.reject.bind(this));
}
then(fn) {
if (!this.resolveds) {
this.resolveds = [];
}
this.resolveds.push(fn);
return this;
}
catch(fn) {
this.rejected = fn;
}
resolve(...args) {
if (this.resolveds) {
let arg = args;
for (let i = 0; i < this.resolveds.length; i++) {
let resolved = this.resolveds[i];
let res = resolved(...arg);
if (res instanceof myPromise) {
i = i+1;
res.then((...args) => {
if (typeof this.resolveds[i] == 'function') {
this.resolveds[i](...args);
}
});
} else {
arg = res;
}
}
}
}
reject(err) {
if (this.rejected) {
this.rejected(err);
}
}
}
var fs = require('fs');
function readFile(file) {
return new myPromise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
function writeFile(file, data) {
return new myPromise((resolve, reject) => {
fs.writeFile(file, data, (err) => {
if (err) reject(err);
else resolve(file, data);
});
});
}
readFile('./file')
.then((data) => {
console.log('read ./file: ', data.toString());
return writeFile('./newfile', data);
})
.then((file, data) => {
console.log('write ', file);
})
.catch((err) => {
console.log('read ./file err: ', err);
});
实际的Promise实现要复杂地多,会进行三种状态之间地转换:pending: 初始状态,不是成功或失败状态。fulfilled: 意味着操作成功完成。rejected状态。: 意味着操作失败。状态只能由pending状态转换成fulfilled状态或rejected状态。
Promise经常用来将一个异步函数进行封装后,配合生成器函数,使得代码书写时看起来像同步的。在之前的介绍中生成器函数需要一个运行器来运行,co库便是这样的运行器,co库的介绍,简单使用下co:
js
let fs = require('fs')
let co = require('co');
function readFile(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
co(function* () {
let file = yield readFile('./file');
console.log(file);
})(function(err, res){
console.log(res);
});
实现一个简单的co库:
js
function co(gen) {
return function(fn) {
let g = gen();
next();
function next(err, result) {
if(err){
return fn(err);
}
let step = g.next(result);
if (!step.done) {
if (typeof step.value == 'function') {
step.value(next);
} else if (step.value instanceof Promise) {
step.value.then(next);
} else {
next(null, step.value);
}
} else {
fn(null, step.value);
}
}
}
}
将一个异步方法,转成Promise对象,其实过程都一样,nodejs中的所有异步方法,都遵循了一个规范,若异步函数执行时发生了异常,则回调函数的第一个参数肯定是一个错误对象,在回调函数内部需要对错误进行处理。基于这种特性,nodejs提供了一个工具方法,util.promisify,只要满足nodejs异步函数格式的,这个工具类都可以将其转换成Promise对象。
async/await
async/await是es7中的规范,目的是为了进行优雅的异步编程。async/await其实就是上面生成器加Promise结合的语法糖。async定义一个异步函数,返回一个Promise对象。await只能在async函数中使用,像yield一样会将函数暂停,await命令后面可以是Promise和原始类型。并且不需要额外的运行器来执行。async/await的介绍。async/await简单用法:
js
var fs = require('fs');
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
var asyncReadFile = async function () {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
介绍es6新特性的文章,推荐阅读
阮一峰的es6教程
深入浅出es6
扫一下api,有一些新函数可用