前言
常用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库进行包的命名 |
比较操作 | 尽量使用===,数值比较可以用== |