横向对比ES5、ES6、ES7、ES8

前言

常用JS的代码片段

//连接数组
let arrA =[10,20,30]
let arrB = [40,50,60]
let arrC = [...arrA,...arrB]

//数组去掉重复项
let arrD = [30,30,40,40,50,50]
let arrE =[...new Set(arrD)]

//查找索引

[10,20,30].indexOf(20)

//遍历数组

[10,20,30].forEach((value,index)=>{

console.log(`${value} ${index}`)

})

//映射新数组

let arrF = [10,20,30].map(v=>v*2)

//检验数组中每个元素

[10,20,30].every(v=>v>10)

//是否有元素通过测试

[10,20,30].some(v=>v>10)

//过滤数组

[10,20,30].filter(v=>v>10)

//获取对象的key、value、es6遍历数组

Object.keys({a:1,b:2},{a:3,b:11})
Object.values({a:1,b:2},{a:3,b:11})
Object.entries({a:1,b:2},{a:3,b:11})

//获取对象里元素数量

Object.keys({a:1,b:2},{a:3,b:11}).length



前言

ECMAScript是ECMA组织制定的脚本语言规范,该规范来源于网景的Javascript和微软的Jscript,ECMAScript-262明确的定义了JavaScript。最初,JavaScript由Brendan Eich发明,最先出现在Navigator 2.0浏览器中。ES大会定期整理并发布新的JS版本。

ES版本 发布时间 新增特性
ES5 200911 扩展了Object、Array、Function等的功能
ES6(2015) 201506 类、模块化、剪头函数、函数参数默认值、模版字符串、解构赋值、延展操作符、对象属性简写(对象扩展,还有计算属性)、Promise、let、const(块级作用域)、新数据结构Set、Map、Symbol
ES7(2016) 201603 Array.prototype.includes()、指数操作符
ES8(2017) 201706 async/await、Object.values()、Object.entries()、String padding、函数参数列表结尾允许逗号、Object.getOwnPropertyDescriptors()等

延展操作符严格来说,不算ES6标准,但是babel支持,就写在这里了

ES5我就不说了,主要说说其他几个都更新了什么,另外可以使用babel将其他语法转换为es5,此外babel还支持装饰器、async-await因此,就是浏览器有些不支持es6等更高级的语法,也还是可以基于babel来使用更高级的语法

部分ES6 新增特性说明

新数据结构

Set(不可重复元素集合)、Map、Symbol

JS语言原来实现面向对象是直接通过原型的方式进行,这就让Java、Object-c、C#等面向对象语言的程序员比较难以理解了。随着JS能做的事情增多,因此,才会原生的引入class这个概念。我们来看看下边这段代码,来看看类与继承的代码如何写,此次需要注意的是,继承的时候,super要写在子类构造函数的最顶部。另外,从源码层面可以看出,class、extends、constructor都是prototype的语法糖

class Animal {
    constructor(name, feature) {
        this.name = name
        this.feature = feature
    }

    toString() {
        console.log('name:' + this.name + ',feature:' + this.feature)
        // return 'Animal done'
    }
}

var animal = new Animal('monky', 'beauty')//实例化
animal.toString()

console.log(animal.hasOwnProperty('name'))//true
console.log(animal.hasOwnProperty('toString'))//false
console.log(animal.__proto__.hasOwnProperty('toString'))//true

//继承

class Cat extends Animal {
    constructor(action) {
        super('cat', 'sex')
        this.action = action
    }

    toString(){
        // console.log(super.toString())
        super.toString()
    }
}

var cat = new Cat('catch')
cat.toString()

console.log(cat instanceof Cat)//true
console.log(cat instanceof Animal)//true

模块化(Module)

ES6实现了模块化,于是告别了基于commonjs的标准,使用require.js的AMD模式和sea.js的CMD模式来实现模块化的古老方式。

模块化主要基于export(导出,还有export default)和import(引用,还有import * as)两个关键字来实现,这就为JS引入了一个相对容易理解的封装的概念(以前是基于函数作用域和闭包,通过底层直接实现,现在可以优雅的通过关键字原生实现)

export定义了对外开放的接口,import定义了引用哪些接口。模块化为js创建了比较容易理解的命名空间,防止函数的命名冲突。

export

export var name = 'Ryan'//导出变量
export const sort = Math.sqrt//导出常量
export {name, sort}//导出对象(模块)
export function aModule(){return 1000}//导出函数

import

import defaultMethod,{otherMethod} from 'aModule'//xxx.js

箭头(Arrow)函数

