ES6簡單總結(搭配簡單的講解和小案例)

數字字母下劃線發表於2018-01-31

在學習es6的過程中,為了方便自己複習,以及檢視,對api做了一個極簡用例介紹。如有錯誤多多指正。

一 let和const

1.let

(1)一個大括號就是一個塊級作用域,let宣告的變數只在自己作用域有效; (2)es6強制開啟嚴格模式,變數未宣告不能引用,所以會報 Uncaught ReferenceError

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;
複製程式碼

2.const

(1)const宣告之後必須賦值,否則會編譯不通過; (2)const宣告的值不允許修改;

const PI = 3.14;
// PI = 2;  
// const PI;
console.log(PI);
複製程式碼

(3)const如果是物件的話,可以向物件中新增屬性,也可以修改a的屬性;json是指向記憶體地址的一個指標,指標的指向不變,但是那個被json指標所指向的記憶體地址所儲存的內容是可以變化的;

const json = {
  a: 2
}
json.a = 3;
json.b = 3;
console.log(json.a)   //3
console.log(json.b)   //3
複製程式碼

二 解構賦值

1.基本用法

先上兩個例子瞭解什麼是解構賦值

{
  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]
}
複製程式碼

2.物件的解構賦值

{
  let a, b;
  ({ a, b } = { a: 1, b: 2 });  //a,b 順序不影響其結構結果
  console.log(a, b); // 1 2
}
複製程式碼

3.預設值

{
  let a, b, rest;
  [a, b, rest = 3] = [1, 2];
console.log(a, b, rest); // 1 2 3
}
複製程式碼

4.實際應用

變數的交換

{
  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]
}
複製程式碼

5.物件的解構賦值的應用

{
  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
}
複製程式碼

6.解構賦值的簡單應用舉例

{
  let metaData = {
    title: 'abc',
    test: [{
      title: 'gaojingbo',
      desc: 'description'
    }]
  }

  let { title: esTitle, test: [{ title: cnTitle }] } = metaData;
  console.log(esTitle, cnTitle);
}
複製程式碼

三 正則的擴充套件

1.建構函式來建立正則

{
  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新增的,用來獲取正規表示式的修飾符
}
複製程式碼

2.g修飾符和y修飾符

y修飾符的作用與g修飾符類似,也是全域性匹配,後一次匹配都從上一次匹配成功的下一個位置開始。不同之處在於,g修飾符只要剩餘位置中存在匹配就可,而y修飾符確保匹配必須從剩餘的第一個位置開始。

{
  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簡單總結(搭配簡單的講解和小案例)

3.u修飾符(unicode)

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
}
複製程式碼

四 字串擴充套件

1.unicode的表示方法

{
  console.log('a', '\u0061'); // a a
  console.log('s', '\u20BB7'); // s ₻7  把前兩個位元組當作一個整體
  console.log('s', '\u{20BB7}'); // s ?  unicode編碼用{}可以正常識別
}
複製程式碼

2.codePointAt和charCodeAt的對比

對於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
}
複製程式碼

3.fromCharCode和fromCodePoint

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'))  //?
}
複製程式碼

4.字串遍歷器

{
  // 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
  }
}
複製程式碼

5.一些常用的字串api

{
  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
}
複製程式碼

6.模板字串

{
  let name = "List";
  let info = "hello world";
  let m = `i am ${name} ${info}`;
  console.log(m);  //i am List hello world
}
複製程式碼

7.標籤模板

{
  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簡單總結(搭配簡單的講解和小案例)

8.String.row API

ES6還為原生的String物件,提供了一個raw方法。String.raw方法,往往用來充當模板字串的處理函式,返回一個斜槓都被轉義(即斜槓前面再加一個斜槓)的字串,對應於替換變數後的模板字串。

{
  console.log('raw '+String.raw`hi\n${1+2}`)
  console.log('noRaw '+`hi\n${1+2}`)
}
複製程式碼

輸出結果

ES6簡單總結(搭配簡單的講解和小案例)

五 數值擴充套件

1.二進位制八進位制表示法

從 ES5 開始,在嚴格模式之中,八進位制就不再允許使用字首0表示,ES6進一步明確,要使用字首0o表示。如果要將0b和0o字首的字串數值轉為十進位制,要使用Number方法。

{
  console.log('B',0b11010101010);  //二進位制表示,b大小寫都可以
  console.log('O',0O1237637236);  // 八進位制表示法
}
複製程式碼

2.Number.isFinite()和Number.isNaN()

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
}
複製程式碼

3.Number.isInteger

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
}
複製程式碼

4.Number.MAX_SAFE_INTEGER,Number.MIN_SFAE_INTEGER和isSafeInterger

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));
}
複製程式碼

5.Math.trunc和Math.sign

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
} 
複製程式碼

