在学习es6的过程当中,为了方便本身复习,以及查看,对api作了一个极简用例介绍。若有错误多多指正。es6
(1)一个大括号就是一个块级做用域,let声明的变量只在本身做用域有效;
(2)es6强制开启严格模式,变量未声明不能引用,因此会报 Uncaught ReferenceErrorajax
function test() { for (let i = 1; i < 3; i++) { console.log(i) } console.log(i); // Uncaught ReferenceError: i is not defined } test();
(3)let不能重复声明正则表达式
function test() { let a = 1; let a = 2; } test();
(4)let不存在变量提高(这个地方有问题)编程
// var 的状况 console.log(a); // 输出undefined var a = 2; // let 的状况 console.log(b); // 报错ReferenceError let b = 2;
(1)const声明以后必须赋值,不然会编译不经过;
(2)const声明的值不容许修改;json
const PI = 3.14; // PI = 2; // const PI; console.log(PI);
(3)const若是是对象的话,能够向对象中添加属性,也能够修改a的属性;json是指向内存地址的一个指针,指针的指向不变,可是那个被json指针所指向的内存地址所存储的内容是能够变化的;api
const json = { a: 2 } json.a = 3; json.b = 3; console.log(json.a) //3 console.log(json.b) //3
先上两个例子了解什么是解构赋值数组
{ let a, b, rest; [a, b, rest] = [1, 2]; console.log(a, b, rest); //1 2 undefined }
{ let a, b, rest; [a, b, ...rest] = [1, 2, 3, 4, 5, 6, 7]; console.log(a, b, rest); //1 2 [3, 4, 5, 6, 7] }
{ let a, b; ({ a, b } = { a: 1, b: 2 }); //a,b 顺序不影响其结构结果 console.log(a, b); // 1 2 }
{ let a, b, rest; [a, b, rest = 3] = [1, 2]; console.log(a, b, rest); // 1 2 3 }
{ let a = 1; let b = 2; [a, b] = [b, a]; console.log(a, b); //2 1 }
{ function f() { return [12, 13]; } let a, b; [a, b] = f(); console.log(a, b); //12 13 } { function f() { return [12, 13, 14, 15, 16]; } let a, b; [a, , , b] = f(); //函数返回多个值,能够选择性的接收对应的值 console.log(a, b); // 12 16 } { function f() { return [12, 13, 14, 15, 16]; } let a, b; [a, , ...b] = f(); //取出对应的值,其余的值能够直接赋值给数据 console.log(a, b); // 12 [14, 15, 16] }
{ let o = { p: 42, q: true }; let { p, q } = o; console.log(p, q); //42 true } { let { a = 10, b = 11 } = { a: 3 } // 对象的默认值更改 console.log(a,b); // 3, 11 }
{ let metaData = { title: 'abc', test: [{ title: 'gao', desc: 'description' }] } let { title: esTitle, test: [{ title: cnTitle }] } = metaData; console.log(esTitle, cnTitle); }
{ let regex1 = new RegExp('xyz', 'i'); let regex2 = new RegExp(/xyz/i); console.log(regex1.test('xyz123'), regex2.test('xyz123')); // true true let regex3 = new RegExp(/xyz/ig, 'i'); // 后面的修饰符会把前面的修饰符给覆盖掉 console.log(regex3.flags); // es6新增的,用来获取正则表达式的修饰符 }
y修饰符的做用与g修饰符相似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不一样之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始。promise
{ let s = 'bbb_bb_b'; let a1 = /b+/g; // g只要匹配到都算 let a2 = /b+/y; // y必须是下一个开始的字母开始匹配 console.log('one', a1.exec(s), a2.exec(s)); // g修饰符匹配到均可以,y修饰符必须从第一个开始匹配,若是一第个不是b则会输出null console.log('two', a1.exec(s), a2.exec(s)); // 第二次匹配,g修饰符会只要匹配到均可以,y修饰符必须从紧邻的下一个字符开始匹配 console.log(a1.sticky, a2.sticky); // 判断是否开启了y修饰符 false true }
one和two的输出结果安全
ES6 对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于uFFFF的 Unicode 字符。数据结构
{ console.log('u-1', /^\uD83D/.test('\uD83D\uDC2A')); // 不加u把后面的四个字节当成两个字符 console.log('u-2', /^\uD83D/u.test('\uD83D\uDC2A')); // 加u把后面的4个字节看成一个字符 console.log(/\u{61}/.test('a')); // false 大括号括起来表明一个unicode字符,因此必须加u才能识别 console.log(/\u{61}/u.test('a')); // true console.log(`\u{20BB7}`); let s = '?'; console.log('u-1', /^.$/.test(s)); //false 字符串大于两个字节,必须加u修饰符才能匹配到 console.log('u-2', /^.$/u.test(s)); //true console.log('test-1', /?{2}/.test('??')); // false console.log('test-2', /?{2}/u.test('??')); // true }
{ console.log('a', '\u0061'); // a a console.log('s', '\u20BB7'); // s ₻7 把前两个字节看成一个总体 console.log('s', '\u{20BB7}'); // s ? unicode编码用{}能够正常识别 }
对于4个字节的字符,JavaScript不能正确处理,字符串长度会误判为2,并且charAt方法没法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。ES6提供了codePointAt方法,可以正确处理4个字节储存的字符,返回一个字符的码点。
{ let s = '?'; console.log(s.length); // 2 console.log('0', s.charAt(0)); // 0 � //es5未对多个字节的字符作处理 console.log('1', s.charAt(1)); // 1 � console.log('at0', s.charCodeAt(0)); //at0 55362 console.log('at1', s.charCodeAt(1)); //at1 57271 let s1 = '?a'; console.log('length', s1.length); // 3 console.log('code0', s1.codePointAt(0)); // code0 134071 console.log('code0', s1.codePointAt(0).toString(16)); // code0 es6会自动把多个字节的字符看成一个总体来处理 console.log('code1', s1.codePointAt(1)); // code1 57271 console.log('code2', s1.codePointAt(2)); // code2 97 }
ES5提供String.fromCharCode方法,用于从码点返回对应字符,可是这个方法不能识别Unicode编号大于0xFFFF。ES6提供了String.fromCodePoint方法,能够识别大于0xFFFF的字符,弥补了String.fromCharCode方法的不足。在做用上,正好与codePointAt方法相反。注意,fromCodePoint方法定义在String对象上,而codePointAt方法定义在字符串的实例对象上。
{ console.log(String.fromCharCode('0x20bb7')); //ஷ console.log(String.fromCodePoint('0x20bb7')) //? }
{ // es5 let str = '\u{20bb7}abc'; for (let i = 0; i < str.length; i++) { console.log('es5', str[i]); //� � a b c } //es6 for (let code of str) { console.log('es6', code); // ? a b c } }
{ let str = 'string'; console.log('includes', str.includes('c')); // 判断是否包含 false console.log('start', str.startsWith('s')); // 以什么开头 true console.log('end', str.endsWith('ng')); // 以什么结尾 true console.log('repeat', str.repeat(2)); // 字符串重复两次 stringstring }
ES6 引入了字符串补全长度的功能。若是某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。若是原字符串的长度,等于或大于指定的最小长度,则返回原字符串。若是用来补全的字符串与原字符串,二者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
{ console.log('1'.padStart(2,'0')); // 01 console.log('1'.padEnd(2,'0')); // 10 }
{ let name = "List"; let info = "hello world"; let m = `i am ${name} ${info}`; console.log(m); //i am List hello world }
{ let user = { name:'list', info:'hello world' } function fn(s,v1,v2){ console.log(s,v1,v2); return s+v1+v2; } console.log(fn`i am ${user.name} ${user.info}`) // ``符号至关于一个函数的参数fn(i am ${user.name} ${user.info}); }
输出结果
ES6还为原生的String对象,提供了一个raw方法。String.raw方法,每每用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。
{ console.log('raw '+String.raw`hi\n${1+2}`) console.log('noRaw '+`hi\n${1+2}`) }
输出结果
从 ES5 开始,在严格模式之中,八进制就再也不容许使用前缀0表示,ES6进一步明确,要使用前缀0o表示。若是要将0b和0o前缀的字符串数值转为十进制,要使用Number方法。
{ console.log('B',0b11010101010); //二进制表示,b大小写均可以 console.log('O',0O1237637236); // 八进制表示法 }
Number.isFinite()用来判断数字是否有限(无尽小数),Number.isNaN()来判断一个数是否是小数
{ console.log('15',isFinite(15)); //true console.log('NaN',isFinite(NaN)); //false console.log('1/0',isFinite(1/0)); //false console.log('isNaN',Number.isNaN(15)); // false console.log('isNaN',Number.isNaN(NaN)); // true }
Number.isInteger用来判断一个数是否是整数
{ console.log('13',Number.isInteger(13)); // true console.log('13.0',Number.isInteger(13.0)); // true console.log('13.1',Number.isInteger(13.1)); //false console.log('13',Number.isInteger('13')); // false }
Number.MAX_SAFE_INTEGER,Number.MIN_SFAE_INTEGER表示js能够准确表示的值的范围,isSafeInterger用来判断这个值是否在安全范围内。
{ console.log(Number.MAX_SAFE_INTEGER,Number.MIN_SFAE_INTEGER); console.log('15',Number.isSafeInteger(15)); console.log('9999999999999999999999',Number.isSafeInteger(9999999999999999999999)); }
Math.trunc方法用于去除一个数的小数部分,返回整数部分。Math.sign方法用来判断一个数究竟是正数、负数、仍是零。对于非数值,会先将其转换为数值。
{ console.log('4.1',Math.trunc(4.1)); //4 console.log('4.9',Math.trunc(4.9)); //4 } { console.log('-5',Math.sign(-5)) //-1 console.log('5',Math.sign(5)) //+1 console.log('0',Math.sign(0)) //0 console.log('50',Math.sign(50)) //+1 console.log('NaN',Math.sign(NaN)) //NaN }
cbrt用来计算一个数的开方
{ console.log('-1',cbrt(-1)); //-1 console.log('8',cbrt(8)); //2 }
Array.of方法用于将一组值,转换为数组,这个方法的主要目的,是弥补数组构造函数Array()的不足。由于参数个数的不一样,会致使Array()的行为有差别。
{ let arr = Array.of(1,2,3,4); console.log('arr=',arr); // arr= [1, 2, 3, 4] let emptyArr = Array.of(); console.log(emptyArr); // [] //与Array方法对比 Array() // [] Array(3) // [, , ,] Array(3, 11, 8) // [3, 11, 8] }
Array.from方法用于将两类对象转为真正的数组:相似数组的对象和可遍历的对象(包括ES6新增的数据结构Set和Map)。
<p>你好</p> <p>我好</p> <p>你们好</p> { let p = document.querySelectorAll('p'); let pArr = Array.from(p); pArr.forEach(function(item){ console.log(item.textContent); // 你好 我好 你们好 }) console.log(Array.from([1,3,5],function(item){return item*2})) // [2,6,10] }
fill方法使用给定值,填充一个数组。
{ console.log('fill-7',[1,3,'undefined'].fill(7)); //[7,7,7] console.log('fill,pos',[1,2,3,4,5,7,8].fill(7,1,4)); //[1, 7, 7, 7, 5, 7, 8] // 后两个参数表示索引的位置 }
ES6 提供三个新的方法——entries(),keys()和values()——用于遍历数组。
{ for(let index of [1,2,3,4].keys()){ console.log('index',index); // index 0 // index 1 // index 2 // index 3 } for(let value of [1,2,3,4].values()){ console.log('value',value); // value 1 // value 2 // value 3 // value 4 } for(let [index,value] of [1,2,4,5,6].entries()){ console.log(index,value); // 0 1 // 1 2 // 2 4 // 3 5 // 4 } }
截取必定长度的数字而且替换在相对应的索引的位置
{ console.log([1,4,9,6,7,2,3].copyWithin(1,3,5)); // [1, 6, 7, 6, 7, 2, 3] // 截取3-5的位置的数字,从索引1的位置开始替换 console.log([1,4,9,6,7,2,3].copyWithin(1,3,6)); // [1, 6, 7, 2, 7, 2, 3] }
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,全部数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,而后返回该成员。若是没有符合条件的成员,则返回undefined。数组实例的findIndex方法的用法与find方法很是相似,返回第一个符合条件的数组成员的位置,若是全部成员都不符合条件,则返回-1。
{ console.log([1,2,3,4,5,6].find(function(item){return item > 3})); //4 console.log([1,2,3,4,5,6].findIndex(function(item){return item > 3})); // 3 }
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法相似。ES2016 引入了该方法。
{ console.log([1,2,NaN].includes(1)); // true console.log([1,2,NaN].includes(NaN)); // true }
扩展运算符(spread)是三个点(...)。将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 [...document.querySelectorAll('div')] // [<div>, <div>, <div>]
ES6 以前,不能直接为函数的参数指定默认值;ES6容许为函数的参数设置默认值,即直接写在参数定义的后面。
{ function fn(x,y='hello'){ // 默认值后面不能再出现形参 console.log(x,y); } fn('word'); // word hello fn('word','nihao') // word nihao } { let a = 'nihao'; function test(a,b=a){ //1. //let a = 1; 参数变量是默认声明的,因此不能用let或const再次声明 console.log(a,b); } test('word'); // word word test(); //undefined undefined } { let a = 'nihao'; function test(x,b=a){ //2. console.log(x,b) } test('hello');// hello nihao }
ES6 引入rest参数(形式为...变量名),用于获取函数的多余参数,这样就不须要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
{ function fn(...arg){ for(let v of arg){ console.log(v); } } fn(1,2,3,4); //1 //2 //3 //4 } { console.log(...[1,2,3,4]); // 1,2,3,4 console.log('a',...[1,2,3,4]); // a,1,2,3,4 }
ES6 容许使用“箭头”(=>)定义函数。
{ let arr = v => v*2; console.log(arr(2)); var sum = (num1, num2) => { return num1 + num2; } //若是箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,而且使用return语句返回。 }
使用注意点
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不能够看成构造函数,也就是说,不可使用new命令,不然会抛出一个错误。
(3)不可使用arguments对象,该对象在函数体内不存在。若是要用,能够用 rest 参数代替。
(4)不可使用yield命令,所以箭头函数不能用做 Generator 函数。
函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,做为上下文环境(即this对象),绑定到右边的函数上面。
foo::bar; // 等同于 bar.bind(foo); foo::bar(...arguments); // 等同于 bar.apply(foo, arguments); const hasOwnProperty = Object.prototype.hasOwnProperty; function hasOwn(obj, key) { return obj::hasOwnProperty(key); }
尾调用(Tail Call)是函数式编程的一个重要概念,自己很是简单,一句话就能说清楚,就是指某个函数的最后一步是调用另外一个函数。
{ function fn1(x){ console.log('fn1',x); } function fn2(x){ return fn1(x); // 对fn1的调用必须在最后一步操做 } fn2(2); }
ES6 容许直接写入变量和函数,做为对象的属性和方法。这样的书写更加简洁。
{ let a = 5,b=6; let es5 = { a:a, b:b } let es6 = { a, b } console.log(es5,es6) // {a: 5, b: 6} {a: 5, b: 6} let es5_fn = { // fn:function(){ console.log('hello') } } let es6_fn = { fn(){ console.log('hello') } } console.log(es5_fn.fn,es6_fn.fn); }
es6容许属性的key值是动态的变量
{ let a = 'b'; let es5_obj = { a:'c', b:'c' } let es6_obj = { [a]:'c' // a是动态的变量,能够自由赋值 } console.log(es5_obj, es6_obj); }
这个方法至关于es5 中的 ===,来判断属性是否相等
{ console.log('is',Object.is('a','a')); // true console.log('is',Object.is([],[])); // false 数组对象拥有不一样的地址, }
Object.assign方法用于对象的合并,将源对象的全部可枚举属性,复制到目标对象。
{ console.log('拷贝',Object.assign({a:1},{b:2})); //浅拷贝 let test = {a:2,b:3} for(let [key,value] of Object.entries(test)){ // 遍历 console.log([key,value]); //[a:2] //[b:3] } }
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。
{ let a1 = Symbol(); let a2 = Symbol(); console.log(a1===a2) // false let a3 = Symbol.for('a3'); let a4 = Symbol.for('a3'); console.log(a3===a4); //true }
Symbol.for能够用来命名具备相同的key值的对象。
Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的全部用做属性名的 Symbol 值。
Reflect.ownKeys方法能够返回全部类型的键名,包括常规键名和 Symbol 键名。
{ let a1 = Symbol.for('abc'); let obj = { [a1]:123, abc:234, c:345 } console.log(obj); // abc:234 // c:345 // Symbol(abc):123 Object.getOwnPropertySymbols(obj).forEach(function(item){ console.log('symbol',item,obj[item]); //symbol Symbol(abc) 123 }) Reflect.ownKeys(obj).forEach(function(item){ console.log(item,obj[item]); //abc 234 //c 345 //Symbol(abc) 123 }) }
ES6 提供了新的数据结构 Set。它相似于数组,可是成员的值都是惟一的,没有重复的值。Set 自己是一个构造函数,用来生成 Set 数据结构。 Set 结构不会添加剧复的值
{ let list = new Set(); list.add(2); list.add(3); console.log(list.size); //2 let arr = [1,2,3,4,5]; let list2 = new Set(arr); console.log(list2.size); //5 console.log(list2) //{1, 2, 3, 4, 5} let arr2 = [1,2,3,4,2,1]; //这里能够看成数组去重 let list3 = new Set(arr2); console.log(list3) //{1, 2, 3, 4} }
add(value):添加某个值,返回Set结构自己。
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
has(value):返回一个布尔值,表示该值是否为Set的成员。
clear():清除全部成员,没有返回值。
{ let arr = ['add','delete','clear','has']; let list = new Set(arr); console.log(list); // {"add", "delete", "clear", "has"} list.delete('add'); console.log(list); // {"delete", "clear", "has"} console.log(list.has('clear')); // true list.clear(); console.log(list); //{} //set遍历方法 { let arr = ['add','delete','clear','has']; let list = new Set(arr); for(let key of list.keys()){ console.log('keys',key) //keys add //keys delete //keys clear //keys has } for(let value of list.values()){ console.log('values',value) //values add //values delete //values clear //values has } for(let [key,value] of list.entries()){ console.log(key,value); //add add //delete delete //clear clear //has has } list.forEach(function(item){console.log(item)}) // add // delete // clear // has } }
WeakSet结构与Set相似,也是不重复的值的集合。可是,它与 Set有两个区别。首先,WeakSet 的成员只能是对象,而不能是其余类型的值。
WeakSet中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,若是其余对象都再也不引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在
{ const ws = new WeakSet(); ws.add(1) // TypeError: Invalid value used in weak set ws.add(Symbol()) // TypeError: invalid value used in weak set let weakset = new WeakSet() // 没有clear,set方法,不能遍历 let obj = {} weakset.add(obj) // weekset.add(2) WeakSet必须添加的是对象,弱引用 console.log(weakset); }
ES6 提供了 Map 数据结构。它相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的
{ const map = new Map([ ['name', '张三'], ['title', 'Author'] ]); map.size // 2 map.has('name') // true map.get('name') // "张三" map.has('title') // true map.get('title') // "Author" } { let map = new Map(); let arr = ['123']; map.set(arr,'456'); console.log(map,map.get(arr)) // {["123"] => "456"} "456" } { let map = new Map([['a',123],['b',456]]) console.log(map); //{"a" => 123, "b" => 456} console.log(map.size); //2 console.log('123'+map.delete('a')); //true console.log(map) // {"b" => 456} map.clear() console.log(map); //{} }
WeakMap只接受对象做为键名(null除外),不接受其余类型的值做为键名。
WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。所以,只要所引用的对象的其余引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦再也不须要,WeakMap里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操做(即没有key()、values()和entries()方法),也没有size属性。由于没有办法列出全部键名,某个键名是否存在彻底不可预测,跟垃圾回收机制是否运行相关。这一刻能够取到键名,下一刻垃圾回收机制忽然运行了,这个键名就没了,为了防止出现不肯定性,就统一规定不能取到键名。二是没法清空,即不支持clear方法。所以,WeakMap只有四个方法可用:get()、set()、has()、delete()。
{ let weakmap = new WeakMap() //没有clear,set方法,不能遍历 let o = {} weakmap.set(o,123); console.log(weakmap.get(o)); }
Proxy用于修改某些操做的默认行为,等同于在语言层面作出修改,因此属于一种“元编程”(meta programming),即对编程语言进行编程。Proxy 能够理解成,在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操做,能够译为“代理器”。
{ let obj = { name:'gao', time:'2017-08-13', emp:'123', } let temp = new Proxy(obj,{ get(target,key){ return target[key].replace('2017','2018'); }, set(target,key,value){ if(key === 'name'){ return target[key] = value; }else{ return target[key]; } }, has(target,key){ if(key === 'name'){ return target[key]; }else{ return false; } }, deleteProperty(target,key){ if(key.indexOf('i') > -1){ delete target[key]; return true; }else{ return target[key]; } }, ownKeys(target){ return Object.keys(target).filter(item=>item!='name'); } }) console.log('get',temp.time); //get 2018-08-13 temp.time = '2018'; console.log('set',temp.name,temp); //set gao {name: "gao", time: "2017-08-13", temp: "123"} temp.name = 'he'; console.log('set',temp.name,temp); // set he {name: "he", time: "2017-08-13", temp: "123"} console.log('has','name' in temp,'time' in temp); //has true false delete temp.time; console.log('delete',temp); //delete {name: "he", temp: "123"} console.log('ownkeys',Object.keys(temp)); //["emp"] }
Reflect对象与Proxy对象同样,也是 ES6 为了操做对象而提供的新 API。Reflect对象的设计目的有这样几个。
(1) 将Object对象的一些明显属于语言内部的方法(好比Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,将来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上能够拿到语言内部的方法。
(2) 修改某些Object方法的返回结果,让其变得更合理。好比,Object.defineProperty(obj, name, desc)在没法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。
(3) 让Object操做都变成函数行为。某些Object操做是命令式,好比name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象能够方便地调用对应的Reflect方法,完成默认行为,做为修改行为的基础。也就是说,无论Proxy怎么修改默认行为,你总能够在Reflect上获取默认行为。
{ let obj = { name:'gao', time:'2017-08-13', emp:'123', } console.log('reflect get',Reflect.get(obj, 'name')); // reflect get gao Reflect.set(obj,'name','hexaiofei'); console.log(obj); // {name: "hexaiofei", time: "2017-08-13", emp: "123"} console.log('reflect has', Reflect.has(obj,'name')); //reflect has true }
{ function validator(target,validator) { return new Proxy(target,{ _validator:validator, set(target,key,value,proxy){ if(target.hasOwnProperty(key)){ let va = this._validator[key]; if(!!va(value)){ return Reflect.set(target,key,value,proxy); }else{ throw Error(`不能设置${key}到${value}`); } }else{ throw Error(`${key}不存在`); } } }) } const personValidators={ name(value){ return typeof value === 'string' }, age(value){ return typeof value === 'number' && value > 18; } } class Person{ constructor(name,age) { this.name = name; this.age = age; return validator(this,personValidators) } } const person = new Person('lilei',30); console.log(person); person.name = 48; }
ES6 提供了更接近传统语言的写法,引入了Class(类)这个概念,做为对象的模板。经过class关键字,能够定义类。基本上,ES6的class能够看做只是一个语法糖,它的绝大部分功能,ES5 均可以作到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
{ class Parent { constructor(name='gao') { this.name = name; } } let v_parent = new Parent(); console.log(v_parent); //{name: "gao"} }
Class能够经过extends关键字实现继承,这比ES5的经过修改原型链实现继承,要清晰和方便不少。
{ class Parent { constructor(name='gao') { this.name = name; } } class child extends Parent { } let v_child = new child(); console.log(v_child); //{name: "gao"} }
constructor方法是类的默认方法,经过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,若是没有显式定义,一个空的constructor方法会被默认添加。
super这个关键字,既能够看成函数使用,也能够看成对象使用。在这两种状况下,它的用法彻底不一样。第一种状况,super做为函数调用时,表明父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。第二种状况,super做为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。super()在子类constructor构造方法中是为了获取this上下文环境,因此若是在constructor中使用到this,必须在使用this以前调用super(),反之不在constructor中使用this则没必要调用super()
{ class Parent { constructor(name='gao') { this.name = name; } } class child extends Parent { constructor(name='child'){ super(name); this.type = 'child' } } let v_child = new child(); console.log(v_child); //{name: "child", type: "child"} }
与 ES5 同样,在“类”的内部可使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
{ class Parent { constructor(name='gao') { this.name = name; } get longName(){ return 'mk' + this.name; } set longName(value){ // console.log(value); this.name = value; } } let v_parent = new Parent(); console.log('get',v_parent.longName); //get mkgao v_parent.longName = 'hello'; console.log('get',v_parent.longName); //get mkhello }
类至关于实例的原型,全部在类中定义的方法,都会被实例继承。若是在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接经过类来调用,这就称为“静态方法”。
{ class Parent { constructor(name='gao') { this.name = name; } static tell(){ console.log('tell'); } } let v_parent = new Parent(); console.log(v_parent); //{name: "gao"} Parent.tell(); // tell }
静态属性指的是Class自己的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
{ class Parent { constructor(name='gao') { this.name = name; } } Parent.tell = 'nihao'; let v_parent = new Parent(); console.log(v_parent); //{name: "gao"} console.log(Parent.tell); // nihao }
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最先提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。所谓Promise,简单说就是一个容器,里面保存着某个将来才会结束的事件(一般是一个异步操做)的结果。从语法上说,Promise 是一个对象,从它能够获取异步操做的消息。Promise 提供统一的 API,各类异步操做均可以用一样的方法进行处理。
Promise对象有如下两个特色。
(1)对象的状态不受外界影响。Promise对象表明一个异步操做,有三种状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其余手段没法改变。
(2)一旦状态改变,就不会再变,任什么时候候均可以获得这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Fulfiled和从Pending变为Rejected。只要这两种状况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 Resolved(已定型)。若是改变已经发生了,你再对Promise对象添加回调函数,也会当即获得这个结果。这与事件(Event)彻底不一样,事件的特色是,若是你错过了它,再去监听,是得不到结果的。
注意,为了行文方便,本章后面的Resolved统一只指Fulfilled状态,不包含Rejected状态。
有了Promise对象,就能够将异步操做以同步操做的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操做更加容易。
Promise也有一些缺点。首先,没法取消Promise,一旦新建它就会当即执行,没法中途取消。其次,若是不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。
若是某些事件不断地反复发生,通常来讲,使用 Stream 模式是比部署Promise更好的选择。
Promise构造函数接受一个函数做为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用本身部署。
resolve函数的做用是,将Promise对象的状态从“未完成”变为“成功”(即从 Pending 变为 Resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去;reject函数的做用是,将Promise对象的状态从“未完成”变为“失败”(即从 Pending 变为 Rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。
Promise实例生成之后,能够用then方法分别指定Resolved状态和Rejected状态的回调函数。
// ES5的回调函数 { let ajax = function(callback){ console.log('nihao'); setTimeout(function(){ callback && callback.call() },1000) } ajax(function(){ console.log('timeout1'); }) } // es6 Promise的用法 { let ajax = function(){ console.log('wohao'); return new Promise((resolve, reject) => { setTimeout(function(){ resolve(); },1000); }); } ajax().then(function(){ console.log('promise','timeout1'); }) } promise.then(function(value) { // promise的用法 // success }, function(error) { // failure });
Promise实例具备then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的做用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是Resolved状态的回调函数,第二个参数(可选)是Rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。所以能够采用链式写法,即then方法后面再调用另外一个then方法。
{ let ajax = function(){ console.log('dajiahao'); return new Promise((resolve, reject) => { setTimeout(function(){ resolve(); },1000); }); }; ajax().then(function(){ return new Promise((resolve, reject) => { setTimeout(function(){ resolve(); },2000) }); }) .then(function(){ console.log('timeout3'); }) }
Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
{ let ajax = function(num){ console.log('dajiahao'); return new Promise((resolve, reject) => { if(num>6){ console.log('6'); }else{ throw new Error('出错了'); } }); }; ajax(3).then(function(){ console.log('3'); }) .catch(error=>{ console.log(error) //出错了 }) }
Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
var p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all方法接受一个数组做为参数,p一、p二、p3都是 Promise 实例,若是不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每一个成员都是 Promise 实例。)
p的状态由p一、p二、p3决定,分红两种状况。
(1)只有p一、p二、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p一、p二、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
{ function loadImg(src){ return new Promise((resolve, reject) => { let img = document.createElement('img'); img.src=src; img.onload = function(){ resolve(img); } img.onerror = function(error){ reject(error); } }); } function showImgs(imgs){ imgs.forEach(function(img){ document.body.appendChild(img); }) } Promise.all([ loadImg(''), loadImg(''), loadImg(''), ]).then(showImgs) }
Promise.race方法一样是将多个Promise实例,包装成一个新的Promise实例。
var p = Promise.race([p1, p2, p3]);
上面代码中,只要p一、p二、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race方法的参数与Promise.all方法同样,若是不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。
下面是一个例子,若是指定时间内没有得到结果,就将Promise的状态变为reject,不然变为resolve。
{ function loadImg(src){ return new Promise((resolve, reject) => { let img = document.createElement('img'); img.src=src; img.onload = function(){ resolve(img); } img.onerror = function(error){ reject(error); } }); } function showImg(img){ let img = document.createElement('p'); p.appendChild(img); document.body.appendChild(p); } Promise.race([ loadImg(''), loadImg(''), loadImg(''), ]).then(showImgs) }
Iterator 接口的目的,就是为全部数据结构,提供了一种统一的访问机制,即for...of循环。当使用for...of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口。一种数据结构只要部署了 Iterator 接口,咱们就称这种数据结构是”可遍历的“(iterable)。
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具备Symbol.iterator属性,就能够认为是“可遍历的”(iterable)。Symbol.iterator属性自己是一个函数,就是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator,它是一个表达式,返回Symbol对象的iterator属性,这是一个预约义好的、类型为 Symbol的特殊值,因此要放在方括号内。
变量arr是一个数组,原生就具备遍历器接口,部署在arr的Symbol.iterator属性上面。因此,调用这个属性,就获得遍历器对象。
{ let arr = ['hellow','world']; let map = arr[Symbol.iterator](); console.log(map.next()); //{value: "hellow", done: false} console.log(map.next()); //{value: "world", done: false} console.log(map.next()); //{value: "undefined", done: false} }
{ let obj = { start:[1,3,2], end:[7,8,9], [Symbol.iterator](){ let self = this; let index = 0; let arr = self.start.concat(self.end); let len = arr.length; return { next(){ if(index<len){ return { value:arr[index++], done:false } }else{ return { value:arr[index++], done:true } } } } } } for(let key of obj){ console.log(key); //1 3 2 7 8 9 } }
Generator 函数有多种理解角度。从语法上,首先能够把它理解成,Generator函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,仍是一个遍历器对象生成函数。返回的遍历器对象,能够依次遍历Generator函数内部的每个状态。形式上,Generator 函数是一个普通函数,可是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不一样的内部状态(yield在英语里的意思就是“产出”)。
{ let tell = function* (){ yield 'a'; yield 'b'; return 'c'; } let k = tell(); console.log(k.next()); //{value: "a", done: false} console.log(k.next()); //{value: "b", done: false} console.log(k.next()); //{value: "c", done: true} console.log(k.next()); //{value: undefined, done: true} }
因为 Generator 函数就是遍历器生成函数,所以能够把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具备 Iterator 接口。
{ let obj = {}; obj[Symbol.iterator] = function* (){ yield '1'; yield '2'; yield '3'; } for(let value of obj){ console.log(value); // 1 2 3 } }
{ let state = function* (){ yield 'a'; yield 'b'; yield 'c'; } let status = state(); console.log(status.next()); //a console.log(status.next()); //b console.log(status.next()); //c console.log(status.next()); //a console.log(status.next()); //b console.log(status.next()); //c console.log(status.next()); //a }
//简单的抽奖 { let draw = function(count){ console.info(`剩余${count}次`); } let chou = function *(count){ while (count>0) { count--; yield draw(count); } } let start = chou(5); let btn = document.createElement('button'); btn.id = 'start'; btn.textContent = '抽奖'; document.body.appendChild(btn); document.getElementById('start').addEventListener('click',function(){ start.next(); },false); } // 长轮询 { let ajax = function* (){ yield new Promise((resolve, reject) => { setTimeout(function(){ resolve({code:1}) },200) }); } let pull = function(){ let generator = ajax(); let step = generator.next(); step.value.then(function(d){ if(d.code != 0){ setTimeout(function(){ console.log('wait'); //隔一秒输出 wait pull(); },1000) }else{ console.log(d); } }) } pull(); }
修饰器函数一共能够接受三个参数,第一个参数是所要修饰的目标对象,即类的实例(这不一样于类的修饰,那种状况时target参数指的是类自己);第二个参数是所要修饰的属性名,第三个参数是该属性的描述对象。
{ let readonly = function(target,name,descriptor){ descriptor.writable = false; return descriptor; }; class test{ @readonly time(){ return '2017-08-27' } } let tests = new test(); console.log(tests.time()); // 2017-08-27 // let testss = new test(); // // tests.time = function(){ // // console.log('2017-08-28'); // // } // console.log(tests.time()); //Cannot assign to read only property 'time' of object }
修饰器是一个对类进行处理的函数。修饰器函数的第一个参数,就是所要修饰的目标类。
{ let typename = function(target,name,descriptor){ target.myname = 'hello'; }; @typename class test{ } console.log(test.myname) // hello }
ES6 模块不是对象,而是经过export命令显式指定输出的代码,再经过import命令输入。
{ export let A = 123; export function text(){ console.log('123'); } export class hello{ text(){ console.log('345'); } } } { let A = 123; function text(){ console.log('123'); } class hello{ text(){ console.log('345'); } } export default { A, text, hello } }
借鉴了阮一峰ECMAScript 6 入门的内容