1.=>是function的缩写
2.箭头函数与包围它的代码共享同一个this。这种情况下,就很好的解决了this的指向问题。我在写一个钱包服务器的时候,做过这样一个事情,通过定义var self = this,或者var that = this来引用外围的this,也就是我想用其他function中的this,我就需要想将其复制到一个全局的或者是类的属性上。借助=>,就不需要这种模式了。箭头函数的书写形式如下

()=>100
v=>x+1
(a,b)=>a-b
()=>{var=u}//return undefined
p=>{return 100}

箭头函数与bind的注意事项

箭头函数和bind方法,每次被执行后都会返回一个新的函数引用,因此,如果还需要对函数的引用做些事情的话(例如卸载监听器),那么你还需要保存之前的引用。以bind为例,我们说明一下:

class PauseMenu extends React.Component {
    componentWillMount() {
        AppStateIOS.addEventListener('change', this.onAppPaused.bind(this))
    }
    componentWillUnmount() {
        AppStateIOS.removeEventListener('change', this.onAppPaused.bind(this))
    }
    onAppPaused(event) { }
}

此处,因为bind每次都会调用一个新的函数引用,因此,造成卸载的不是原来的监听,造成卸载失败。此处应该修改为

class PauseMenu extends React.Component {
    constructor(props) {
        super(porps)
        this._onAppPaused = this.onAppPaused.bind(this)
    }
    componentWillMount() {
        AppStateIOS.addEventListener('change', this._onAppPaused)
    }
    componentWillUnmount() {
        AppStateIOS.removeEventListener('change', this._onAppPaused)
    }
    onAppPaused(event) { }
}

那么基于剪头函数,我们还可以使用箭头函数来做,因为箭头函数会共享包围它的this,因此,这样就可以简单明了的处理返回新的函数引用的问题了。

class PauseMenu extends React.Component {
    componentWillMount() {
        AppStateIOS.addEventListener('change', this.onAppPaused)
    }
    componentWillUnmount() {
        AppStateIOS.removeEventListener('change', this.onAppPaused)
    }
    onAppPaused = event => { }
}

函数参数默认值

