es6语法最全深入浅出

es6语法最全深入浅出

es6是继es5之后的一次主要改进,语言规范由es5.1时代的245页扩充至600页。es6增添了许多必要的特性,如模块和类,以及一些实用特性,如Maps、Sets、Promises、生成器 (Generators) 等。尽管es6做了大量的更新,但是它依旧完全向后兼容以前的版本,标准化委员会决定避免由不兼容版本语言导致的Web体验破碎。因此所有老代码都可以正常运行,整人过渡也显得更为平滑,但随之而来的问题是,开发者们抱怨了多年的老问题依然存在。

变量

es6新增了块作用域,以及let和const命令,完善变量定义规范,为数组和对象增加了解构赋值。

let命令

ECMAScript 6新增了let命令,用来声明变量。用法与var类似,但是所声明的变量,只能在let命令所在的代码块内有效。

下面代码在代码块中分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。

{
	let a = 10;
	var b = l;
}
a //抛出引用错误:a 未定义b //1

let不会发生“变量提升”现象,仅能够在声明之后使用。例如:

function do_something() {
	console.log(foo)//抛出引用错误let foo = 2;
	let foo = 2;
}; 

上面代码在声明foo之前,就使用这个变量,结果会抛出一个错误

tips: let不允许在相同作用域内,重复声明同一个变量。下面写法是错误的

const命令

const也用来声明变量,但声明的是常量。一旦声明,常量的值就不能改变。

下面代码表明改变常量的值是不起作用的。但对常量重新赋值不会报错,只会失效。

const PI=3.1415
PI //3.1415
PI=3 ;
PI //3.1415
const PI=3.1:
PI //3.1415

const的作用域与let命令相同: 只在声明所在的块级作用域内有效,不可重复声明

数组解构赋值

es6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构 (Destructuring)

ECMAScript 5为变量赋值,只能直接指定值

var a=l.
var b=2.
var c=3:

ECMAScript 6允许按如下方式进行赋值

var [a, b, c]=[1, 2, 3];

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

对象解构赋值

解构不仅可以用于数组,还可以用于对象。对象的解构与数组的不同之处:数组的元素是按次序排列的,变量的取值由它的位置决定,而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

在下面代码中,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,但是对取值完全没有影响。如果变量没有对应的同名属性,导致取不到值最后等于undefined.

bar, foo l= foo:"aaa",bar:"bbb] .var
foo //返回值“aaa
bar //返回值"bbb"
baz ]=[ foo:"aaa",bar:"bbb”];var
baz //返回值undefined

如果变量名与属性名不一致,必须写成下面这样

foo: baz ]=[ foo:"aaa", bar:"bbb" ] ;var
baz //"aaa'

字符和字符串

ECMAScript 6加强了对Unicode的支持,并且扩展了字符串对象。

字符

codePointAt()

JavaScript字符以UTF-16格式存储,每个字符为两个字节。对于那些需要4个字节存储的字符 (Unicode编号大于0xFFFF的字符),JavaScript会认为它们是两个字符.

在下面代码中,汉字“6q”的Unicode编号是0x20BB7,UTF-16编码为0xD842 0xDFB7 (十进制为55362 57271),需要4个字节存储。对于这种4个字节的字符,JavaScript不能正确处理,字符串长度会误判为2,而目charAt()方法无法读取字符,charCodeAt()方法只能分别返回前两个字节和后两个字节的值。

var s = "?";
s.length //2
s.charAt(0) // ""
s.charAt(1) // ""
s.charCodeAt(0) //55362
s.charCodeAt(1) //57271

字符串

u修饰符

ECMAScript 6为正则表达式添加了u修饰符,用来正确处理大于luFFFF的Unicode字符。

使用u修饰符

var s ="?";
/^.$/. test(s) //false
/^.$/u.test(s) //true

上面代码表示,如果不添加u修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。

利用这一点,可以写出一个正确返回字符串长度的函数

function codePointLength(text) {
var result = text.match(/[sS]/gu);
return result ? result.length : 0;
}
var s="吉吉"
s.length //4
codePointLength(s)//2
包含检测

传统上,JavaScript只有indexOf()方法,可以用来确定一个字符串是否包含在另一个字符串中。ECMAScript 6又提供了3种新方法。

contains(): 表示是否找到了参数字符串。

startsWith(): 表示参数字符串是否在源字符串的头部。

endsWith(): 表示参数字符串是否在源字符串的尾部。

使用包含检测方法

var s = "Hello world!";
s.startsWith("Hello") //true
s.endsWith("!") //true
s.contains("0") //true
定义重复字符串

repeat()返回一个新字符串,表示将原字符串重复n次

"x".repeat(3) //"xxx"
"hello".repeat(2) //"hellohello"
y修饰符

除了u修饰符,ECMAScript 6还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。它的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下个位置开始,不同之处在于,g修饰符只确保剩余位置中存在匹配,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的含义。

使用y修饰符