6.cbrt

cbrt用來計算一個數的開方

{
  console.log('-1',cbrt(-1));   //-1
  console.log('8',cbrt(8));     //2
}
複製程式碼

六 陣列擴充套件

1. Array.of

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]
}
複製程式碼

2.Array.from

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]
}
複製程式碼

3.Array.fill

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]  // 後兩個參數列示索引的位置
}
複製程式碼

4.entries(),keys() 和 values()

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 
  }
}
複製程式碼

5.Array.copyWithin

擷取一定長度的數字並且替換在相對應的索引的位置

{
  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] 

}
複製程式碼

6.findIndex和find

陣列例項的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
}
複製程式碼

7.includes

Array.prototype.includes方法返回一個布林值,表示某個陣列是否包含給定的值,與字串的includes方法類似。ES2016 引入了該方法。

{
  console.log([1,2,NaN].includes(1));  // true
  console.log([1,2,NaN].includes(NaN));  // true
}
複製程式碼

8.擴充套件運算子

擴充套件運算子(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>]
複製程式碼

七 函式擴充套件

1.預設值

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
}
複製程式碼

3.rest引數

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
}
複製程式碼

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 函式。

5.繫結 this

函式繫結運算子是並排的兩個冒號(::),雙冒號左邊是一個物件,右邊是一個函式。該運算子會自動將左邊的物件,作為上下文環境(即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);
}
複製程式碼

八 物件擴充套件

1.屬性的簡介表示法

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);
}
複製程式碼

2.動態key值

es6允許屬性的key值是動態的變數

{
	let a = 'b';
	let es5_obj = {
		a:'c',
		b:'c'
	}
	let es6_obj = {
		[a]:'c'   // a是動態的變數,可以自由賦值
	}
	console.log(es5_obj, es6_obj);
}
複製程式碼

3.Object.is

這個方法相當於es5 中的 ===,來判斷屬性是否相等

{
	console.log('is',Object.is('a','a'));  // true
	console.log('is',Object.is([],[]));    // false   陣列物件擁有不同的地址,
}
複製程式碼

4.Object.assign

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]
	}
}
複製程式碼

九 Symbol

1.Symbol簡單舉例

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
}
複製程式碼

2.Symbol的一些API

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
	})

}
複製程式碼

十 Map和Set資料結構

1.set的基本用法

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
	}
}

複製程式碼

2.WeakSet基本用法

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);
}
複製程式碼

3.Map的基本用法

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);					//{}
}
複製程式碼

4.WeakMap的一些API

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和reflect

1.Proxy

Proxy用於修改某些操作的預設行為,等同於在語言層面做出修改,所以屬於一種“超程式設計”(meta programming),即對程式語言進行程式設計。Proxy 可以理解成,在目標物件之前架設一層“攔截”,外界對該物件的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾和改寫。Proxy這個詞的原意是代理,用在這裡表示由它來“代理”某些操作,可以譯為“代理器”。

{
  let obj = {
    name:'gaojingbo',
    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 gaojingbo   {name: "gaojingbo", time: "2017-08-13", temp: "123"}

  temp.name = 'hexiaofei';
  console.log('set',temp.name,temp); // set hexiaofei  {name: "hexiaofei", 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: "hexiaofei", temp: "123"}

  console.log('ownkeys',Object.keys(temp));  //["emp"]
}
複製程式碼

2.Reflect

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:'gaojingbo',
    time:'2017-08-13',
    emp:'123',
  }
  console.log('reflect get',Reflect.get(obj, 'name'));  // reflect get gaojingbo
  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
}
複製程式碼

3.簡單應用

{
  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;

}
複製程式碼

十二 Class的基本語法

1.簡介

ES6 提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為物件的模板。通過class關鍵字,可以定義類。基本上,ES6的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓物件原型的寫法更加清晰、更像物件導向程式設計的語法而已。

{
  class Parent {
    constructor(name='gaojingbo') {
      this.name = name;
    } 
  }

  let v_parent = new Parent();
  console.log(v_parent);  //{name: "gaojingbo"}
}
複製程式碼

2.繼承

Class可以通過extends關鍵字實現繼承,這比ES5的通過修改原型鏈實現繼承,要清晰和方便很多。

{
  class Parent {
    constructor(name='gaojingbo') {
      this.name = name;
    } 
  }

  class child extends Parent {

  }
  let v_child = new child();
  console.log(v_child);  //{name: "gaojingbo"}
}
複製程式碼

3.constructor

constructor方法是類的預設方法,通過new命令生成物件例項時,自動呼叫該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被預設新增。

4.super關鍵字

