目录:
- 数组、函数、Symbol和对象
- Proxy和Reflect
- Generator函数和Promise对象
- Class、Module
ES6简介:ECMAScript6是JavaScript语言的下一代标准,在2015年6月正式发布。其目标是使JavaScript语言可用于编写复杂的大型应用程序,成为企业级开发语言。
本文参考:《ES6 标准入门》阮一峰,《JavaScript权威指南》、JavaScript标准库
数组、函数、Symbol和对象
数组
1、数组在ES6与ES5用法的异同
let arrayLike = {
'0' : 'T',
'1' : 'h',
'2' : 'i',
'3' : 'n',
'4' : 'k',
length: 5
};
// ES5的写法
const array1 = [].slice.call(arrayLike);
// ES6的写法
const array2 = Array.from(arrayLike);
console.log(array1);
console.log(array2);
输出:
[ 'T', 'h', 'i', 'n', 'k' ]
[ 'T', 'h', 'i', 'n', 'k' ]
只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组。
2、新语法
for(let index of array2.keys()){
console.log(index);
}
for(let [index,elem] of array2.entries()){
console.log(index + ' - ' + elem);
}
keys()跟entries()是ES6提供的新方法,用于遍历数组。
函数
1、默认值
es6之前,js不能直接为函数的参数指定默认值。es6赋给默认值的例子如下:
'use strict';
function billboard(author='Linkin Park', song = 'numb'){
console.log(author + ' --- ' + song);
}
billboard();
billboard('Taylor swift', 'shake it off');
通常默认值为函数的未参数。
2、rest参数
ES6引入了rest参数,用于获取函数的多余参数,这样就不需要使用arguments对象了。
function album(author, ...songs){
console.log('author: ' + author);
for(let song of songs){
console.log(song);
}
}
album('Eason', '放', '收心操', '海胆');
3、替换apply方法
扩展运算符(…)可以展开数组,所以不再需要apply方法将数组转为函数的参数。
function Mayday(a, b ,c ,d, e){
console.log(a + ', '+ b + ', '+ c + ', '+ d + ', '+ e + ', ')
}
const args = ['Monster', 'Ashin', 'Stone', 'Matthew Tsai', 'Lau Ming'];
// es5写法
Mayday.apply(null,args);
// es6写法
Mayday(...args);
4、箭头函数
ES6允许使用箭头定义函数。
let sum = (male, female) => {
console.log('新浪瘫痪');
};
sum('deer', 'Traey Miley’);
Symbol
1、概述
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。只要属性名是Symbol类型,可以保证不会与其他属性名产生冲突。
let Mayday = Symbol();
let SHE = Symbol();
let JayChou = Symbol('中国风');
let Beyond = Symbol('永远的beyond');
let JJ_Lin = Symbol.for('Singer');
let Leehom_Wang = Symbol.for('Singer');
console.log(JayChou === Beyond); // false
console.log(Mayday === SHE); // false
console.log(JJ_Lin === Leehom_Wang); // true
每一个symbol都不一样所以Mayday、SHE、JayChou、Beyond都是独一无二的。而用Symbol.for()可以做到使用同一个Symbol值,所以JJ_Lin和Leehom_Wang是一样的普通歌手。
2、消除魔法字符串
这个很像java中的Enum类型的用法。
const singerAttr = {
maestro: Symbol('change'),
only: Symbol('name'),
outstanding: Symbol(),
};
function getName(singer){
switch(singer){
case singerAttr.maestro:
console.log('Michael');
break;
case singerAttr.only:
console.log('JayChou');
break;
case singerAttr.outstanding:
console.log('JJ_Lin');
break;
}
}
getName(singerAttr.maestro);
getName(singerAttr.only);
getName(singerAttr.outstanding);
3、Symbol方法和内置的Symbol值
ES6可以定义自己使用的Symbol值,还提供了11个内置的Symbol值。
class Music {
[Symbol.sing](string){
return 'name : '+string;
}
}
const music = new Music();
console.log(music[Symbol.sing]('JJ_lin'));
console.log('a = ' + ‘JayChou'.search('a'));
Symbol.sing是自定的,search是内置的属性。
let obj = {
[Symbol.toPrimitive](singer){
switch(singer){
case 'number':
return 2;
case 'string':
return 'JayChou';
case 'default':
return 'JJ_Lin'
}
}
};
console.log(5 * obj);
重写对象的Symbol.toPrimitive属性。
补充
对象
对象是JavaScript的基本数据类型。对象是一中复合值:它将很多值聚合在一起,可以通过名字访问这些值。
1、创建对象最简单的方式就是使用对象直接量
const Maestor = {
name: 'Michael',
gender: 'male',
title: 'only',
dance() {
console.log('.......');
}
};
console.log(Maestor); // { name: 'Michael', gender: 'male', dance: [Function: dance] }
console.log(Maestor.name); // Michael
Maestor.title = 'KingOfPop';
console.log(Maestor.title); // KingOfPop
也可用通过new的方式创建对象
let nowDays = new Date();
2、Object.assign()
Object.assign()方法用来将源对象的所有可枚举属性复制到目标对象。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出TypeError错误。
Object.assign(Maestor, {
resurrection: 'resurrection'
}, {
sing() {
console.log('sing........')
}
});
console.log(Maestor.resurrection); // resurrection
Maestor.sing(); // sing........
3、Object.prototype属性
引用:Object.prototype属性表示Object的原型对象。JavaScript中几乎所有的对象都是Object的实例;所有的对象都继承了Object.prototype的属性和方法。它们可以被覆盖(除了以null为原型的对象,如 Object.create(null))。例如,新的构造函数的原型覆盖原来的构造函数的原型,提供它们自己的 toString() https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype)
Proxy和Reflect
1、Proxy
Proxy的意思是:代理。当访问添加了代理的对象时,都必须先经过代理才能访问到对象。
let actor = {
get: function(target, name){
if(name === 'prototype') return Object.prototype;
if(name === 'Miranda') return 'She is good(^.^)';
return 'who?'
},
apply: function(target, thisBinding, args) { console.log(args[0]); },
construct: function(target, args) {
return { name: args[1] };
}
};
let man = new Proxy(function(say, fire){
return '...';
}, actor);
man('Justin', 'Orlando'); // Justin
let another = new man('Justin', 'Orlando');
console.log(another.name); // Orlando
console.log(man.Miranda); // She is good(^.^)
get(target, propKey, receiver):拦截对象的读取属性;
apply(target, object, args):拦截Proxy实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
construct(target, args, proxy):拦截Proxy实例作为构造函数调用的操作,比如new proxy(…args)。
总共有11个拦截方法(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy)。
2、Reflect
Reflect是一个内置的对象,它提供可拦截JavaScript操作的方法。方法与Proxy的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
Reflect对象提供的静态函数,与Proxy提供的方法名称相同。这些方法中的一些与Object上的对应方法相同。具体参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect)。
Generator函数和Promise对象
Generator函数
Generator函数是ES6提供的一种异步编程解决方案。该函数有两个特征:1、function与函数名之间有一个星号;2、函数体内部使用yield语句定义不同的内部状态。
普通的Generator函数如下:
function *generatorDemo(){
yield 'z';
yield 'h';
yield 'a';
yield 'o';
}
let demo = generatorDemo();
console.log(demo.next()); // { value: 'z', done: false }
console.log(demo.next()); // { value: 'h', done: false }
console.log(demo.next()); // { value: 'a', done: false }
console.log(demo.next()); // { value: 'o', done: false }
console.log(demo.next()); // { value: undefined, done: true }
调用generatorDemo()后,该函数并不执行,而是返回一个指向内部状态的指针对象(遍历器对象)。想要往下执行必须调用next方法,使得指针指向下一个地方。当返回的状态done=true时表示遍历结束了。
next方法的作用是分阶段执行Generator函数。每次调用next方法,会返回一个对象,表示当前阶段的信息(value属性和done属性)。value属性是yield语句后表达式的值,表示当前阶段的值;done属性是一个布尔值,表示Generator函数是否执行完毕。
更近一步分析Generator函数:
function* tapNumber(first)
{
let second = 2 * ( yield (first+1));
let third = yield (second/3);
return ( first + second + third);
}
let tap = tapNumber(5);
console.log(tap.next()); // { value: 6, done: false }
console.log(tap.next()); // { value: NaN, done: false }
console.log(tap.next()); // { value: NaN, done: true }
我们理想得到的结果应该是:21。但是得到的结果是NaN。为什么呢?
原因:
1、yield语句只是一个暂停标志,当调用next()时Generator函数会从上次停止的地方开始执行。
2、因为并没有给next()传入参数,所以let second = 2 * undefined,second=undefined。
那我们怎么得到想要的结果呢?修改代码如下:
let tapTap = tapNumber(5);
let tapTapNext1 = tapTap.next();
console.log(tapTapNext1); // { value: 6, done: false }
let tapTapNext2 = tapTap.next(tapTapNext1.value);
console.log(tapTapNext2); // { value: 4, done: false }
console.log(tapTap.next(tapTapNext2.value)); // { value: 21, done: true }
把上一步得到的value传递给要执行的next(),这样我们就得到了想要的结果。yield本身并不返回任何值,它只是一个暂停状态。
Generator函数是协程在ES6中的实现,最大特点就是可以交出函数的执行权(即暂停执行)。整个Generator函数就是一个封装的异步任务。异步操作需要暂停的地方,都用yield语句注明。协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。
Promise对象
Promise就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API,可共进异步处理。
Promise有3种状态:Pending、Resolved和Rejected。只会从Pending到Resolved和Pending到Rejected,状态一旦改变就不会再变。
示例:
const IPromise = function(play){
return new Promise((resolve, reject)=>{
if(play === true){
resolve('I won't run away no more, I promise');
}else{
reject(new Error('oh on...'));
}
});
};
IPromise(false).then(lyrics=>{
console.log(lyrics);
}, errorMsg=>{
throw errorMsg;
}).catch(err => {
console.error('catch error', err); // catch error Error: oh on...
});
当IPromise传入false时会抛出错误(throw errorMsg),所以能输出错误。
IPromise(true).then(lyrics=>{
console.log(lyrics); // I won't run away no more, I promise
}, errorMsg=>{
console.log(errorMsg);
}).then(lyrics => {
console.log('Even when I get bored, I promise'); // Even when I get bored, I promise
});
当IPromise传入true时会依次执行then。
Class、Module
Class
JS通常是通过定义一个函数来创建并初始化新对象。ES6语法引入了Class这个概念作为对象模板。如下:
class Singer {
constructor(){
if(new.target === Singer){
throw new Error('Singer不能实例化');
}
}
static hold(){
console.log('microphone');
}
feature(){
console.log('Hairstyle');
}
}
class Eason extends Singer {
constructor(){
// this.tall = '173cm'; // this只能在super之后使用
super();
this.gender='male';
}
}
const monIn = new Eason();
const theBestMoment = new Eason();
Eason.hold();
monIn.feature();
console.log(monIn.gender);
console.log(Eason === Eason.prototype.constructor); // true
console.log(Eason.__proto__ === Singer); // true
console.log(Eason.prototype.__proto__ === Singer.prototype); // true
monIn.__proto__.printOwner = function(){
console.log('Eason');
};
monIn.printOwner(); // Eason
theBestMoment.printOwner(); // Eason
monIn.hold(); // TypeError: monIn.hold is not a function
const singer = new Singer(); // Error: Singer不能实例化
1、创建一个Singer类,并且Eason类继承Singer类。
2、因为Singer类中的constructor方法中有if(new.target === Singer),所以Singer类不能new。
3、static方法直接用类名(父类、子类都可以)调用,不能用实例调用。
4、Eason类中的constructor方法内this必须在super()之后使用,因为子类是继承的父类的this。
5、类的所有实例共享一个原型对象。
Module
模块是一个独立的JavaScript文件。模块文件可以包含一个类定义、一组相关的类、一个实用函数库或者是一些待执行的代码。
ES6模块的设计思想是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS和AMD模块都只能在运行时确定这些东西。
ES6模块例子:
module-demo.js
function getMaestro(){
return firstName+' '+ middleName + ' ' + lastName;
}
const firstName = 'Niels';
const middleName = 'Henrik';
const lastName = 'Abel';
export {
firstName,
middleName,
lastName,
getMaestro
}
module-use.js
import {firstName, middleName, lastName, getMaestro } from './module-demo';
CommonJS的例子:
commonjs-demo.js
function getMaestro(){
return firstName+' '+ middleName + ' ' + lastName;
}
const firstName = 'Niels';
const middleName = 'Henrik';
const lastName = 'Abel';
module.exports = {
firstName,
middleName,
lastName,
getMaestro
};
commonjs-use.js
const {firstName, middleName, lastName, getMaestro } = require('./commonjs-demo');
console.log(firstName);
console.log(getMaestro());
ES6模块加载的机制与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用。