var s = "aaa_aa_a";
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) //["aaa"]
r2.exec(s) //["aaa"]
r1.exec(s) //["aa"]
r2.exec(s) //null

上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行时,两者行为相同,剩余字符串都是“ aa a”。因为g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。

模板字符串

模板字符串 (template string) 是增强版的字符串,用反引号 ()标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

使用模板字符串。

//普通字符串
`In JavaScript '\n' is a line-feed.  `
//多行字符串
`In JavaScript this is not legal.`
//字符串中嵌入变量
var name = "Bob",time = "today";
`Hello ${name},bow are you ${time}?`
var x = 1;
var y = 2;
console.log(`${x}+${y}=${x+y}`)
//"1+2=3"

上面代码表示,在模板字符串中嵌入变量,需要将变量名写在${}中。

数值

es6对数值的进制表示进行修订,新增了Number和Math方法,扩展数学计算能力。

进制表示

ECMAScript 6提供了二进制和八进制数值的新的写法,分别用前缀0b和0o表示,

0b111110111===503 //true
0o767==-503 //true

八进制用0o前缀表示的方法,将要取代已经在ECMAScript 5中被逐步淘汰的加前缀0的写法。

Number方法

NumberisFinite()、Number.isNaN()

ECMAScript 6在Number对象上,新提供了Number.isFinite()和Number.isNaN()两个方法,用来检查Infinite和NaN这两个特殊值。