super這個關鍵字,既可以當作函式使用,也可以當作物件使用。在這兩種情況下,它的用法完全不同。第一種情況,super作為函式呼叫時,代表父類的建構函式。ES6 要求,子類的建構函式必須執行一次super函式。第二種情況,super作為物件時,在普通方法中,指向父類的原型物件;在靜態方法中,指向父類。super()在子類constructor構造方法中是為了獲取this上下文環境,所以如果在constructor中使用到this,必須在使用this之前呼叫super(),反之不在constructor中使用this則不必呼叫super()

{
  class Parent {
    constructor(name='gaojingbo') {
      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"}
}
複製程式碼

5.getter和setter

與 ES5 一樣,在“類”的內部可以使用get和set關鍵字,對某個屬性設定存值函式和取值函式,攔截該屬性的存取行為。

{
  class Parent {
    constructor(name='gaojingbo') {
      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 mkgaojingbo

  v_parent.longName = 'hello';
  console.log('get',v_parent.longName);  //get mkhello
}
複製程式碼

6.靜態方法

類相當於例項的原型,所有在類中定義的方法,都會被例項繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被例項繼承,而是直接通過類來呼叫,這就稱為“靜態方法”。

{
  class Parent {
    constructor(name='gaojingbo') {
      this.name = name;
    } 
    static tell(){
      console.log('tell');
    }
  }

  let v_parent = new Parent();
  console.log(v_parent);  //{name: "gaojingbo"}
  Parent.tell(); // tell
}
複製程式碼

7.靜態屬性

靜態屬性指的是Class本身的屬性,即Class.propName,而不是定義在例項物件(this)上的屬性。

{
  class Parent {
    constructor(name='gaojingbo') {
      this.name = name;
    } 
    
  }
  Parent.tell = 'nihao';

  let v_parent = new Parent();
  console.log(v_parent);  //{name: "gaojingbo"}
  console.log(Parent.tell);   // nihao
}
複製程式碼

十三 Promise

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更好的選擇。

1.基本用法

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
});
複製程式碼

2.Promise.prototype.then()

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');
  })
}
複製程式碼

3.Promise.prototype.catch()

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)   //出錯了
  })


}
複製程式碼

4.Promise.all

Promise.all方法用於將多個 Promise 例項,包裝成一個新的 Promise 例項。

var p = Promise.all([p1, p2, p3]);
複製程式碼

上面程式碼中,Promise.all方法接受一個陣列作為引數,p1、p2、p3都是 Promise 例項,如果不是,就會先呼叫下面講到的Promise.resolve方法,將引數轉為 Promise 例項,再進一步處理。(Promise.all方法的引數可以不是陣列,但必須具有 Iterator 介面,且返回的每個成員都是 Promise 例項。)

p的狀態由p1、p2、p3決定,分成兩種情況。

(1)只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個陣列,傳遞給p的回撥函式。

(2)只要p1、p2、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)
}
複製程式碼

4.Promise.race

Promise.race方法同樣是將多個Promise例項,包裝成一個新的Promise例項。

var p = Promise.race([p1, p2, p3]);
複製程式碼

上面程式碼中,只要p1、p2、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 迴圈

Iterator 介面的目的,就是為所有資料結構,提供了一種統一的訪問機制,即for...of迴圈。當使用for...of迴圈遍歷某種資料結構時,該迴圈會自動去尋找 Iterator 介面。一種資料結構只要部署了 Iterator 介面,我們就稱這種資料結構是”可遍歷的“(iterable)。 ES6 規定,預設的 Iterator 介面部署在資料結構的Symbol.iterator屬性,或者說,一個資料結構只要具有Symbol.iterator屬性,就可以認為是“可遍歷的”(iterable)。Symbol.iterator屬性本身是一個函式,就是當前資料結構預設的遍歷器生成函式。執行這個函式,就會返回一個遍歷器。至於屬性名Symbol.iterator,它是一個表示式,返回Symbol物件的iterator屬性,這是一個預定義好的、型別為 Symbol的特殊值,所以要放在方括號內。

1.陣列的Symbol.iterator屬性

變數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}
}
複製程式碼

2.自定義的Iterator介面

{
  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
  }
}
複製程式碼

十五 Genertor

1.基本概念

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}
}
複製程式碼

2.與 Iterator 介面的關係

由於 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
  }
}
複製程式碼

3.next方法

{
  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
}
複製程式碼

4.Genertor的簡單應用

//簡單的抽獎
{
  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();
}
複製程式碼

十六修飾器

1.方法的修飾

修飾器函式一共可以接受三個引數,第一個引數是所要修飾的目標物件,即類的例項(這不同於類的修飾,那種情況時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
}
複製程式碼

2.類的修飾

修飾器是一個對類進行處理的函式。修飾器函式的第一個引數,就是所要修飾的目標類。

{
  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 入門的內容

相關文章