一 背景编程
JavaScript通过二十来年年的发展,由最初简单的交互脚本语言,发展到今天的富客户端交互,后端服务器处理,跨平台(Native),以及小程序等等的应用。JS的角色愈来愈重要,处理场景愈来愈复杂。在这个背景下,JS最初的简陋设计显然是不太够用的,其松散的语法规则,拗口的继承机制(传说中的6种继承方法),无命名空间,模块化,以及异步处理的回调地狱等等特性在开发过程当中容易成为开发人员的各类痛点,各个JS框架好比jQuery,SeaJs,等等为了这些问题也是操碎了心。不过随着JS语言的发展,上面的各类问题也逐渐获得了优化。json
然而JS又是一门神奇的语言,他的大部分场景寄存于浏览器的,不像PHP,ASP.NET,Java背后有一个专注的厂商支持,而是面对IE,Chrome,Firefox,Safari,Opera等各类互为竞争对手厂商,面对的状况略微复杂,其兼容性又能够写一本血泪史。因此本文提到的各类特性在使用以前须要考究下你的用户群体是否支持,降级处理,polyfill转义等等。小程序
关于ES6的书如今已经有不少了,其中包括不少名著,好比阮一峰的ECMAScript 6 入门,本文从个人角度从新梳理了一下,力求由浅入深,而且重点介绍一些比较实用的特性,让知识点更加系统化,清晰化。后端
二 ES6带来了什么api
由于任何一门语言都是在不断的发展和进化的,好比C#语言依托.NetFramework由1.0发展到4.5语言能力不断在加强,PHP由最初的1.0发展到7.0版本,一样JS一样也是在不断的完善进步中的,使用ES6能够给你带来如下变化:数组
(1) 语法更加规范,语义化更强,不容易出错:引入块级做用域避免变量冲突,下降出错概率。浏览器
(2) 语法更加简洁,便于维护:函数默认参数,箭头函数,解构赋值,扩展运算符,字符串变量等等让以前不少冗余代码瞬间消失,可读性更强;类的定义和继承更接近常见的C++和PHP语法,更加便于维护。服务器
(3) 功能更增强大:新增了Map,Set,ArrayBuffer等数据结构,新增了Proxy,Reflect,Decorator等辅助函数,处理对象更加灵活,扩展了JS语言能力。网络
(4) 异步编程更加直观:摆脱回调地狱,以相似于同步的语法进行异步编程。数据结构
(5) 模块化开发:JS终于内置支持了模块,命名空间,变量冲突的问题获得了原生支持。
二 总览
本系列主要从复杂性和实用性两个维度来逐步探讨,主要包括:
(1) JS语言的加强:包括语法的规范,数据类型的扩展,新增数据结构,解构赋值等
(2) 函数的加强:包括针对编写函数的优化语法,默认参数,扩展运算符,箭头函数等
(3) 对象的加强:包括对对象定义和继承的优化写法等
(4) 异步编程:包括Promise,Generator,Async,Await等异步编程写法
(5) 辅助模块:包括Symbol,Proxy,Reflect,Decorator等辅助API,这块由于应用很少,实用性略低一些,因此本文暂不讨论,若是有兴趣能够查询相关资料。
(6) 模块化编程:包括针对JS模块的语言层级的支持
三 本篇目录
本篇做为JS语言的加强篇,主要有如下内容:
(1) let/const/块级做用域
(2) 解构赋值
(3) 字符串/数组的扩展
(4) Set/Map数据结构
(5) ArrayBuffer数据结构
四 开始
1. let/const/块级做用域:
咱们知道JS语言var是来定义变量的,并且是动态变量很是方便,ES6为什么要发明let和const?
ES6以前没有块级做用域的说法,而只有函数做用域,这是JS跟C++,PHP相比一个很明显的特性:
void main() { int num = 2; int index = 1; if(index > 0) { int test = 10; } printf("%d/n",test); }
运行时会test变量不存在,由于test变量只能在if的花括号内有效,出了花括号会销毁,可是JS就不同了:
function main() { var num = 2; var index = 1; if(index > 0){ var test = 10; } document.write(test); }
妥妥的没问题,由于定义的test变量在main函数内都是有效的,为何呢,由于在JS里面存在变量提高和函数声明提高的过程,最终执行结果实际上是这样子:
function main() { var num,index,test; num = 2; index = 1; if(index > 0){ test = 10; } document.write(test); }
因此在ES6以前,var声明变量就有这些特性,变量提高,函数做用域,以及能够重复命名,每次命名都是一次覆盖,因此使用var变量容易出现坑:
(1) 由于变量能够提高,变量能够在任何位置声明,回溯性很差,维护性很差。
(2) 容许同名,内部变量覆盖外部变量 ,下面的例子本意是想先用外部变量,但实际上被内部变量覆盖了。
var name = "michael"; function test(){ console.log(name); var name = "leo"; } test();//undefined
(3) for循环变量容易泄露为全局变量,并且在数组赋值中会引发歧义
var func = [];
for (var index = 0; index < 10; index++) {
func[index] = function(){
console.log(index);
};
}
console.log(index); //10
func[0](); //10
上面的例子存在两个问题:index变量在循环结束后暴露在全局污染外部函数,func数组的每一个成员打印出来的都是10。由于他们引用的是同一个全局变量index,而这个变量在调用func[0]的时候值是10,因此就须要使用闭包来建立变量的方式来解决。
而let和const声明变量就能够避免以上问题
(1) 不存在变量提高,变量必须先声明再使用,更加规范严谨
(2) 变量名不能重复定义,避免歧义覆盖
(3) 定义的变量引入块级做用域,只有块级做用域内有效,避免内外部变量相互污染,不再用使用匿名函数了。
(4) for循环的计算器内部使用,index用完即销毁,并且再也不须要闭包来处理,由于let在每次循环都从新生成,并且会自动记录上次的值,这是ES6在处理循环的一个特性。
let func = []; for (let index = 0; index < 10; index++) { func[index] = function(){ console.log(index); }; } func[0](); //0 console.log(index); //index is not defined
let和const的区别
let 用于可变值的变量,一旦声明做用域就会被限定在本块。
const用于定义常亮,好比PI等等,对于简单类型的数据(数值、字符串、布尔值),const定义之后地址就会固定,因此值也不能改变,可是对于引用类型(数组,对象),虽然地址是不会变的,可是const对于地址指向的内存堆的值是能够变的,也就是说const声明的对象属性是能够修改的,这一点须要留意。
2. 解构赋值:
解构赋值是访问数组和对象的一种便捷方式,可让代码更加简洁优雅。好比访问数组,之前咱们用的方式是:
let name = "mic", sex = "male", city = "深圳", height = 1.65;
用解构的方式,明显代码简洁了不少。
let [name,sex,city,height] = ["mic","male","深圳",1.65];
(1) 数组解构赋值
数组的结构赋值就是把右边数组按位置赋值到左边的变量,能够嵌套,能够为空。
let [name, [[city], sex]] = ["mic", [["深圳"], "male"]];
let [ , , name] = ["深圳", "male", "mic"];
右边的结构不只仅是数组,只有是有Iterator接口的数据结构均可以。
解构赋值能够带有默认值,若是右边对应的值是undefined,默认值生效。
let [name="mic"] = ["mic2"];
let [name= "mic"] = [undefined];
(2) 对象解构赋值,一样对比之前的方式:
let obj = { name:"mic", city:"深圳", sex:"male" } //ES5 let name = obj.name, city = obj.city, sex = obj.sex; //ES6 let {name,city,sex} = obj;
还能够带上别名:
let {name:my_name,city:my_city,sex:my_sex} = obj; console.log(my_name);//mic console.log(my_city);//深圳 console.log(my_sex);//male
对象解构一样能够嵌套带默认值。
(3) 函数参数解构,为函数体内的参数赋值,也能够带默认值。
function reg([name,sex,city="深圳"]){ console.log(name); console.log(sex);
console.log(city); } reg(["mic","male"]); //mic //male
3.字符串/数组的扩展
3.1 字符串扩展比较实用的就是两个特性:模板字符串
(1)模板字符串:还记得之前多行字符串的拼凑么,如今实用`符号能够定义多行文本,而且能够嵌入变量。
//ES5 var name = "mic"; var str = "<div>" + "this is a test,my name = " + name + "</div>"; //ES6 let name = "mic; let str = `<div> this is a test,my name = ${name} </div>`;
使用模板字符串显然方便不少。
3.2 数组扩展
(1) 扩展运算符:...符号能够把数组解析为逗号分隔的序列,也就是展开数组,经常使用语数组传递给函数参数。
(2) Array.from():将类数组转换为真正的数组,不再须要使用拗口的Array.prototype.slice.call了。、
(3) Array.of():将一组值转为数组,用来替代new Array,由于new Array(3) 存在歧义,3表明数组长度,实际上我也可能只想建立一个数组只包含3,因此使用Array.of的行为更加规范。
(4) Array.copyWith/find/findIndex/includes 具体能够参考api
(5) Array.keys/values/entries 分别用来遍历下标,值以及下标和值组成的数组
4. Set/Map结构:
4.1 Set结构:Set结构是一种没有重复值的数组,当你在业务中须要用到去重时,Set最合适不过了。
(1) 建立Set:传递数组或者具备iterable接口的结构来建立。
const set = new Set([1, 2, 3, 4, 4]);
(2) 方法:size/add/delete/has/clear 具体见api
(3) 遍历:
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每一个成员
(4) 小技巧,数组去重:
function uniqArray(array_list){ return [...new Set(array_list)]; }
4.2 Map结构:Map的出现是为了Object键值只能是字符串的问题,Map出现之前,处理Hash值的数据结构只有对象,可是对象的键值只能是字符串,因此有局限性,而Map结构在Object的字符串基础上,能够设置各类类型的值,包括数字,字符串,对象等等均可以做为键值,极大的丰富了处理Hash值的功能。
(1) 建立/构造函数
二维数组,或者任何具备 Iterator 接口、且每一个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)均可以看成Map构造函数的参数
const map = new Map([ ['name','mic'], ['city', 'shenzhen'] ]);
(2) 方法:
size
set(key,value)
get(key)
has(key)
delete(key)
clear()
(3) 遍历:
keys():返回键名的遍历器
values():返回键值的遍历器
entries():返回键值对的遍历器
forEach():使用回调函数遍历每一个成员
5. ArrayBuffer结构:
5.1 什么是ArrayBuffer结构:
ArrayBuffer是ES6新增的一种数据结构,对于之前浏览器场景而言,最经常使用的数据结构就是字符串,json字符串等文本格式,对于小数据量的业务已经足够了,可是在大数据量场景,使用字符串就会比较吃力,好比WebGL的处理,须要处理大量的数据,或者小程序蓝牙通讯,须要传输声音数据等等,这种状况下字符串转换是须要大量时间的,而ArrayBuffer是一种二进制数据,可让JS和设备之间之间进行二进制数据传输,无需转换。
二进制数据的操做主要由ArrayBuffer对象来存储,一个保存了内存某块区域的对象,经过TypeArray或DataView来访问,TypedArray是固定格式来访问,而DataView能够混合格式来访问。
5.2 ArrayBuffer对象:
(1)建立:ArrayBuffer对象是二进制数据的存储媒介,至关于数据源,获取和设置二进制数据最终是存储在这个对象里面,可是不能直接对他读取和保存,只能经过TypedArray或DataView来读写。
let buf = new ArrayBuffer(128);//建立一个128字节的内存区域,每一个字节默认值0 if(buf.byteLength == 128){ //分配成功 }
else{
//异常处理
}
(2)属性和方法:
ArrayBuffer.prototype.byteLength: 返回内存字节长度
ArrayBuffer.prototype.slice(start,end): 拷贝出一个新的ArrayBuffer对象,从start开始到end
ArrayBuffer.isView():静态方法,判断一个对象是不是TypedArray
实例或DataView
实例
5.3 TypedArray视图
ArrayBuffer存储的数据能够有不一样的视角来读取和保存,TypedArray视图就是来访问ArrayBuffer对象的方法之一,他经过某个固定的数据类型来访问,主要有如下几种类型:
Int8Array:8位有符号整数,长度1个字节。
Uint8Array:8位无符号整数,长度1个字节。
Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不一样。
Int16Array:16位有符号整数,长度2个字节。
Uint16Array:16位无符号整数,长度2个字节。
Int32Array:32位有符号整数,长度4个字节。
Uint32Array:32位无符号整数,长度4个字节。
Float32Array:32位浮点数,长度4个字节。
Float64Array:64位浮点数,长度8个字节。
构造函数的静态属性和实例的属性BYTES_PER_ELEMENT均可以获取该类型的字节数:
Int32Array.BYTES_PER_ELEMENT // 4 let view = new Int32Array(8); view.BYTES_PER_ELEMENT;// 4
(1) 建立:
TypedArray(ArrayBuffer buffer, [int start], [int length]]);//buffer为ArrayBuffer对象,start可选值,视图的开始字节,默认0,length结束的字节,默认所有
var buff = new ArrayBuffer(128);// 建立一个128字节的ArrayBuffer var view = new Int32Array(buff);//建立一个指向b的Int32视图,开始于字节0,直到缓冲区的末尾
TypedArray(length);//直接经过指定长度来建立ArrayBuffer和View
let view = new Int32Array(8); //等同于 let buf = new ArrayBuffer(4*8); let view = new Int32Array(buf); view[0] = 10; view[1] = 20; view[2] = view[0] + view[1];
TypedArray(typedArray);//经过其余视图拷贝新视图,新的视图会从新建立内存
TypedArray(arrayLikeObject);//经过数组来建立
TypedArray相似于数组结构,数组的各类方法均可以用于TypedArray,也能够被遍历。
(2) 属性和方法:
TypedArray.prototype.buffer:返回整段内存区域对应的ArrayBuffer对象,属性为只读。
TypedArray.prototype.byteLength:返回TypedArray数组占据的内存长度,单位为字节,属性为只读。
TypedArray.prototype.byteOffset:返回TypedArray数组从底层ArrayBuffer对象的哪一个字节开始,属性为只读。
TypedArray.prototype.length:TypedArray数组含有多少个成员。
TypedArray.prototype.set():用于复制数组(普通数组或TypedArray数组)。
TypedArray.prototype.subarray():对于TypedArray数组的一部分,再创建一个新的视图。
TypedArray.prototype.slice():返回一个指定位置的新的TypedArray实例。
TypedArray.of():用于将参数转为一个TypedArray实例。
TypedArray.from():返回一个基于这个结构的TypedArray实例。
(3)字符串和ArrayBuffer的转换
// ArrayBuffer转为字符串 function ab2str(buf) { return String.fromCharCode.apply(null, new Uint16Array(buf)); } // 字符串转为ArrayBuffer对象 function str2ab(str) { var buf = new ArrayBuffer(str.length * 2); // 每一个字符占用2个字节 var bufView = new Uint16Array(buf); for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; }
5.4 DataView视图:要了解DataView视图的使用场景,须要了解计算机存储二进制数据的大端序和小段序问题,具体能够百度一下,总的来讲,对于我的电脑和服务器存储二进制的格式顺序稍微有点差异。
对于跟网卡,声卡等本机设备进行通讯,TypedArray视图是彻底够用的,可是对于服务器获取其余网络设备传来的大端序数据,使用TypedArray就会出现问题,DataView就是为了解决这个问题的方法。
(1) 建立:
DataView(ArrayBuffer buffer,[int start],[length]);//同TypedArray
(2) 属性和方法
DataView实例有如下属性,含义与TypedArray实例的同名方法相同。
DataView.prototype.buffer:返回对应的ArrayBuffer对象
DataView.prototype.byteLength:返回占据的内存字节长度
DataView.prototype.byteOffset:返回当前视图从对应的ArrayBuffer对象的哪一个字节开始
DataView实例提供8个方法读取内存。
getInt8:读取1个字节,返回一个8位整数。
getUint8:读取1个字节,返回一个无符号的8位整数。
getInt16:读取2个字节,返回一个16位整数。
getUint16:读取2个字节,返回一个无符号的16位整数。
getInt32:读取4个字节,返回一个32位整数。
getUint32:读取4个字节,返回一个无符号的32位整数。
getFloat32:读取4个字节,返回一个32位浮点数。
getFloat64:读取8个字节,返回一个64位浮点数。
DataView视图提供8个方法写入内存。
setInt8:写入1个字节的8位整数。
setUint8:写入1个字节的8位无符号整数。
setInt16:写入2个字节的16位整数。
setUint16:写入2个字节的16位无符号整数。
setInt32:写入4个字节的32位整数。
setUint32:写入4个字节的32位无符号整数。
setFloat32:写入4个字节的32位浮点数。
setFloat64:写入8个字节的64位浮点数。
var buffer = new ArrayBuffer(128); var data_view = new DataView(buffer); var view1 = data_view.getUint8(0); var view2 = data_view.getUint16(1,true);
view1.setUint8(0,10);
View2.setUint16(1,20,true);
get和set方法都有第二个可选参数,用于指定是大端序仍是小端序访问,默认是大端序,设置为true可指定为小端序。