function foo(height = 50,color = '#fff")

如果不使用函数默认值,就会有个小问题。

function foo (height,color)
{
var height = height||50
var color = color||'red'
}

如果参数的布尔值是false,例如,foo(0,''),因为0的布尔值是false,这样height的取值将会是50,color也会取red,因此,函数的默认值不仅能使代码简洁,也帮助规避一些风险

模版字符串

let name = 'name'+first+''+last+''

使用模版字符串,就简洁了很多。从此告别 +号拼接字符串的尴尬,并且还支持多行字符串

let name = `name is ${first} ${last}`

let names = `
xiaohong

zhangzhang

dada

`


解构赋值

解构赋值可以方便快速的从数组或者对象中提取赋值给定义的变量。

获取数组中的值

从数组中获取值并赋值到变量中,变量的顺序与数组中对象顺序对应。

var foo = [1,2,3,4,5]

var [one,twe,three] = foo
console.log(one)//1
console.log(twe)//2
console.log(three)//3

如果想要会略某些值,则可以

var [first,,last] = foo
console.log(first)//1
console.log(last)//5

也可以先声明变量

var a,b

[a,b] = [1,2]

console.log(a)//1
console.log(b)//2

如果没有从数组中获取到值,可以为变量设置一个默认值

var a,b

[a=5,b=7]=[1]

console.log(a)//1
console.log(b)//7

方便的交互两个变量的值

var a=1
var b = 3

[a,b]=[b,a]

console.log(a)//3
console.log(b)//1

获取对象中的值

const student={
name:'xxx',
age:'19',
city:'bj'
}

const {name, age,city}=student

console.log(name)//xxx
console.log(age)//19
console.log(city)//bj

延展操作符(Spread operator)

延展操作符 = ...可以在函数调用/数组构造时,将数组表达式或者string在语法层面展开,还可以在构造对象时,将对象表达式按key-value的方式展开。

函数调用

function(...iterableObj)

数组构造或者字符串

[...iterableObj,'4',...'hello',6]

es2018下构造对象时,进行克隆或者属性拷贝

let objClone={...obj}

应用场景

function sum(x,y,x){

return x+y+z

}

const numbers = [1,2,3]

不使用延展操作符

sum.apply(null, numbers)

使用延展操作符

sum(...numbers)

或者在构造数组时

如果没有展开语法,只能组合使用push,splice,concat,slice
将已有数组元素变为新数组的一部分

const people=['jan','tom']
const person = ['ali',...people,'alliance','ketty']
console.log(person)//Ali,jan,tom,alliance,ketty

另外,还有一个例子

var arr =[1,2,3]
var arr2=[...arr]
arr2.push(4)
console.log(arr2)//1,2,3,4

展开语法与Obj.assign()行为一致,都是执行浅拷贝,也就是只遍历一层,不会遍历父对象相关的数据

var arr1=[0,1,2]
var arr2=[3,4,5]
var arr3=[...arr1,...arr2] 等同于var arr4 = arr1.concat(arr2)

es2018中增加了对对象的支持

var obj1 = {foo:1,foo2:2}
var obj2={foo3:12,foo4:30}

var clonedObj={...obj1}
var mergedObj={...obj1,...obj2}

...在react中的应用

我们封装组件的时候,会对外公开props用于参数传递。我们一般都会使用...props进行参数的传递与遍历数据

<CustomComponent name='Jine' age={21} />
等价于
const params = {
name:'jine',
age:21
}

<CustomComponent {...params} />

我们还可以配合结构赋值,避免传入一些不必要的参数

var param = {
name:123,
title:456,
type:789
}

var {type,...other} = param

<CustomComponent type='normal' number={2} {...other} />
等价于

<CustomComponent type='normal' number={2} name=123,title=456/>

对象属性简写

设置一个对象时,不指定属性名

es5
const name=ming,age=18,city=Shanghai
const student={
name:name,
age:age,
city:city
}
es6
const name=ming,age=18,city=Shanghai
const student={
name,
age,
city
}


Promise

Promise是异步编程的一种解决方案,对callback做进一步的封装和优化。Promise由社区提出和实现,es6将其写进了语言标准,并统一了用法,提供了原生的promise支持。此处详细解答,请看我的另外一篇blog

let、const

let、const提供块级作用域,之前只能通过函数+闭包的方式来模拟块级作用域。那么let与const的区别呢?let声明可变变量,类型和值都可以改变。const声明不变变量(可以认为是常量,声明后就不能再次赋值了),声明变量后,必须马上初始化,不能留到以后赋值。

es5
{var a=19}

console.log(a)//19
es6

{
let a=20
}
console.log(a)//a is not defined


部分ES7新增特性说明

Array.prototype.includes()

用来判断一个数组是否包含指定的值,包含返回true,不包含返回false

arr.includes(x)等价于arr.indexOf(x)>0

指数操作符(**)

** 与Math.pow(..)等效的计算结果

Math.pow(2,10)等价于2**10

部分ES8新增特性说明

async/await

异步转同步,可以看我的另外一片blog。我们主要是用async/await来顺序获取异步函数返回值。
另外,依托于Promise.all还可以实现await的并发调用,这样就避免了同步调用的时间等待,提高了程序的整体效率。

async function charCountAdd(data1, data2) {

    const [d1, d2] = await Promise.all([charCount(data1), charCount(data2)])
    // const d1 = await charCount(data1)
    // const d2 = await charCount(data2)
    return d1 + d2

}

charCountAdd('Hello','Hi').then(console.log)
function charCount(data){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve(data.length)
        },5000)
    })
}
//捕捉错误,可以使用try catch,也可以在then后追加catch

Object.values()

Object.values()类似于Object.keys(),返回Object自身的所有属性值,不包括继承的值。

const obj={a:12,b:13,c:14}
es7
const vals=Object.keys(obj).map(key=>obj[key])
console.log(vals)//12,13,14
es8
const vals =Object.values(obj)
console.log(vals)//12,13,14

Object.entries

Object.entries()函数返回一个给定对象自身可枚举属性的键值对的数组

const obj={a:12,b:13,c:14}
es7
Object.keys(obj).forEach(key=>{
console.log('key:'+key+'values:'+obj[key])//12,13,14
})
es8
for (let [key,value] of Object.entries(obj)){
console.log('key: ${key} values: ${value}')//12,13,14
}

String padding

es8中,新增String.prototype.padStart(targetLength,[padString])和String.prototype.padEnd允许将空字符串或者其他字符串添加到原始字符串的开头或者结尾

console.log('0.0'.padStart(4,'10'))//10.0
console.log('0.0'.padStart(20))//                    0.0
console.log('0.0'.padEnd(4,'0'))//0.00
console.log('0.0'.padEnd(10,'0'))//0.000000000

编码规范

单引号、双引号

node中,单引号双引号有很多的时候是可以混用的,因此,此处也是一个重点,如果不注意,很容易出现奇怪的错误。在JSON中,全部请使用双引号,在其他地方也请使用双引号,如果在双引号中又存在引号,请使用单引号做区分。

分号

必须记得加分号,不加分号就跟耍流氓一样。

缩进、空格、大括号、小括号、逗号、数组、对象等的基本格式