与传统的isFinite()和isNaN(的区别在于,传统方法先调用Number()将非数值的值转为数值,再进行判断,而这两个新方法只对数值有效,非数值一律返回false。

isFinite(25) //true
isFinite("25") //true
Number.isFinite(25) //true
Number.isFinite("25) //false
isNaN(NaN) //true
isNaN("NaN) //true
Number.isNaN(NaN) //true
Number.isNaN("NaN") //false
Number.parselnt()、 Number.parseFloat()

ECMAScript 6将全局方法parselnt()和parseFloat(),移植到Number对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

Numberislnteger()和安全整数

Number.isInteger()用来判断一个值是否为整数。需要注意的是,在JavaScript内部整数和浮点数是同样的存储方法,所以3和3.0被视为同一个值。

Number.isInteger(25) //true
Number.isInteger(25.0) //true
Number.isInteger(25.1) //false

JavaScript能够准确表示的整数范围在-2~53 ~253之间。ECMAScript 6引入了Number.MAX SAFE INTEGER和Number.MIN SAFE INTEGER这两个常量,用来表示这人范围的上下限。NumberisSafelnteger()则是用来判断一个整数是否落在这人范围内。

var inside=Number.MAX_SAFE_INTEGER;
var outside=inside+l:
Number.isInteger(inside) //true
Number.isSafeInteger(inside) //true
Number.isInteger (outside) //true
Number.isSafeInteger (outside) //false

Math方法

Math.trunc()

Math.trunc方法用于去除一个数的小数部分,返回整数部分。

Math.trunc(4.1) //4
Math.trunc(4.9) //4
Math.trunc(-4.1) //-4
Math.trunc(-4.9) //-4

新数学方法

es6在Math对象上新增了很多新的数学方法。简单说明如下。

Math.acosh(x): //返回x的反双曲余弦
Math.asinh(x): //返回x的反双曲正弦
SMath.atanh(x): //返回x的反双曲正切。
Math.cbrt(x): //返回x的立方根S
Math.clz32(x): //返回x的32位二进制整数表示形式的前导0的个数
Math.cosh(x): //返回x的双曲余弦
Math.expm1(x): //返回e~x-1。S
Math.fround(x): //返回x的单精度浮点数形式
Math.hypot(...values): //返回所有参数的平方和的平方根
Math.imul(x,y): //返回两个参数以32位整数形式相乘的结果S
Math.log1p(x): //返回1+x的自然对数S
Math.log10(x): //返回以10为底的x的对数
Math.log2(x): //返回以2为底的x的对数S
Math.sign(x): //如果x为负返回-1,x为0返回0,x为正返回1。
Math.tanh(x): //返回x的双曲正切

数组

es6在es5基础上扩展了JavaScript数组功能,新增转换方法,引入数组推导和监听概念。

转换

Array.from()

Array.from()用于将两类对象转为真正的数组: 类似数组的对象和可遍历 (iterable) 的对象,其中包括ECMAScript 6新增的Set和Map结构。

在下面代码中,querySelectorAll()方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用forEach()方法。

let ps = document.querySelectorAll('p');
Array.from(ps).forEach(function(p) {
	console.log(p);
})

Array.from()还可以接收第二个参数,作用类似于数组的map()方法,用来对每个元素进行处理。

Array.from(arrayLike, x => x * x):

等同于

Array.from(arrayLike).map(x => x * x):
Array.of()

Array.of()方法用于将一组值转换为数组。这个函数的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。例如:

Array(3) //[undefined, undefined, undefined]
Array.of(3).length //1
Array(3,11,8) //[3,11, 8]
Array.of(3,11,8) //[3,11,8]

上面代码说明,只有当参数个数不少于两个,Array()才会返回由参数组成的新数组。

实例

find()findlndex()

数组实例的find()用于找出第一个符合条件的数组元素。它的参数是一个回调函数,所右数组元素依次遍历该回调函数,直到找出第一个返回值为true的元素,然后返回该元素,否则返回undefined。

使用find()方法

[1,5,10,15].find(function(value,index,arr) {
	return value > 9
})  //10

从上面代码可以看到,回调函数接收3个参数,依次为当前的值、当前的位置和原数组。

数组实例的findindex()的用法与find()非常类似,返回第一个符合条件的数组元素的位置,如果所有元素都不符合条件,则返回-1.

[1,5,10,15].findIndex(function(value,index,arr) {
	return value > 9;
}) // 2

这两个方法都可以接收第二个参数,用来绑定回调函数的this对象。另外,这两人方法都可以发现NaN,弥补了IndexOf的不足。

[NaN].indexOf(NaN) //-1
[NaN].findIndex(y => Object.is(NaN,y)) // 0
fill()

fill()使用给定值,填充一个数组。

使用fill()方法

['a','b','c'].fill(7)  //(7,7,7]
new Array(3).fill(7)  //(7,7,7]

上面代码表明,fill()方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。fill()还可以接收第二个和第三个参数,用于指定填充的起始位置和结束位置

['a','b','c'].fill(7,1,2) //['a',7,'c']
entries()、keys()和values()

ECMAScript 6提供3个新的方法: entries()、keys()和values(),用于遍历数组。它们都返回一个遍历器,可以用for of循环进行遍历,唯一的区别是keys()是对键名的遍历values()是对键值的遍历,entries()是对键值对的遍历。例如:

for(let index of['a','b'].keys()) {
	console.log(index);
}
//0
//1

推导

ECMAScript 6提供简洁写法,允许直接通过现有数组生成新数组,这被称为数组推导。

下面代码通过for of结构,数组a2直接在a1的基础上生成。

var a1 = [1,2,3,4];
var a2 = [for (i of al) i * 2];
a2 // [2,4,6,8]

在数组推导中,for of结构总是写在最前面,返回的表达式写在最后面。

监听

Array.observe()和Array.unobserve()方法用于监听或取消监听数组的变化,指定回调函数。它们的用法与Object.observe和Object.unobserve()方法完全一致。唯一的区别是,对象可监听的变化一共有6种,而数组只有4种: add、update、delete、splice(数组的length属性发生变化)

对象

es6在es5基础上继续完善JavaScript对象系统,新增了多人静态方法和原型方法,完善了对象直接量的语法格式和用法灵活性,增强对象代理保护和监听控制。

新增方法

Object.is()

Object.is()用来比较两个值是否严格相等。它与严格比较运算符 (===)的行为基本-致,不同之处: +0不等于-0,NaN等于自身。

使用Object.is()工具函数比较两个值。

+0===-0 //true
NaN-==NaN //false
Object.is(+0-0) //false
Object.is (NaN,NaN) //true
Object.assign()

Object.assign()方法用来将源对象 (source) 的所有可枚举属性,复制到目标对象(target)。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出TypeError错误。

使用Object.assign()方法复制属性

var target = {a:1};
var sourcel = {b:2};
var source2 = {b:3}
Object.assign(target, source1, source2)
target // {a:1, b:2, c:3}

tips: 如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性

var target={ a: 1, b: 1 );
var sourcel={ b: 2,c: 2 );
var source2={b:3}:
Object.assign(target, sourcel, source2) ;
target //a:1, b:2, c:3)

原型方法

proto属性

proto属性用来读取或设置当前对象的prototype对象。该属性一度被正式写入ECMAScript 6草案,但后来又被移除。目前,所有浏览器 (包括IE 11)都部署了这个属性。

//es6的写法
var obj = {
	_proto_:someOtherObj,
	method:function() {...}
}
//es5的写法
var obj = Object.create(someOtherObj);
obj.method=funtion() {...}

有了这个属性,实际上已经不需要通过Object.create()来生成新对象了

Obiect.setPrototypeOf()

Object.setPrototypeOf()方法的作用与proto相同,用来设置一个对象的prototype对象。基本用法如下:

function(obj,proto) {
	obj._proto_=proto;
	return obj;
}
Object.getPrototypeOf()

该方法与setPrototypeOf()方法配套,用于读取一个对象的prototype对象。用法如下

Object.getPrototype0f(obj)

增强语法

es6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

快速定义函数。

var Person = {
	name:'张三', //等同于 birth:birth
	birth,  // 等同于 hello:funtion()...
	hello() {console.log('我的名字是',this.name)}
}

这种写法用于函数的返回值,将会非常方便

function getPoint() {
	var x = 1;
	var y = 10;
	return {x,y};
}
getPoint()   //{x:1,y:10}
届性名表达式

es6允许定义对象时,用表达式作为对象的属性名。在写法上,要把表达式放在方括号内。

下面代码中,对象a的属性名lastWord是一人变量

var lastWord = "last word".
var a = {
	"first word":"hello",
	[lastWord]:"world"
}
a["first word"] //"hello"
a[lastWord] //"world"
a["last word"] //"world"
符号数据

es6引入了一种新的原始数据类型Symbol。它通过Symbol()函数生成

下面代码中,Symbol()函数接收一个字符串作为参数,用来指定生成的Symbol的名称,可以通过name属性读取。typeof运算符的结果,表明Symbol是一种单独的数据类型

var mySymbol = Symbol("Test");
mySymbol.name // Test
typeof mySymbol // "symbol"

注意: Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一人原始类型的值,不是对象。

代理防护层

引入对象代理层Proxy,Proxy可以理解成在目标对象之前,架设一层“拦载”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

es6原生提供Proxy构造函数,用来生成proxy实例对象。

var proxy = new Proxy({}, {
	get:function(target,property) {
		return 35;
	}
})
proxy.time //35
proxy.name //35
proxy.title //35

上面代码就是Proxy构造函数使用实例,它接收两个参数,第一个是所要代理的目标对象 (上例是一个空对象),第二个是拦截函数,它有一个get方法,用来拦截对目标对象的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于栏截函数总是返回35,所以访问任何属性都得到35。

监听

Obiect.observe()方法用来监听对象 (以及数组)的变化。一旦监听对象发生变化,就会触发回调函数。

在下面代码中,Object.observe()方法监听一个空对象o,一旦o发生变化 (如新增或删除一个属性),就会触发回调函数

var o = {};
function observer(changes) {
	changes.forEach(funtion(change) {
		console.log("发生变动的属性:"+change.name);
		console.log("变动前的值:"+change.oldValue);
		console.log("变动后的值:"+change.object[change.name]);
		console.log("变动类型:"+change.type);
	})
}
Object.observe(o,observer);

Object.observe()方法指定的回调函数,接收一个数组 (changes) 作为参数。该数组的成员与对象的变化一一对应,也就是说,对象发生多少个变化,该数组就有多少个成员。每个成员是一个对象 (change) ,它的name属性表示发生变化源对象的属性名oldValue属性表示发生变化前的值,object属性指向变动后的源对象,type属性表示变化的种类。基本上,change对象是下面的样子。

var change = {
	object:{...},
	type:'update',
	name:'p2',
	oldValue:'property 2'
}

Object.observe()方法目前共支持监听6种变化。

add: 添加属性.
update: 属性值的变化
delete: 删除属性,S
setPrototype: 设置原型<
reconfigure: 属性的attributes对象发生变化
preventExtensions: 对象被禁止扩展

Object.observe()方法还可以接收第三个参数,用来指定监听的事件种类。用法如下

Object.observe(o, observer, ['delete']) .

上面代码表示,只在发生delete事件时,才会调用回调函数

Obiect.unobserve()方法用来取消监听

Object.unobserve(o, observer)

提示: Object.observe()和Object.unobserve()这两个方法不属于ECMAScript 6,而是属于ES7的一部分,Chrome 36已经开始支持了。

函数

es6增强了JavaScript函数功能,改进了很多函数定义的方式,使函数使用更方便、更高效

默认值

在es6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。例如:

function log(x, y) {
	y = y || 'World'
	console.log(x,y);
}
log('Hello')//输出: Hello World
log('Hello''China') //输出: Hello China
log('Hello','') //输出: Hello World

上面代码检查函数log的参数有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数v等于空字符,结果被改为默认值。

为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值

例如:

// 写法一
if(typeof y === 'undefined') {
	y = 'World';
}
// 写法二
if(arguments.length === 1) {
	y = 'World';
}

es6允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x,y='World') {
	console.log(x,y);
}
log('Hello') //输出:Hello World
log("Hello",'China') //输出:Hello China
log("Hello",'') //输出:Hello

可以看到,ECMAScript 6的写法比ECMAScript 5简洁许多,而且非常自然

下面代码为函数的参数设置默认值。

function Point(x=0,y=0) {
	this.x = x;
	this.y = y;
}
var p = new Point(); //结果:p = {x:0,y:0}

rest函数

es6引入rest参数 (…变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

下面代码中的add()函数是一个求和函数,利用rest参数,可以向该函数传入任意数目的参数。

function add(...values) {
	let sum = 0;
	for(var val of values) {
		sum+=val;
	}
	return sum;
}
add(2,5,3)			//10

rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量.

箭头函数

es6允许使用箭头 (=>) 定义函数。例如:

var f = function(v) {
	return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。例如:

var sum = (num1, num2) => num1+num2;

等同于:

var sum = function(num1,num2) {
	return num1+num2;
}

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。例如:

var sum = (num1,num2) => { return num1+num2; }

提示:由于大括号被解释为代码块,如果箭头函数直接返回一个对象,必须在对象外面加上括号。例如:

var getTempItem = id => ({id: id, name:"Temp"})

使用箭头函数可以简化回调函数

var result = values.sort(function(a,b) {
	return a - b;
})
//箭头函数写法
var result = values.sort((a,b) => a - b);

提示: 使用箭头函数应该注意下面几点问题:
函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。
箭头函数不可以当作构造函数,即不可以使用new命令,否则会抛出一个错误。

箭头函数不可以使用arguments对象,该对象在函数体内不存在。

数据结构

es6完善了JavaScript数据结构,新增了两个数据类型: Set和Map。下面分别进行说明。

Set

es6提供了新的数据结构Set。它类似于数组,但成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,调用该函数可以生成Set数据结构。

下面代码使用add()方法向Set结构添加成员,结果显示Set结构不会包含重复的值。

var s = new Set();
[2,3,4,5,2,2].map(x => s.add(x));
for(i of s) {console.log(i)} //2 3 4 5

Set结构的原型 (Set.prototype) 定义两个属性,说明如下
constructor: 构造函数,默认就是Set函数
size: 返回Set结构的成员数

Set数据结构的原型 (Set.prototype) 定义了4个方法,说明如下
add(value): 添加某个值。
delete(value): 删除某人值
has(value): 返回一个布尔值,表示该值是否为set的成员
clear(): 清除所有成员

WeakSet

WeakSet结构与Set类似,也是不重复的值的集合。但是,它与Set有两个区别
WeakSet的成员只能是对象,而不能是其他类型的值
WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。

WeakSet是一个构造函数,可以使用new命令创建WeakSet数据结构

var ws=new WeakSet();

作为构造函数,WeakSet可以接收一个数组或类似数组的对象作为参数。该数组的所有成员都会自动成为WeakSet实例对象的成员。

在下面代码中,a是一个数组,它有两个成员,也都是数组。将a作为WeakSet构造函数的参数,a的成员会自动成为WeakSet的成员

var a=[[1,2],[3,4]];
var ws=new WeakSet(a);

WeakSet结构的原型 (WeakSet.prototype) 定义了4个方法:
add(value): 向WeakSet实例添加一个新成员
clear(): 清除WeakSet实例的所有成员
delete(value): 清除WeakSet实例的指定成员
has(value): 返回一个布尔值,表示某个值是否在WeakSet实例中。

Map

JavaScript对象都是键值对的集合,只能使用字符串作为键。使用起来不是很方便

下面代码计划将一个DOM节点作为对象data的键,但是由于对象只接收字符串作为键名,所以element被自动转为字符串[Obiect HTMLDivElement]。

var data = {};
var element=document:getElementById("myDiv");
data[element]=metadata;

为了解决这人问题,es6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,对象也可以当作键

Map数据结构定义了下面这些属性和方法

size: 返回成员总数。
set(key,value): 设置一个键值对
get(key): 读取一个键
has(key): 返回一个布尔值,表示某个键是否在Map数据结构中
delete(key): 删除某个键
clear(): 清除所有成员

Map提供3个原生遍历器。具体说明如下
keys(): 返回键名的遍历器
values(): 返回键值的遍历器
entries(): 返回所有成员的遍历器

WeakMap

WeakMap结构与Map结构基本类似,唯一的区别是它只接收对象作为键名 (null除外),不接收原始类型的值作为键名

设计WeakMap结构的目的: 键名是对象的弱引用,当对象被回收后,WeakMap自动移除对应的键值对

WeakMap常用于DOM元素,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除,这样可以防止内存泄漏。

var map = new WeakMap();
var element = document.querySelector(".element");
map.set(element,"Original");
var value=map.get(element);
console.log(value); //"Original"
element.parentNot.removeChild(element);
element=null;
value=map.get(element);
console.log(value); //undefined

WeakMap支持has和delete方法,但是没有size属性,也无法遍历它的值,这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。

循环遍历

es6新增的遍历器和for of循环语句。它们都是针对for循环的局限进行功能改善和增强

遍历器

遍历器 (lterator)是一种协议,任何对象只要部署该协议,就可以完成遍历操作。在ECMAScript 6中,遍历操作特指for of循环。遍历器的作用如下:
为遍历对象的属性提供统一的接口
使得对象的属性能够按次序排列。

es6的遍历器协议规定,部署了next()方法的对象,就具备了遍历器功能.next()方法必须返回一个包含value和done两个属性的对象。其中,value属性是当前遍历位置的值,done属性是一个布尔值,表示遍历是否结束

下面代码定义了一个makelterator()函数,该函数能够返回一个遍历器对象用来遍历参数数组。

function makeIterator(array) {
	var nextIndex = 0;
	return {
		next:function() {
			return nextIndex < array.length?
				{value:array[nextIndex++],done:false};
				{value:undefined,done:true};
		}
	}
}
var it = makeIterator(['a','b']);
it.next().value;   			//'a'
it.next().value;			//'b'
it.next().done;				//true

状态机

Generator就是一个内部状态的遍历器,即每调用一次遍历器,内部状态发生一次改变(可以理解成发生某些事件) 。ECMAScript 6引入Generator函数,作用就是可以完全控制内部状态的变化,依次遍历这些状态

使用Generator函数

Generator函数就是普通函数,但是有以下两个特征

function关键字后面有一个星号。

函数体内部使用vield语句,定义遍历器的每个成员,即不同的内部状态

下面代码定义了一个Generator函数helloWorldGenerator(),它的遍历器有两个成员“hello”和“world”。调用这个函数,就会得到遍历器。

function helloWorldGenerator() {
	yield 'hello';
	yield 'world';
	return 'ending';
}
var hw = helloWorldGenerator();

当调用Generator函数时,该函数并不执行,而是返回一个遍历器 (可以理解成暂停执行)。以后,每次调用这个遍历器的next()方法,就从函数体的头部或者上一次停下来的地方开始执行 (可以理解成恢复执行),直到遇到下一个vield语句为止。也就是说next()方法就是在遍历vield语句定义的内部状态

hw.next()		//{value:'hello',done:false}
hw.next()		//{value:''world},done:false}
hw.next()		//{value:'ending',done:true}
hw.next()		//{value:undefined,done:true}

上面代码一共调用了4次next()方法

第一次调用,函数开始执行,直到遇到第一句yield语句为止。next()方法返回一个对象,它的value属性就是当前yield语句的值hello,done属性的值false,表示遍历还没有结束。

第二次调用,函数从上次vield语句停下的地方,一直执行到下一个yield语句。next()方法返回的对象的value属性就是当前vield语句的值world,done属性的值false,表示遍历还没有结束。

第三次调用,函数从上次vield语句停下的地方,一直执行到return语句 (如果没有return语句,就执行到函数结束)。next()方法返的对象的value属性,就是紧跟在return语句后面的表达式的值 (如果没有return语句,则value属性的值为undefined) ,done属性的值true,表示遍历已经结束

第四次调用,此时函数已经运行完毕,next()方法返回对象的value属性为undefineddone属性为true。以后再调用next()方法,返回的都是这个值。

总之,Generator函数使用iterator接口,每次调用next()方法的返回值,就是一个标准的iterator返回值: 有着value和done两人属性的对象。其中,value是vield语句后面那个表达式的值,done是一个布尔值,表示是否遍历结束。

Generator函数的本质,其实是提供一种可以暂停执行的函数。yield语句就是暂停标志,next()方法遇到yield,就会暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回对象的value属性的值。当下一次调用next()方法时,再继续往下执行,直到遇到下一个yield语句。如果没有再遇到新的yield语句,就一直运行到函数结束,将return语句后面的表达式的值,作为value属性的值,如果该函数没有return语句,则value属性的值为undefined.

由于yield后面的表达式,直到调用next()方法时才会执行,因此等于为JavaScript提供了手动的“情性求值”(Lazy Evaluation) 的语法功能。

yield语句与return语句有点像,都能返回紧跟在语句后面的那人表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。

next()方法

vield语句本身没有返回值,或者说总是返回undefined。next()方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。

下面代码先定义了一个可以无限运行的Generator函数f(),如果next()方法没有参数,每次运行到yield语句,变量reset的值总是undefined。当next方法带一个参数true时,当前的变量reset就被重置为这个参数 (即true) ,因此i会等于-1,下一轮循环就会从-1开始递增

function f() {
	for(var i=0;true;i++) {
		var reset = yield i;
		if(reset) {i=-1;}
	}
}
var g = f();
g.next()		//{value:0,done:false}
g.next()		//{value:1,done:false}
g.next(true)	//{value:0,done:false}
异步操作

Generator函数的这种暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next()方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next()方法时再执行。所以Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数

下面代码表示,第一次调用loadUI)函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next()方法,则会显示Loading界面,并且异步加载数据。等到数据加载完成,再一次使用next()方法,则会隐藏Loading界面。可以看到,这种写法的好处是所有Loading界面的逻辑都被封装在一个函数,按部就班非常清晰

function loadUI() {
	showLoadingScreen();
	yield loadUIDataAsynchronously();
	hideLoadingScreen();
}
var loader = loadUI();		//加载UI
loader.next()				//卸载UI
loader.next()
for of循环

for of循环可以自动遍历Generator函数,且此时不再需要调用next()方法

下面代码使用for of循环,依次显示5个yield语句的值。这里需要注意,一目next0)方法的返回对象的done属性为true,for of循环就会中止,目不包含该返回对象所以上面代码的return语句返回的6,不包括在for of循环中。

function foo() {
	yield 1;
	yield 2;
	yield 3;
	yield 4;
	yield 5;
	yield 6;
}
for(let v of foo()) {
	console.log(v);
}
//1 2 3 4 5
yidld*语句

如果yield命令后面跟的是一个遍历器,需要在yield命令后面加上星号,表明它返回的是个遍历器。这被称为yield*语句。

在下面代码中,delegatinglterator是代理者,delegatedlterator是被代理者。由于yield* delegatedlterator语句得到的值,是一个遍历器,所以要用星号表示运行结果就是使用一个遍历器,遍历了多个Genertor函数,有递归的效果

let delegatedIterator = (function*() {
	yidld 'Hello!';
	yield 'Bye!';
}());
let delegatingIterator = (function*() {
	yield 'Greetings!';
	yield* delegatedIterator;
	yield 'Ok,bye';
}());
for(let value of delegatingIterator) {
	console.log(value);
}
//"Greetings!"
//"Hello!"
//"Bye!"
//"Ok,bye."

预处理

es6原生提供了Promise对象。所谓Promise对象,就是代表了未来某个将要发生的事件 (通常是一个异步操作)。它的好处在于,有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象还提供了一整套完整的接口,使得可以更加容易地控制异步操作

基本用法

es6的Promise对象是一个构造函数,用来生成Promise实例。下面是Promise对象的基本用法。

下面代码表示,Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve()方法和reject()方法。如果异步操作成功,则用resolve()方法将Promise对象的状态变为“成功”(即从pending变为resolved) ; 如果异步操作失败,则用reject()万法将状态变为“失败”(即从pending变为rejected)

var promise = new Promise(function(resolve,reject) {
	if(/* 异步操作成功 */) {
		resolve(value);
	} else {
		reject(error);
	}
});
promese.then(function(value) {	//成功
},function(value) {			//失败
});
then方法

Promise.prototype.then方法返回的是一个新的Promise对象,因此可以采用链式写法。

getJSON("/posts.json").then(function(json)
	return json.post;
}).then(function(post) {
	//执行后续处理
});

上面的代码使用then()方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数

如果前一个回调函数返回的是Promise对象,这时后一个回调函数就会等待该Promise对象有了运行结果,才会进一步调用。

getJSON("/post/1.json").then(function(post)
	return getJSON(post.commentURL);
}).then(function(comments) {
	//对 comments 进行处理
});

