在學習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 入門的內容