通过IDE协助缩进,具体方式请根据不同的IDE进行总结,本处简单介绍webstrom的使用方式----全选之后,剪切代码,然后重新粘贴到代码编辑文本输入框中。

变量声明

如果是ES5的话永远都要加var,如果是ES6.....如果是ES7....

命名

变量命名:名词+小驼峰
方法命名:动词开头+名词+小驼峰
类命名(ES6开始有类喽,MD以前都是原型....艹):大驼峰
常量命名:大写字母 + 下划线
文件命名:小写字母 + 下划线
中间件(包)命名:小写字母

==和===

==用于直接比较数值的情况
===用于其他情况

字面量

这个字面量的相关说明来源于朴灵的深入浅出,我在这里做一下搬运工。书中说“请尽量使用{}、[]替代new Object()、new Array(),不要使用string(new String)、bool(new Boolean)、number(new Number)对象类型”,当然,此处应该更多的是针对ES5下的相关语法,如果是ES6、ES7应该完全不一样了。(需要补充ES6、ES7的相关语法和规范)

作用域

依然会存在ES5、ES6、ES7不一致的地方。
ES5中没有块级语句,最多是通过with来增加一个临时的块级变量复用区域,但是with又不是特别安全的。
ES6有了块级区域,变量范围跟java差不多了

数组

数组不要用for in进行循环,详见代码:

var foo = [];
foo[100] = 100;
//循环1
for (var i in foo) {
console.log(i);
}
//循环2
for (var i = 0; i < foo.length; i++) {
console.log(i);
}

循环1只会执行一次,循环2则会执行0~100,因此数组就不要for in 了 ,因为,我们可能希望遍历数组中的全部数据,而不是非undefine的数据。

异步

异步回调第一个参数应该是错误指示。

类与模块

依然会存在ES5、ES6、ES7不一致的地方。(之后做整理)
类继承、导出都不一样。

在es5下,类继承推荐的方式是

function Socket(options){
//....
stream.Stream.call(this);
//....
}

util.inherits(Socket,stream.Stream);

最佳实践的理论

1.注释要写成/** */的形式,方面工具导出
2.遵循原来项目的编码规范
3.基于JSLint或者JSHint进行代码质量管理,可以在编辑器中提醒不规范的信息,我们可以增加.jshintrc文件。这个可以去github上去查找并学习。
4.代码提交的hook脚本,例如precommit,这个需要学习整理。
5.持续集成,一方面,持续集成代表着代码质量的扫描,可以定时扫描或者触发式扫描,另一方面,可以通过集中的平台统计代码质量的好坏趋势,根据统计结果可以判定团队中的个人对编码规范的执行情况,决定用宽松的质量管理还是严格的质量管理。
6.可以使用CoffeeScript规范(编译式的js,还有typescript等),来避免一些不必要的编码规范的问题

说明 规则或方法
空格 利用IDE的排版功能自动增加需要的空格。(+、-、*、/、%、(、)等操作符前后都增加空格)
缩进 利用IDE的排版功能自动增加需要的缩进。与Java编码规范相同
变量声明 使用var声明变量
单引号、双引号 json中必须使用双引号,es5 use strict下也必须使用双引号,其他地方单引号双引号可以随便用
分号 必须增加分号在语句的结尾
变量命名 小驼峰
方法命名 小驼峰,动词开头
类命名 大驼峰
常量命名 都是大写字母,使用下划线隔开每个单词
文件命名 都是小写字母,使用下划线隔开每个单词
对外文件命名 都是小写字母,使用下划线开头,并使用下划线隔开每个单词
包名 都是小写字母,仿照npm库进行包的命名
比较操作 尽量使用===,数值比较可以用==
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 前面的话 函数是所有编程语言的重要组成部分,在ES6出现前,JS的函数语法一直没有太大的变化,从而遗留了很多问题,...
    CodeMT阅读 794评论 0 1
  • 1、let和const命令 let声明的变量只在let所在的代码块有效,即有块级作用域,不同于var; let定义...
    风之化身呀阅读 365评论 0 1
  • 一、ES6简介 ​ 历时将近6年的时间来制定的新 ECMAScript 标准 ECMAScript 6(亦称 ...
    一岁一枯荣_阅读 5,961评论 8 25
  • [TOC] 参考阮一峰的ECMAScript 6 入门参考深入浅出ES6 let和const let和const都...
    郭子web阅读 1,745评论 0 1
  • 又是好久没写文章,又给自己找了一大堆理由。从今天起,以后按部就班每天写一写,不断提高,不断进步。
    猫哥阅读 512评论 0 1