这种设计使得嵌套的异步操作,可以被很容易地改写,从回调函数的“横向发展”改为“向下发展”。

catch()方法

Promise.prototype.catch()方法是Promise.prototype.then(null,rejection)的别名,用于指定发生错误时的回调函数。

getJSON("/posts.json").then(function(posts) {
	//JavaScript 代码
}).catch(function(error) {
	//处理前一个回调函数运行时发生的错误
	console.log("发生错误!",error)
})

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

getJSON("/post/1.json").then(function(post) {
	return getJSON(post.commentURL);
}).then(function(comments) {
	//JavaScript代码
}).catch(function(error) {
	//处理前两个回调函数的错误
});
all和race()方法

Promise.all()万法用于将多个Promise实例,包装成一个新的Promise实例.

	var p = Promise.all([p1,p2,p3]);

上面代码中,Promise.all()方法接收一个数组作为参数,p1、p2、p3都是Promise对象的实例。 (Promise.all()万法的参数不一定是数组,但是必须具有iterator接口,且返回的每个成员都是Promise实例。)
p的状态由p1、p2、p3决定,分成两种情况。

只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2p3的返回值组成一个数组,传递给p的回调函数

只要p1、p2、p3中有一个被reiected,p的状态就变成rejected,此时第一个被reiect的实例的返回值,会传递给p的回调函数

下面是一个具体的例子

//生成一个Promise对象的数组
var promises = [2,3,5,7,11,14].map(function(id){
	return getJSON("/post/" + id + "json");
});
Promise.all(promises).then(function(posts) {
	//...
}).catch(function(reason){
`//...
});

Promise.race()方法同样是将多个Promise实例,包装成一个新的Promise实例

	var p=Promise.race([p1,p2,p3]);

上面代码中,只要p1、p2、p3中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的返回值。
如果Promise.all0方法和Promise.race()方法的参数,不是Promise实例,就会先调用下面讲到的Promise.resolve()方法,将参数转为Promise实例,再进一步处理

resolve(和reject()方法

有时需要将现有对象转为Promise对象,Promise.resolve()方法就起到这个作用

var jsPromise=Promise.resolve($.ajax("/whatever.json"));

上面代码将jQuery生成deferred对象,转为一个新的es6的Promise对象

如果Promise.resolve()方法的参数,不是具有then()方法的对象 (又称thenable对象),则返回一个新的Promise对象,且它的状态为fulfilled。

var p = Promise.resolve('Hello');
p.then(function(s) {
	console.log(s)
})
//Hello

上面代码生成一个新的Promise对象的实例p,它的状态为fulfilled,所以回调函数会立即执行,Promise.resolve()方法的参数就是回调函数的参数

如果Promise.resolve()方法的参数是一个Promise对象的实例,则会被原封不动地返回.

Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。Promise. reject()方法的参数reason,会被传递给实例的回调函数

var p = Promise.reject('出错了');
p.then(null,function(s){
	console.log(s)
})

上面代码生成一个Promise对象的实例p,状态为reiected,回调函数会立即执行

async函数

async函数是用来取代回调函数的另一种方法。只要函数名之前加上async关键字,就表明该函数内部有异步操作。该异步操作应该返回一个Promise对象,前面用await关键字注明。当函数执行时,一旦遇到await就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

下面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数将返回一个Promise对象。调用该函数时,当遇到await关键字,立即返回它后面的表达式(getStockPrice()函数) 产生的Promise对象,不再执行函数体内后面的语句。等到getStockPrice完成,再自动回到函数体内,执行剩下的语句.

async function getStockPrice(symbol,currency) {
	let price = await getStockPrice(symbol);
	return convert(price,currency);
}

async函数并不属于ECMAScript 6,而是被列入了ES7,但是traceur编译器已经实现了这个功能。

类和模块

类和模块都是代码封装的基本方法,它们的主要差异在于:类可以实例化为对象,而标准模块则不能。由于标准模块的数据只有一个副本,因此当程序的一部分更改标准模块中的公共变量时,如果程序的其他任何部分随后读取该变量,都会获取同样的值。与之相反,每个实例化对象的对象数据则单独存在。另一个不同在于: 不像标准模块,类可以实现接口。

es5通过构造函数,定义类。ECMAScript 6开始引入Class (类) 概念。通过class关键字,可以定义类

本示例定义了一个类,类中包含一个constructor构造函数,而this关键字表示实例对象。除了构造方法,还可以自定义方法。定义方法时,前面不需要加上function保留字。

class Point {
	constructor(x,y) {
		this.x = x;
		this.y = y;
	}
	toString() {
		return '('+this.x+','+this.y+')';
	}
}
var point = new Point(2,3);
point.toString()		//(2,3)

模块

es6支持模块功能,解决JavaScript代码的依赖和部署问题,取代现有的CommonJS和AMD规范,成为浏览器和服务器通用的横块解决方案

基本用法

模块功能有两个关键字: export和import。export用于用户自定义模块,规定对外接口;import用于输入其他模块提供的功能,同时创造命名空间 (namespace) ,防止函数名冲突。

es6允许将独立的JavaScript文件作为模块,也就是说,允许一个JavaScript脚本文件调用另一个脚本文件。该文件内部的所有变量,外部无法获取,必须使用export关键字输出变量。

下面是一个JavaScript文件,里面使用export关键字输出变量

//profile.js
export var frstName='David';
export var lastName='Belle';
export var year=1973;

上面是profile.js文件,ECMAScript 6将其视为一个模块,里面用export关键字输出了3个变量。

模块的整体加载

export关键字除了输出变量,还可以输出方法或类 (class)

本示例是一个circle.is文件,它输出两人方法

//circle.js
export function area(radius) {
	return Math.PI * radius * radius;
}
export function circumference(radius) {
	return 2 * Match.PI * radius;
}

然后,可以在main.js文件中引用这个模块

//main.js
import {area,circumference} from 'circle';
console.log("圆面积:" + area(4));
console.log("原周长:"+ circumference(14));

export default语句

如果不想为某个属性或方法指定输入的名称,可以使用export default语句

//export-defult.js
export default function foo() {
   console.log('foo');
}

在上面代码中,foo()方法被称为该模块的默认方法

在其他模块导入该模块时,import语句可以为默认方法指定任意名字

//import-default.js
import customName form './export-default'
customName(); // 'foo'

注意:一个模块只能有一个默认方法。

如果想在一条import语句中,同时输入默认方法和指定名称的变量,可以写成下面这样。

import customName, { otherMethod } from './export-default';

如果要输出默认属性,只需将值跟在export default之后即可

export default 42;

提示: export default也可以用来输出类

//MyClass.is
export default class {...}
import MyClass from 'MyClass'
//main.is
模块继承

模块之间也可以继承.

下面示例设计一个circleplus模块,继承了circle模块。

//circlcplus.js
export * from 'circle':
export var e =2.71828182846:
export default function(x) {
	return Math.exp(x);
}

在上面代码中,“export *”表示输出circle模块的所有属性和方法,export default命令定义模块的默认方法。

这时可以为circle中的属性或方法,改名后再输出。

export [ area as circleArea ] from 'circle'

加载上面模块的写法如下:

//main.is
module math from "circleplus";;
import exp from "circleplus";
console.log(exp(math.pi));

上面代码中的“import exp”表示将circleplus模块的默认方法加载为exp方法

讲解结束感谢观看
参考自《JavaScript网页编程从入门到精通》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋叶原的琴音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值