ES6經常使用知識點總結(上)

  這片文章主要是基於阮一峯老師ECMAScript 6 入門。在看了阮一峯老師的這ES6入門以後,本身作了一下總結,將一些以爲對本身目前有用的東西整理出來方便往後再來鞏固複習。總以爲看別人的東西當時懂了過了一段時間就忘記了,因此我老是會將別人的東西驗證一遍,這樣對知識的理解是能提高一個層次的。javascript

一、ECMAScript和JavaScript的關係

  前者是後者的規格,後者是前者的一種實現。ES6 既是一個歷史名詞,也是一個泛指,含義是 5.1 版之後的 JavaScript 的下一代標準,涵蓋了 ES201五、ES201六、ES2017 等等,而 ES2015 則是正式名稱,特指該年發佈的正式版本的語言標準。html

二、let和const

 2.1 let

let用來聲明變量。它的用法相似於var,可是所聲明的變量,只在let命令所在的代碼塊內有效
let和const有幾個特色:
一、 不存在變量聲明提高;
二、 暫時性死區(只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。);
三、 不容許重複聲明。不容許在相同做用域內,重複聲明同一個變量。
四、 塊級做用域java

塊級做用域的例子
    {
        let a=12;
        var b=23;
    }
    console.log(b);//23
    console.log(a);// a is not defined
    for(let i=0;i<10;i++){				
      
    }
    console.log(i);// is not defined
-----------------------------------------------------------------
    var a = [];
    for (let i = 0; i < 10; i++) {
      a[i] = function () {
        console.log(i);
      };
    }
    a[6](); // 6
複製代碼

  分析:變量i是let聲明的,因此i只在let聲明的代碼塊內有效。for循環一共循環了10次,每一次都是一個獨立的代碼塊——{},因此每次循環中的i都是獨立的,當前的i只在當前循環有效,因此每一次循環的i其實都是一個新的變量,因此最後輸出的是6。
  for循環還有一個特別之處,就是設置循環變量的那部分是一個父做用域,而循環體內部是一個單獨的子做用域。JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算。node

for (let i = 0; i < 3; i++) {
      let i = 'abc';
      console.log(i);
    }
    // abc
    // abc
    // abc
複製代碼

2.2 不存在變量聲明提高es6

// var 的狀況
    console.log(foo); // 輸出undefined,變量聲明提高,至關於在輸出以前就var  foo;
    var foo = 2;
    // let 的狀況
    console.log(bar); // 報錯ReferenceError,沒有變量聲明提高
    let bar = 2;
    ————————————————————————————————————————————————————————————
複製代碼

2.3 暫時性死區(temporal dead zone,簡稱 TDZ)
  在區塊中使用let和const命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯(聲明以前都是死區)。本質:只要一進入當前做用域,所要使用的變量就已經存在了,可是不可獲取,只有等到聲明變量的那一行代碼出現,才能夠獲取和使用該變量。ajax

2.4 不容許重複聲明算法

  let不容許在相同做用域內,重複聲明同一個變量。編程

function func() {
      let a = 10;
      var a = 1;
    }
    funb()// // 報錯 Identifier 'a' has already been declared
    
    function func() {
      let a = 10;
      let a = 1;
    }
    func()// 報錯 Identifier 'a' has already been declared
    ————————————————————————————————————————————————————————
    function bar(x = y, y = 2) {
      return [x, y];
    }
    bar(); // 報錯   參數x默認值等於另外一個參數y,而此時y尚未聲明,屬於"死區"(參數讀取從左至右)。
複製代碼

  不能在函數內部從新聲明參數。json

function funb(arg) {
      let arg;
    }
    func() // 報錯Identifier 'arg' has already been declared     形參arg跟局部變量arg在同一個{}內,因此報錯
    function func(arg) {
      {
        let arg;
        console.log(arg);//undefined
      }
      console.log(arg);//34
     }
     func(34)
複製代碼

2.5 塊級做用域數組

優勢:

一、沒有塊級做用域,內層變量可能會覆蓋外層變量(變量聲明提高)。
二、用來計數的循環變量會泄露爲全局變量。

特色:

一、 容許任意嵌套。
二、 外層做用域沒法讀取內層做用域的變量。
三、 使得當即執行函數再也不必要了。
四、 容許在塊級做用域中聲明函數,函數聲明相似於var,函數聲明會提高到所在的塊級做用域的頭部。

2.6 const

  const一旦聲明變量,就必須當即初始化,不能留到之後賦值,且變量的值也不能改變。本質:並非變量的值不得改動,而是變量指向的那個內存地址所不得改動。
  對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,所以等同於常量。但對於複合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的(即老是指向一個固定的地址),至於它指向的數據結構是否是可變的,就徹底不能控制了。

一、變量指向的是對象時,能夠改變該對象的屬性。可是不可將該變量指向另外一個對象。
    const obj = {}
    // 爲 foo 添加一個屬性,能夠成功
    obj.prop = 123;
    // 將 obj 指向另外一個對象,就會報錯,此時已經改變了obj所指向的內存地址了
    obj = {}; // TypeError: "foo" is read-only
一、變量指向的是數組時,能夠改變該數組中的元素及數組的屬性。可是不可將該變量指向另外一個數組。
    const a = [];
    a.push('Hello'); // 可執行
    a.length = 0;    // 可執行
    a = ['Dave'];    // 報錯,指向了另外一個數組
複製代碼

2.7 ES6聲明變量的6種方式

  var、function、let、const、class、import。es5只有var和function兩種。
  頂層對象的差別: 在瀏覽器環境指的是window對象,在 Node中 指的是global對象,在Web Worker 裏面,self也指向頂層對象。
  ES5 之中,頂層對象的屬性與全局變量是等價的。ES6中的var命令和function命令聲明的全局變量,依舊是頂層對象的屬性;let命令、const命令、class命令聲明的全局變量,不屬於頂層對象的屬性。

var a = 1;
    window.a // 1
    
    let b = 1;
    window.b // undefined
複製代碼

三、變量的解構賦值

  ES6容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構。本質上,這種寫法屬於模式匹配,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。 若是解構不成功,變量的值就等於undefined。

 三、1 數組的結構賦值

事實上,只要某種數據結構具備 Iterator 接口,均可以採用數組形式的解構賦值。

let [foo = true] = [];foo // true
    let [x, y = 'b'] = ['a']; // x='a', y='b'
    let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
    let [x = 1] = [null]; x //null null不嚴格等於undefined,可是null==undefined
    let [x = 1, y = x] = [];     // x=1; y=1
    let [x = 1, y = x] = [2];    // x=2; y=2
    let [x = 1, y = x] = [1, 2]; // x=1; y=2
    let [x = y, y = 1] = [];     //  y is not undefined
    從左到右的讀取。
     let [x , y] = [];//[undefined,undefined]
複製代碼

   注意:ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。因此,只有當一個數組成員嚴格等於undefined,默認值纔會生效。若是一個數組成員是null,默認值就不會生效,由於null不嚴格等於undefined。

 三、2 對象的解構賦值

   對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。

let { bar, foo } = { foo: "aaa", bar: "bbb" };
    foo // "aaa"
    bar // "bbb"
    let { baz } = { foo: "aaa", bar: "bbb" };baz // undefined
    let { foo: baz } = { foo: "aaa", bar: "bbb" };
    baz//"aaa",foo是匹配的模式,baz纔是變量名
    
    let { foo, bar } = { foo: "aaa", bar: "bbb" };
    是下面表示的簡寫。
    let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
複製代碼

   因爲數組本質是特殊的對象,所以能夠對數組進行對象屬性的解構。數組arr的0鍵對應的值是1,[arr.length - 1]就是2鍵,對應的值是3。方括號這種寫法,屬於屬性名錶達式

let arr = [1, 2, 3];
    let {0 : first, [arr.length - 1] : last} = arr;
    first // 1
    last // 3
複製代碼

 三、3 字符串的解構賦值

   字符串被轉換成了一個相似數組的對象。

const [a, b, c, d, e] = 'hello';
    a // "h"
    b // "e"
    c // "l"
    d // "l"
    e // "o"
複製代碼

   相似數組的對象都有一個length屬性,所以還能夠對這個屬性解構賦值。

let {length : len} = 'hello';
    len // 5
複製代碼

 三、4 數值和布爾值的解構賦值

let {toString: s} = 123;
    s === Number.prototype.toString // true
    let {toString: s} = true;
    s === Boolean.prototype.toString // true
複製代碼

   解構賦值的規則是,只要等號右邊的值不是對象或數組,就先將其轉爲對象。 因爲undefined和null沒法轉爲對象,因此對它們進行解構賦值,都會報錯。

let { prop: x } = undefined; // TypeError
    let { prop: y } = null; // TypeError
複製代碼

 三、5 函數參數的解構賦值

function move({x = 0, y = 0} = {}) {
        return [x, y];
    }
    move({x: 3, y: 8}); // [3, 8]
    move({x: 3}); // [3, 0]
    move({}); // [0, 0]
    move(); // [0, 0]
複製代碼

   上面代碼中,函數move爲變量x和y指定默認值,函數move的參數是一個對象,經過對這個對象進行解構,獲得變量x和y的值。若是解構失敗,x和y等於默認值。用實參將{}覆蓋。

function move({x, y} = { x: 0, y: 0 }) {
      return [x, y];
    }
    move({x: 3, y: 8}); // [3, 8]
    move({x: 3}); // [3, undefined],至關於{x,y}={x,3}
    move({}); // [undefined, undefined],至關於{x,y}={};
    move(); // [0, 0]
複製代碼

   上面代碼是爲函數move的參數(形參)指定默認值,而不是爲變量x和y指定默認值,因此會獲得與前一種寫法不一樣的結果。這種寫法直接是將所傳參數將默認參數進行覆蓋。用實參將{x:0,y:0}覆蓋。
   上面兩種寫法本質上都是用所傳參數將默認參數進行覆蓋。

 三、6 解構賦值的用處

  (1) 交換變量的值。
let{x,y}={y,x};    
複製代碼

  (2) 從函數返回多個值;

function example() {
      return {
        foo: 1,
        bar: 2
      };
    }
    let { foo, bar } = example();
複製代碼

  (3) 函數參數的定義;

function f([x, y, z]) { ... }
    f([1, 2, 3]);
複製代碼

  (4) 提取json數據

let jsonData = {
        id: 42,
        status: "OK",
        data: [867, 5309]
    };
    let { id, status, data: number } = jsonData;
複製代碼

  (5) 輸入模塊的指定方法;

const { SourceMapConsumer, SourceNode } = require("source-map");
複製代碼

  (6) 函數參數的默認值(這樣避免了在函數內部再設置默認值)

jQuery.ajax = function (url, {
          async = true,
          beforeSend = function () {},
          cache = true,
          complete = function () {},
          crossDomain = false,
          global = true,
          // ... more config
    } = {}) {
      // ... do stuff
    };
複製代碼

  (7) 遍歷map結構(map原生支持iterator接口)

const map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    for (let [key, value] of map) {
      console.log(key + " is " + value);
    }
    // first is hello
    // second is world
    // 獲取鍵名
    for (let [key] of map) {
      // ...
    }
    // 獲取鍵值
    for (let [,value] of map) {
      // ...
    }
複製代碼

四、字符串的擴展

 四、1 includes()、startsWith()、endsWith()

includes():返回布爾值,表示是否找到了參數字符串。
startsWith():返回布爾值,表示參數字符串是否在原字符串的頭部。 endsWith():返回布爾值,表示參數字符串是否在原字符串的尾部。

  這三個方法都支持第二個參數,表示開始搜索的位置。endsWith的行爲與其餘兩個方法有所不一樣,n表示的是結束搜索的位置,而其餘兩個方法針n個表示的是開始搜索的位置。

 四、2 repeat()

  repeat方法返回一個新字符串,表示將原字符串重複n次。 參數若是是小數,會被取整。至關於調用了parseInt()。

」a」.repeat(1.9)==>」a」.
複製代碼

  若是repeat的參數是字符串,則會先轉換成數字。

'na'.repeat('na') // ""  Number("na")等於NAN
    'na'.repeat('3') // "nanana"
複製代碼

 四、3 padStart(),padEnd()

  padStart()用於頭部補全,padEnd()用於尾部補全。padStart()和padEnd()一共接受兩個參數,第一個參數是字符串補全生效的最大長度,第二個參數是用來補全的字符串。   (1) 若是原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串。

'xxx'.padStart(2, 'ab') // 'xxx'
    'xxx'.padEnd(2, 'ab') // 'xxx'
    'xxx'.padStart(5, 'ab') // 'abxxx'
    'xxx'.padEnd(5, 'ab') // 'xxxab'
複製代碼

  (2) 若是用來補全的字符串與原字符串,二者的長度之和超過了最大長度,則會截去超出位數的補全字符串。

'abc'.padStart(10, '0123456789') // '0123456abc'
複製代碼

  (3) 若是省略第二個參數,默認使用空格補全長度。

'x'.padStart(4) // '   x'
    'x'.padEnd(4) // 'x   '
複製代碼

 四、4 模板字符串

  是加強版的字符串,用反引號(`)標識。

  (1) 若是在模板字符串中須要使用反引號,則前面要用反斜槓轉義。

let greeting = `\`Yo\` World!`;  
複製代碼

  (2) 全部模板字符串的空格和換行,都是被保留的,好比ul標籤前面會有一個換行。若是你不想要這個換行,可使用trim方法消除它。

$('#list').html(`
    <ul>
      <li>first</li>
      <li>second</li>
    </ul>
    `.trim());
複製代碼

  (3)模板字符串中嵌入變量,須要將變量名寫在${}之中。

  (4)大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。若是大括號中的值不是字符串,將按照通常的規則轉爲字符串。本質:模板字符串的大括號內部,就是執行 JavaScript 代碼。

let x = 1;
    let y = 2;
    `${x} + ${y} = ${x + y}`
    // "1 + 2 = 3"
    `${x} + ${y * 2} = ${x + y * 2}`
    // "1 + 4 = 5"
    let obj = {x: 1, y: 2};
    `${obj.x + obj.y}`
    // "3"
複製代碼

  (5)模板字符串之中還能調用函數。

function fn() {
      return "Hello World";
    }
    `foo ${fn()} bar`
    // foo Hello World bar
複製代碼

五、數值的擴展

 五、1 Number.isFinite()、Number.isNaN()

  這兩個新方法只對數值有效,不會先調用Number()方法。

Number.isFinite(): 用來檢查一個數值是否爲有限的(finite)。若是參數類型不是數值,Number.isFinite一概返回false。
Number.isNaN(): 用來檢查一個值是否爲NaN。Number.isNaN()只有對於NaN才返回true,非NaN一概返回false。

 五、2 Number.isSafeInteger

  Number.isSafeInteger()則是用來判斷一個整數是否落在這個範圍以內。javaScript 可以準確表示的整數範圍在-2^53到2^53之間(不含兩個端點),超過這個範圍,沒法精確表示這個值。Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER這兩個常量,用來表示這個範圍的上下限。

 五、3 Math對象的擴展

  擴展方法在使用時都會參數使用Number()轉爲數值來來處理。

  Math.trunc(): 用於去除一個數的小數部分,返回整數部分。對於非數值,Math.trunc內部使用Number方法將其先轉爲數值(本質上就是parseInt()方法)。

Math.trunc(4.1) // 4
    Math.trunc(4.9) // 4
    Math.trunc(-4.1) // -4
    Math.trunc(-4.9) // -4
    Math.trunc(-0.1234) // -0
    Math.trunc('123.456') // 123
    Math.trunc(true) //1
    Math.trunc(false) // 0
    Math.trunc(null) // 0
    Math.trunc('123.456') // 123
    Math.trunc(true) //1
    Math.trunc(false) // 0
    Math.trunc(null) // 0
複製代碼

  Math.sign(): 用來判斷一個數究竟是正數、負數、仍是零。

• 參數爲正數,返回+1;
• 參數爲負數,返回-1;
• 參數爲 0,返回0;
• 參數爲-0,返回-0;
• 其餘值,返回NaN。

Math.sign(-5) // -1
    Math.sign(5) // +1
    Math.sign(0) // +0
    Math.sign(-0) // -0
    Math.sign(NaN) // NaN
複製代碼

  Math.cbrt(): 用於計算一個數的立方根。對於非數值,Math.cbrt方法內部也是先使用Number方法將其轉爲數值。

Math.cbrt(-1) // -1
    Math.cbrt(0)  // 0
    Math.cbrt(1)  // 1
    Math.cbrt(2)  // 1.2599210498948734
    Math.cbrt("8")//2
複製代碼

  Math.imul(): 方法返回兩個數以 32 位帶符號整數形式相乘的結果,返回的也是一個 32 位的帶符號整數。

Math.imul(2, 4)   // 8
    Math.imul(-1, 8)  // -8
    Math.imul(-2, -2) // 4
複製代碼

  Math.hypot(): 方法返回全部參數的平方和的平方根。

Math.hypot(3, 4);        // 5
    Math.hypot(3, 4, 5);     // 7.0710678118654755
    Math.hypot();            // 0
    Math.hypot(NaN);         // NaN
    Math.hypot(3, 4, 'foo'); // NaN
    Math.hypot(3, 4, '5');   // 7.0710678118654755
    Math.hypot(-3);          // 3
複製代碼

六、函數的擴展

 六、1 函數參數的默認值

  參數變量是默認聲明的,因此不能用let或const再次聲明。

function foo(x = 5) {
      let x = 1; // error
      const x = 2; // error
    }
    foo()//Identifier 'x' has already been declared
複製代碼

 六、2 函數的length屬性

  指定了默認值之後,函數的length屬性,將返回沒有指定默認值以前的的參數的個數。 也就是說,指定了默認值後,length屬性將失真。默認值後面的參數將不參加計算。函數的length屬性,不包括 rest 參數。

(function (a) {}).length // 1
    (function (a = 5) {}).length // 0
    (function (a, b, c = 5) {}).length // 2
    (function(...args) {}).length // 0
複製代碼

 六、3 做用域

  一旦設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域(context)。等到初始化結束,這個做用域就會消失。(本質上是暫時性死區和不能重複聲明)

var x = 1;
    function f(x, y = x) {
      //let x=3;Identifier 'x' has already been declared
      //let y=7;// Identifier 'y' has already been declared
      console.log(y);//2
    }
    f(2) 
複製代碼

 六、4 rest參數

  形式爲(...變量名),用於獲取函數的多餘參數,這樣就不須要使用arguments對象了。rest參數搭配的變量是一個數組,該變量將多餘的參數放入數組中。注意:rest參數以後不能再有其餘參數(即只能是最後一個參數),不然會報錯。

function push(array, ...items) {
      items.forEach(function(item) {
        array.push(item);
        console.log(item);
      });
    }
    var a = [];
    push(a, 1, 2, 3)
複製代碼

 六、5 嚴格模式

  只要函數參數使用了默認值、解構賦值、或者擴展運算符(ES6語法默認是嚴格模式),那麼函數內部就不能顯式設定爲嚴格模式,不然會報錯。

 六、6 name屬性

  若是將一個匿名函數賦值給一個變量,ES5的name屬性,會返回空字符串,而 ES6 的name屬性會返回實際的函數名。 若是將一個具名函數賦值給一個變量,則 ES5 和 ES6 的name屬性都返回這個具名函數本來的名字。

const bar = function baz() {};
    // ES5
    bar.name // "baz"
    // ES6
    bar.name // "baz"
複製代碼

 六、7 箭頭函數

  ES6 容許使用「箭頭」(=>)定義函數。若是箭頭函數不須要參數或須要多個參數,就使用一個圓括號表明參數部分。若是箭頭函數的代碼塊部分多於一條語句,就要使用大括號將它們括起來,而且使用return語句返回。 若是箭頭函數直接返回一個對象,必須在對象外面加上括號 ,不然會報錯。

// 報錯
    let getTempItem = id => { id: id, name: "Temp" };
    // 不報錯
    let getTempItem = id => ({ id: id, name: "Temp" });
複製代碼

箭頭函數須要注意的地方有如下幾點
  1. 函數體內的this對象,就是定義時所在的對象(固定不變),而不是使用時所在的對象。
  2. 不能夠看成構造函數, 也就是說,不可使用new命令,不然會拋出一個錯誤。   3. 不可使用arguments對象, 該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。
  4. 不可使用yield命令, 所以箭頭函數不能用做 Generator 函數。

function Timer() {
      this.s1 = 0;
      this.s2 = 0;
      // 箭頭函數
      setInterval(() => this.s1++, 1000);
      // 普通函數
      setInterval(function () {
        this.s2++;//this表示window,setInterval是window的屬性
      }, 1000);
    }
    var timer = new Timer();
    setTimeout(() => console.log('s1: ', timer.s1), 3100);
    setTimeout(() => console.log('s2: ', timer.s2), 3100);
    // s1: 3
    // s2: 0
複製代碼

  上面代碼中,Timer函數內部設置了兩個定時器,分別使用了箭頭函數和普通函數。前者的this綁定定義時所在的做用域(即Timer函數),後者的this指向運行時所在的做用域(即全局對象)。因此,3100 毫秒以後,timer.s1被更新了 3 次,而timer.s2一次都沒更新。 this固定化的本質:並非由於箭頭函數內部有綁定this的機制,實際緣由是箭頭函數根本沒有本身的this,致使內部的this就是外層代碼塊的this。 正常狀況下,this引用的是函數據以執行的環境對象,或者說是調用該函數的對象。

function foo() {
      return () => {
        return () => {
          return () => {
            console.log('id:', this.id);
          };
        };
      };
    }
    var f = foo.call({id: 1});
    var t1 = f.call({id: 2})()(); // id: 1
    var t2 = f().call({id: 3})(); // id: 1
    var t3 = f()().call({id: 4}); // id: 1
複製代碼

  上面代碼之中,只有一個this,就是函數foo的this,因此t一、t二、t3都輸出一樣的結果。由於全部的內層函數都是箭頭函數,都沒有本身的this,它們的this其實都是最外層foo函數的this。

箭頭函數不適用場合:

  一、定義函數的方法(此時應該用普通函數的方式)

var lives=18;
    const cat = {
      lives: 9,
      a:this.lives,//this指向的是window,
      say:function(){
      	console.log(this.lives);//this指的是cat
      },
      jumps: () => {
        this.lives--;//this指的是window,定義時的this指的就是window
      }
    }
    cat.say();
    cat.jumps();
    console.log(cat.a);
複製代碼

  二、須要動態this的時候

var button = document.getElementById('press');
    button.addEventListener('click', () => {
      this.classList.toggle('on');//this指的是window
    })
複製代碼

適用場合:回調

var handler = {
      id: '123456',
      init: function() {
        document.addEventListener('click',
          event => this.doSomething(event.type), false);//this指的是handler
      },
      doSomething: function(type) {
        console.log('Handling ' + type  + ' for ' + this.id);//this指的是hander
      }
    };
複製代碼

 六、8 尾調用

  是指某個函數的最後一步是調用另外一個函數。 尾調用因爲是函數的最後一步操做,因此不須要保留外層函數的調用幀,由於調用位置、內部變量等信息都不會再用到了,只要直接用內層函數的調用幀,取代外層函數的調用幀就能夠了。(就是說外層函數的做用域鏈會被銷燬,但它的活動對象任然會留在內存中)

function f(x){
      return g(x);
    }
複製代碼

 六、9 尾遞歸

  尾調用自身,就稱爲尾遞歸。缺點:把全部用到的內部變量改寫成函數的參數。優勢:不會發生棧溢出,相對節省內存。

function factorial(n, total) {
      if (n === 1) return total;
      return factorial(n - 1, n * total);
    }
    factorial(5, 1) // 120
複製代碼

採用es6語法(參數的默認值)能夠解決這個缺點

function factorial(n, total=1) {
      if (n === 1) return total;
      return factorial(n - 1, n * total);
    }
    factorial(5) // 120
複製代碼

七、數組的擴展

 七、1 擴展運算符

  擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。 擴展運算符背後調用的是遍歷器接口(Symbol.iterator),若是一個對象沒有部署這個接口,就沒法轉換。

  注意:擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。本質上就是rest參數

console.log(…[1,2,3])// 1 2 3
複製代碼

  擴展運算符的應用:
  一、 替代函數的apply用法

// ES5 的寫法
    function f(x, y, z) {
      // ...
    }
    var args = [0, 1, 2];
    f.apply(null, args);
    // ES6的寫法
    function f(x, y, z) {
      // ...
    }
    let args = [0, 1, 2];
    f(...args);
複製代碼

  二、求取數組中的最大值:

// ES5 的寫法
    Math.max.apply(null, [14, 3, 77])
    // ES6 的寫法
    Math.max(...[14, 3, 77])
    // 等同於
    Math.max(14, 3, 77);
複製代碼

  三、 簡化push函數的用法

// ES5的 寫法
    var arr1 = [0, 1, 2];
    var arr2 = [3, 4, 5];
    Array.prototype.push.apply(arr1, arr2);
    // ES6 的寫法
    let arr1 = [0, 1, 2];
    let arr2 = [3, 4, 5];
    arr1.push(...arr2);
複製代碼

  四、 複製數組

const a1 = [1, 2];
    // 寫法一
    const a2 = [...a1];
    // 寫法二
    const [...a2] = a1;
    寫法一二至關於把數組中a1的元素複製到a2中
    const a1 = [1, 2];
    const a2 = a1.concat();
    a2[0] = 2;
    a1 // [1, 2]
   上面兩個方法修改a2都不會對a1產生影響。   
複製代碼

  五、 合併數組

const arr1 = ['a', 'b'];
    const arr2 = ['c'];
    const arr3 = ['d', 'e'];
    // ES5 的合併數組
    arr1.concat(arr2, arr3);
    // [ 'a', 'b', 'c', 'd', 'e' ]
    // ES6 的合併數組
    [...arr1, ...arr2, ...arr3]
    // [ 'a', 'b', 'c', 'd', 'e' ]
    這兩種方法都是淺拷貝,使用的時候須要注意。
    
    const a1 = [{ foo: 1 }];
    const a2 = [{ bar: 2 }];
    const a3 = a1.concat(a2);
    const a4 = [...a1, ...a2];
    console.log(a3[0] === a1[0]) // true 指向相同的內存地址
    console.log(a4[0] === a1[0]) // true 指向相同的內存地址
    a3[0].foo=2;
    console.log(a1)//{foo: 2}
複製代碼

  a3和a4是用兩種不一樣方法合併而成的新數組,可是它們的成員都是對原數組成員的引用,這就是淺拷貝。若是修改了原數組的成員,會同步反映到新數組。

  六、 與解構賦值結合

const [first, ...rest] = [1, 2, 3, 4, 5];
    console.log(first); // 1
    console.log(rest)  // [2, 3, 4, 5]
    const [first, ...rest] = [];
    console.log(first) // undefined
    console.log(rest)  // []
    const [first, ...rest] = ["foo"];
    console.log(first)  // "foo"
    console.log(rest)   // []
複製代碼

  將擴展運算符用於數組賦值,只能放在參數的最後一位,不然會報錯。跟rest參數同樣。

const [...butLast, last] = [1, 2, 3, 4, 5];// 報錯
    const [first, ...middle, last] = [1, 2, 3, 4, 5];//報錯,Rest element must be last element
複製代碼

  七、 字符串

  擴展運算符還能夠將字符串轉爲真正的數組。

[...'hello']; // [ "h", "e", "l", "l", "o" ]
    […'hello'].length;//5
複製代碼

  八、 實現了 Iterator 接口的對象

  任何定義了遍歷器(Iterator)接口的對象,均可以用擴展運算符轉爲真正的數組。

let nodeList = document.querySelectorAll('div');
    let array = [...nodeList];//實現了Iterator接口
    let arrayLike = {
      '0': 'a',
      '1': 'b',
      '2': 'c',
      length: 3
    };
    let arr = [...arrayLike];// // TypeError: Cannot spread non-iterable object.
    //能夠改爲下面這樣
    let arr=Array.form(arrayLike)//把對象變成數組,把相似數組的變成數組
複製代碼

  map結構:

let map = new Map([
      [1, 'one'],
      [2, 'two'],
      [3, 'three'],
    ]);
    let arr = [...map.keys()]; // [1, 2, 3]
    Generator 函數:
    const go = function*(){
      yield 1;
      yield 2;
      yield 3;
    };
    [...go()] // [1, 2, 3]
複製代碼

 七、2 Array.from()

  Array.from方法用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象(包括 ES6 新增的數據結構 Set 和 Map)。
  任何有length屬性的對象,均可以經過Array.from方法轉爲數組,而此時擴展運算符就沒法轉換。

Array.from({ length: 3 });
    // [ undefined, undefined, undefined ]
複製代碼

  一、相似於數組的對象

let arrayLike = {
        '0': 'a',
        '1': 'b',
        '2': 'c',
        length: 3
    };
    // ES5的寫法
    var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
    // ES6的寫法
    let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
複製代碼

  二、可遍歷的對象:

Array.from('hello')
    // ['h', 'e', 'l', 'l', 'o']
    let namesSet = new Set(['a', 'b'])
    Array.from(namesSet) // ['a', 'b']
複製代碼

  三、不支持該方法的瀏覽器,能夠用下面這種方法來進行兼容:

const toArray = (() =>
      Array.from ? Array.from : obj => [].slice.call(obj)
    )();
複製代碼

  四、Array.from還能夠接受第二個參數,做用相似於數組的map方法,用來對每一個元素進行處理,將處理後的值放入返回的數組。

Array.from(arrayLike, x => x * x);
    // 等同於
    Array.from(arrayLike).map(x => x * x);
    Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]
    let spans = document.querySelectorAll('span.name');
    // map()
    let names1 = Array.prototype.map.call(spans, s => s.textContent);
    // Array.from()
    let names2 = Array.from(spans, s => s.textContent)
複製代碼

  五、Array.from()能夠將各類值轉爲真正的數組。

Array.from({ length: 2 }, () => 'jack')// ['jack', 'jack']
複製代碼

  六、將字符串轉爲數組,而後返回字符串的長度

function countSymbols(string) {
      return Array.from(string).length;
    }
複製代碼

 七、3 Array.of()

  用於將一組值,轉換爲數組。這個方法的主要目的,是彌補數組構造函數Array()的不足。Array.of基本上能夠用來替代Array()或new Array()。
Array() // []
    Array(3) // [, , ,]
    Array(3, 11, 8) // [3, 11, 8]
    Array.of(3, 11, 8) // [3,11,8]
    Array.of(3) // [3]
    Array.of(3).length // 1
複製代碼

 七、4 copywithin()

  數組實例的copyWithin方法,在當前數組內部,將指定位置的成員複製到其餘位置(會覆蓋原有成員),而後返回當前數組。

Array.prototype.copyWithin(target, start = 0, end = this.length)

target(必需):從該位置開始替換數據。若是爲負值,表示倒數。
start(可選):從該位置開始讀取數據,默認爲 0。若是爲負值,表示倒數。
end(可選):到該位置前中止讀取數據,默認等於數組長度。若是爲負值,表示倒數。

[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]
    上面代碼表示將從 3 號位直到數組結束的成員(4 和 5),複製到從 0 號位開始的位置,結果覆蓋了原來的 1 和 2。
    [1, 2, 3, 4, 5].copyWithin(0, 2)// [3, 4, 5, 4, 5]
    [1, 2, 3, 4, 5].copyWithin(0, 2,3)// [3, 2, 3, 4, 5]
複製代碼

 七、5 find()和findIndex()

  數組實例的find方法,用於找出第一個符合條件的數組成員。它的參數是一個回調函數,全部數組成員依次執行該回調函數,直到找出第一個返回值爲true的成員,而後返回該成員。若是沒有符合條件的成員,則返回undefined。
[1, 4, -5, 10].find((n) => n < 0)// -5
    [1, 5, 10, 15].find(function(value, index, arr) {
      return value > 9;
    }) // 10
複製代碼

  數組實例的findIndex(),返回第一個符合條件的數組成員的位置,若是全部成員都不符合條件,則返回-1。

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

  這兩個方法均可以接受第二個參數,用來綁定回調函數的this對象。

function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26
複製代碼

  這兩個方法均可以發現NaN,彌補了數組的indexOf方法的不足。

[NaN].indexOf(NaN)
    // -1
    [NaN].findIndex(y => Object.is(NaN, y))
    // 0 
複製代碼

  Object.is()用來比較兩個值是否嚴格相等,與嚴格相等運算符(===)同樣。

 七、6 fill()

//使用給定值,填充一個數組,並返回填充後的數組。
    ['a', 'b', 'c'].fill(7)
    // [7, 7, 7]
    new Array(3).fill(7)
    // [7, 7, 7]
複製代碼

  數組中已有的元素,會被所有抹去。 fill方法還能夠接受第二個和第三個參數,用於指定填充的起始位置和結束位置。這根splice()很像,只是splice方法沒有返回值。

['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']
複製代碼

  注意:若是填充的類型爲對象,那麼被賦值的是同一個內存地址的對象,而不是深拷貝對象。

let arr = new Array(3).fill({name: "Mike"});
    arr[0].name = "Ben";
    console.log(arr)// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
複製代碼

 七、7 entries()、keys()、values()

  它們都返回一個遍歷器對象(Iterator),能夠用for...of循環進行遍歷。
let letter = ['a', 'b', 'c'];
    let keys=letter.keys();
    let values=letter.values()
    let entries = letter.entries();
    console.log(keys,values,entries)
    // Array Iterator {} Array Iterator {} Array Iterator {}
    for (let index of keys) {
      console.log(index);
    }   //0, //1,//2
複製代碼

 七、8 includes()

  返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes方法相似。
[1, 2, 3].includes(3, 3);  // false
    [1, 2, 3].includes(3, -1); // true
複製代碼

  該方法的第二個參數表示搜索的起始位置,默認爲0。若是第二個參數爲負數,則表示倒數的位置,若是這時它大於數組長度(好比第二個參數爲-4,但數組長度爲3),則會重置爲從0開始。

  indexOf方法有兩個缺點。

一、不夠語義化,它的含義是找到參數值的第一個出現位置,因此要去比較是否不等於-1。
二、它內部使用嚴格相等運算符(===)進行判斷,這會致使對NaN的誤判。

[NaN].includes(NaN) //true
複製代碼

  能夠用以下方法來判斷當前環境是否支持該方法。

const contains = (() =>
    	Array.prototype.includes ?
    	(arr, value) => arr.includes(value) :
    	(arr, value) => arr.some(el => el === value)
    )();
    console.log(contains(['foo', 'bar'], 'baz')); // => false 
複製代碼

 七、9 flat()、flatMap()

  用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數據沒有影響。flat()默認只會「拉平」一層,若是想要「拉平」多層的嵌套數組,能夠將flat()方法的參數寫成一個整數,表示想要拉平的層數,默認爲1。

[1, 2, [3, [4, 5]]].flat()     // [1, 2, 3, [4, 5]]
    [1, 2, [3, [4, 5]]].flat(2)    // [1, 2, 3, 4, 5]
    [1, [2, [3]]].flat(Infinity)   // [1, 2, 3],這種方式無論嵌套多少層,都會被拉平。
複製代碼

  若是原數組有空位,flat()方法會跳過空位。

[1, 2, , 4, 5].flat()    // [1, 2, 4, 5]
複製代碼

  flatMap()方法對原數組的每一個成員執行一個函數(至關於執行Array.prototype.map()),而後對返回值組成的數組執行flat()方法。該方法返回一個新數組,不改變原數組。flatMap()只能展開一層數組。

// 至關於 [[[2]], [[4]], [[6]], [[8]]].flat()
    [1, 2, 3, 4].flatMap(x => [[x * 2]])
    // [[2], [4], [6], [8]]
複製代碼

 七、10 數組的空位

  注意,空位不是undefined,一個位置的值等於undefined,依然是有值的。空位是沒有任何值。

ES5:

一、forEach(), filter(), reduce(), every() 和some()都會跳過空位。
二、map()會跳過空位,但會保留這個值。
三、join()和toString()會將空位視爲undefined,而undefined和null會被處理成空字符串。

ES6:明確將空位轉爲undefined。

八、對象的擴展

 八、1 屬性的簡潔表示法

const foo = 'bar';
    const baz = {foo};
    console.log(baz)   // {foo: "bar"}
    // 等同於
    const baz = {foo: foo};
複製代碼

  ES6 容許在對象之中,直接寫變量。這時,屬性名爲變量名, 屬性值爲變量的值。

function f(x, y) {
      return {x, y};
    }
    // 等同於
    function f(x, y) {
      return {x: x, y: y};
    }
    f(1, 2)    // {x: 1, y: 2}
複製代碼

 八、2 屬性名錶達式

  JavaScript 定義對象的屬性,有兩種方法。
// 方法一
    obj.foo = true;
    // 方法二
    obj['a' + 'bc'] = 123;
複製代碼

  ES6 容許字面量定義對象時,用方法二(表達式)做爲對象的屬性名,即把表達式放在方括號內。

let lastWord = 'last word';
    const a = {
      'first word': 'hello',
      [lastWord]: 'world'
    };
    a['first word'] // "hello"
    a[lastWord] // "world"
    a['last word'] // "world"
複製代碼

  表達式還能夠用於定義方法名。

let obj = {
      ['h' + 'ello']() {
        return 'hi';
      }
    };
    obj.hello() // hi
複製代碼

  注意,屬性名錶達式若是是一個對象,默認狀況下會自動將對象轉爲字符串[object Object],這一點要特別當心。

const keyA = {a: 1};
    const keyB = {b: 2};
    const myObject = {
      [keyA]: 'valueA',
      [keyB]: 'valueB'
    };
    myObject // Object {[object Object]: "valueB"}
複製代碼

 八、3 方法的name屬性

const person = {
        sayName() {
            console.log('hello!');
        },
    };
    person.sayName.name   // "sayName"
複製代碼

  若是對象的方法使用了取值函數(getter)和存值函數(setter),則name屬性不是在該方法上面,而是該方法的屬性的描述對象的get和set屬性上面,返回值是方法名前加上get和set。

const obj = {
      get foo() {},
      set foo(x) {}
    };
    obj.foo.name
    // TypeError: Cannot read property 'name' of undefined
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
    descriptor.get.name // "get foo"
    descriptor.set.name // "set foo"
複製代碼

  若是對象的方法是一個 Symbol 值,那麼name屬性返回的是這個 Symbol 值的描述。

const key1 = Symbol('description');
    const key2 = Symbol();
    let obj = {
      [key1]() {},
      [key2]() {},
    };
    obj[key1].name // "[description]"
    obj[key2].name // ""
複製代碼

  有兩種特殊狀況:bind方法創造的函數,name屬性返回bound加上原函數的名字;Function構造函數創造的函數,name屬性返回anonymous(匿名)。

(new Function()).name // "anonymous"
    var doSomething = function() {
      // ...
    };
    doSomething.bind().name // "bound doSomething"
複製代碼

 八、4 屬性的可枚舉性和遍歷

  一、可枚舉性: 對象的每一個屬性都有一個描述對象(Descriptor),用來控制該屬性的行爲。Object.getOwnPropertyDescriptor方法能夠獲取該屬性的描述對象。
let obj = { foo: 123 };
    Object.getOwnPropertyDescriptor(obj, 'foo')
    //  {
    //    value: 123,
    //    writable: true,
    //    enumerable: true,//可枚舉性
    //    configurable: true
    //  }
複製代碼

  目前,有四個操做會忽略enumerable爲false的屬性。

一、 for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
二、 Object.keys():返回對象自身的可枚舉的屬性的屬性。
三、 JSON.stringify():只串行化對象自身的可枚舉的屬性。
四、 Object.assign(): 忽略enumerable爲false的屬性,只拷貝對象自身的可枚舉的屬性。

總結:儘可能不要用for...in循環,而用Object.keys()代替。

  二、屬性的遍歷

  一、 for…in:for...in循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
  二、 Object.keys(obj) 返回一個數組,包括對象自身的全部可枚舉屬性(不含 Symbol 屬性)的鍵名。
  三、 Object.getOwnPropertyNames(obj) 返回一個數組,包含對象自身的全部屬性(不含 Symbol 屬性,可是包括不可枚舉屬性)的鍵名。
  四、 Object.getOwnPropertySymbols(obj) 返回一個數組,包含對象自身的全部 Symbol 屬性的鍵名。
  五、 Reflect.ownKeys(obj) 返回一個數組,包含對象自身的全部鍵名,無論鍵名是 Symbol 或字符串,也無論是否可枚舉。

  以上的 5 種方法遍歷對象的鍵名,都遵照一樣的屬性遍歷的次序規則。

一、首先遍歷全部數值鍵,按照數值升序排列。
二、其次遍歷全部字符串鍵,按照加入時間升序排列。
三、最後遍歷全部 Symbol 鍵,按照加入時間升序排列。

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })    // ['2', '10', 'b', 'a', Symbol()]
複製代碼

 八、5 對象的擴展運算符

  對象的解構賦值 (在=賦值左邊) 用於從一個對象取值,至關於將目標對象自身的全部可遍歷的(enumerable)、但還沒有被讀取的屬性,分配到指定的對象上面。全部的鍵和它們的值,都會拷貝到新對象上面。屬於淺拷貝。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
    x // 1    y // 2      z // { a: 3, b: 4 }
複製代碼

  對象的解構賦值的注意事項

  一、因爲解構賦值要求等號右邊是一個對象,因此若是等號右邊是undefined或null,就會報錯,由於它們沒法轉爲對象。

let { x, y, ...z } = null; // 運行時錯誤
    let { x, y, ...z } = undefined; // 運行時錯誤
複製代碼

  二、解構賦值必須是最後一個參數,不然會報錯。

let { ...x, y, z } = someObject; // 句法錯誤
    let { x, ...y, ...z } = someObject; // 句法錯誤
複製代碼

  三、擴展運算符的解構賦值,不能複製繼承自原型對象的屬性。

let o1 = { a: 1 };
    let o2 = { b: 2 };
    o2.__proto__ = o1;
    let { ...o3 } = o2;
    o3 // { b: 2 }
    o3.a // undefined
    Object.create({ x: 1, y: 2 });建立的是原型對象
    const o = Object.create({ x: 1, y: 2 });
    o.z = 3;
    let { x, ...newObj } = o;//newObj只能獲取z的值
    let { y, z } = newObj;
    x // 1
    y // undefined
    z // 3
複製代碼

  四、 變量聲明語句之中,若是使用解構賦值,擴展運算符後面必須是一個變量名,而不能是一個解構賦值表達式。

let { x, ...{ y, z } } = o;
    // SyntaxError: ... must be followed by an identifier in declaration contexts
複製代碼

  擴展運算符: (在等號=右邊) 對象的擴展運算符(...)用於取出參數對象的全部可遍歷屬性,拷貝到當前對象之中。

  對象的擴展運算符的注意事項

  一、因爲數組是特殊的對象,因此對象的擴展運算符也能夠用於數組。

let foo = { ...['a', 'b', 'c'] };
    console.log(foo);// {0: "a", 1: "b", 2: "c"}
複製代碼

  二、 若是擴展運算符後面不是對象,則會自動將其轉爲對象。

// 等同於 {...Object(1)}
    {...1} // {}    因爲該對象沒有自身屬性,因此返回一個空對象。
    // 等同於 {...Object(true)}
    {...true} // {}
    // 等同於 {...Object(undefined)}
    {...undefined} // {}
    // 等同於 {...Object(null)}
    {...null} // {}
複製代碼

  三、 若是擴展運算符後面是字符串,它會自動轉成一個相似數組的對象,所以返回的不是空對象。

{...'hello'}    // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
複製代碼

  四、 對象的擴展運算符等同於使用Object.assign()方法。

let aClone = { ...a };
    // 等同於
    let aClone = Object.assign({}, a);
複製代碼

  五、 若是想完整克隆一個對象,還拷貝對象原型的屬性,能夠採用下面的寫法。

// 寫法1
    const clone2 = Object.assign(
      Object.create(Object.getPrototypeOf(obj)),
      obj
    );
    // 寫法2
    const clone3 = Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj)
    )
複製代碼

  六、 擴展運算符能夠用於合併兩個對象。

let ab = { ...a, ...b };
    // 等同於
    let ab = Object.assign({}, a, b);
複製代碼

  七、 若是用戶自定義的屬性,放在擴展運算符後面,則擴展運算符內部的同名屬性會被覆蓋掉。

let a={x:2,y:3;z:4} 
    let aWithOverrides = { ...a, x: 1, y: 2 };
    console.log(aWithOverrides)// {x: 1, y: 2, z: 4}
    
    let arr={a:1,b:2}
    let arr1={b:3,c:4}
    let arr2={...arr,...arr1}
    console.log(arr2)    // {a: 1, b: 3, c: 4}
複製代碼

九、對象的新增方法

 九、1 Object.is()

  用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。與ES5的不一樣之處只有兩個:一是+0不等於-0,二是NaN等於自身。
+0 === -0 //true
    NaN === NaN // false
    Object.is(+0, -0) // false
    Object.is(NaN, NaN) // true
複製代碼

  ES5 能夠經過下面的代碼,部署Object.is。

Object.defineProperty(Object, 'is', {
      value: function(x, y) {
        if (x === y) {
          // 針對+0 不等於 -0的狀況
          return x !== 0 || 1 / x === 1 / y;
        }
        // 針對NaN的狀況
        return x !== x && y !== y;
      },
      configurable: true,
      enumerable: false,
      writable: true
    });
複製代碼

 九、2 Object.assign()

  用於對象的合併,將源對象(source)的全部可枚舉屬性,複製到目標對象(target)。 若是目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。
const target = { a: 1, b: 1 };
    const source1 = { b: 2, c: 2 };
    const source2 = { c: 3 };
    const target={};
    Object.assign(target, source1, source2);    
    Console.log(target); // {a:1, b:2, c:3}
複製代碼

  注意1:因爲undefined和null沒法轉成對象,因此若是它們做爲參數,就會報錯。若是undefined和null不在首參數,就不會報錯。

Object.assign(undefined) // 報錯
    Object.assign(null) // 報錯
    let obj = {a: 1};
    Object.assign(obj, undefined) === obj // true
    Object.assign(obj, null) === obj // true
複製代碼

  注意2:其餘類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。可是,除了字符串會以數組形式,拷貝入目標對象,其餘值都不會產生效果。

const v1 = 'abc';
    const v2 = true;
    const v3 = 10;
    const obj = Object.assign({}, v1, v2, v3);
    console.log(obj); // { "0": "a", "1": "b", "2": "c" }
複製代碼

  注意3:Object.assign拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)。

  Object.assign的特色

  一、 Object.assign()是淺拷貝。
  二、 同名屬性的替換;(後者替換前者)
  三、 數組的處理。Object.assign把數組視爲屬性名爲 0、一、2 的對象,所以源數組的 0 號屬性4覆蓋了目標數組的 0 號屬性1。

Object.assign([1, 2, 3], [4, 5])
    // [4, 5, 3]
複製代碼

  四、 取值函數的處理;(求值後再複製)

const source = {
      get foo() { return 1 }
    };
    const target = {};
    Object.assign(target, source)
    // { foo: 1 }
複製代碼

  Object.assign的常見用途:

  一、 爲對象添加屬性

class Point {
      constructor(x, y) {
        Object.assign(this, {x, y});
      }
    }
複製代碼

  二、 爲對象添加方法

Object.assign(SomeClass.prototype, {
      someMethod(arg1, arg2) {
      },
      anotherMethod() {
      }
    });
    // 等同於下面的寫法
    SomeClass.prototype.someMethod = function (arg1, arg2) {
    };
    SomeClass.prototype.anotherMethod = function () {
    };
複製代碼

  三、 克隆對象(克隆自身與其繼承的值)

function clone(origin) {
      let originProto = Object.getPrototypeOf(origin);
      return Object.assign(Object.create(originProto), origin);
    }
複製代碼

  四、 合併多個對象

const merge = (target, ...sources) => Object.assign(target, ...sources);
複製代碼

  五、 爲屬性指定默認值

const DEFAULTS = {
      logLevel: 0,
      outputFormat: 'html'
    };
    function processContent(options) {
      options = Object.assign({}, DEFAULTS, options);
    }
複製代碼

 九、3 Object.getOwnPropertyDescriptors()

  返回指定對象全部自身屬性(非繼承屬性)的描述對象。主要是爲了解決Object.assign()沒法正確拷貝get屬性和set屬性的問題。

 九、4. __proto__屬性,Object.setPrototypeOf(),Object.getPrototypeOf()

   __proto__屬性:

  用來讀取或設置當前對象的prototype對象。目前,全部瀏覽器(包括 IE11)都部署了這個屬性。建議不要使用此屬性。使用下面的Object.setPrototypeOf()(寫操做)、Object.getPrototypeOf()(讀操做)、Object.create()(生成操做)代替。

  Object.setPrototypeOf(): 用來設置一個對象的prototype對象。若是第一個參數不是對象,會自動轉爲對象。可是因爲返回的仍是第一個參數,因此這個操做不會產生任何效果。 因爲undefined和null沒法轉爲對象,因此若是第一個參數是undefined或null,就會報錯。

Object.setPrototypeOf(1, {}) === 1 // true
    Object.setPrototypeOf('foo', {}) === 'foo' // true
    Object.setPrototypeOf(true, {}) === true // true
    Object.setPrototypeOf(undefined, {})
    // TypeError: Object.setPrototypeOf called on null or undefined
    Object.setPrototypeOf(null, {})
    // TypeError: Object.setPrototypeOf called on null or undefined
複製代碼

  Object.getPrototypeOf() 用於讀取一個對象的原型對象。若是參數不是對象,會被自動轉爲對象。若是參數是undefined或null,它們沒法轉爲對象,因此會報錯。

Object.getPrototypeOf(1) === Number.prototype // true
    Object.getPrototypeOf('foo') === String.prototype // true
    Object.getPrototypeOf(true) === Boolean.prototype // true
    Object.getPrototypeOf(null)
    // TypeError: Cannot convert undefined or null to object
    Object.getPrototypeOf(undefined)
    // TypeError: Cannot convert undefined or null to object
複製代碼

  Object.create() 從指定原型對象建立一個新的對象.

function Person(name,age){
        this.name=name;
        this.age=age;
    }
    Person.prototype.sayName=function(){
        console.log(this.name)
    }
    function Teacher(subject,name,age){
        this.subject=subject;
        return Person.call(this,name,age);//繼承Person實例屬性
    }
    //繼承原型屬性,指向同一引用地址
    Teacher.prototype=Object.create(Person.prototype);
    var person1=new Person();
    
    var person2=Object.create(person1);
    person2.__proto__===person1;//true;
複製代碼

 九、5 Object.keys(),Object.values(),Object.entries()

  Object.keys(): 返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵名。

  Object.values(): 返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值。屬性名爲數值的屬性,是按照數值大小,從小到大遍歷的,所以返回的順序是b、c、a。Object.values會過濾屬性名爲 Symbol 值的屬性。

const obj = { 100: 'a', 2: 'b', 7: 'c' };
    Object.values(obj)
    // ["b", "c", "a"]
    Object.values({ [Symbol()]: 123, foo: 'abc' });
    // ['abc']   會過濾屬性名爲 Symbol 值的屬性。
          若是參數不是對象,Object.values會先將其轉爲對象。若是Object.values方法的參數是一個字符串,會返回各個字符組成的一個數組。
    Object.values('foo')   // ['f', 'o', 'o']
    Object.values(42) // []
    Object.values(true) // []
複製代碼

  Object.entries(): 返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值對數組。除了返回值不同,該方法的行爲與Object.values基本一致。

const obj = { foo: 'bar', baz: 42 };
    Object.entries(obj)
    // [ ["foo", "bar"], ["baz", 42] ]
複製代碼

  主要用途: 一、 遍歷對象的屬性。 二、 將對象轉爲真正的Map結構。

const obj = { foo: 'bar', baz: 42 };
    const map = new Map(Object.entries(obj));
    map // Map { foo: "bar", baz: 42 }
複製代碼

  Object.fromEntries(): 是Object.entries()的逆操做,用於將一個鍵值對數組轉爲對象。

 九、6 Obj.hasOwnProperty(obj.prop)

  返回一個布爾值,指示對象自身屬性中是否具備指定的屬性。和 in 運算符不一樣,該方法會忽略掉那些從原型鏈上繼承到的屬性。
o = new Object();
    o.prop = 'exists';
    o.hasOwnProperty('prop');             // 返回 true
    o.hasOwnProperty('toString');         // 返回 false
    o.hasOwnProperty('hasOwnProperty');   // 返回 false
複製代碼

十、Symbol

  新的原始數據類型Symbol,表示獨一無二的值,是javascript的第七種數據類型。前六種是:undefined、null、布爾值(Boolean)、字符串(String)、數值(Number)、對象(Object)。

  注意事項:

  一、Symbol函數前不能使用new命令,不然會報錯。因爲 Symbol 值不是對象,因此不能添加屬性。基本上,它是一種相似於字符串的數據類型。 Symbol函數能夠接受一個字符串做爲參數,表示對 Symbol 實例的描述。

let s1 = Symbol('foo');
    let s2 = Symbol('bar');
    console.log(s1) // Symbol(foo)
    console.log(s1.toString()) //  Symbol(foo)
複製代碼

  二、若是 Symbol 的參數是一個對象,就會調用該對象的toString方法,將其轉爲字符串,而後才生成一個 Symbol 值。

const obj = {
      toString() {
        return 'abc';
      }
    };
    const sym = Symbol(obj);
    console.log(sym)   // Symbol(abc)
複製代碼

  三、Symbol函數的參數只是表示對當前 Symbol 值的描述,所以相同參數的Symbol函數的返回值是不相等的。

// 沒有參數的狀況
    let s1 = Symbol();
    let s2 = Symbol();
    s1 === s2 // false
    // 有參數的狀況
    let s1 = Symbol('foo');
    let s2 = Symbol('foo');
    s1 === s2 // false
複製代碼

  四、Symbol 值不能與其餘類型的值進行運算,會報錯。

let sym = Symbol('My symbol');
    "your symbol is " + sym
    // TypeError: can't convert symbol to string
    `your symbol is ${sym}`
    // TypeError: can't convert symbol to string
複製代碼

  五、Symbol 值能夠顯式轉爲字符串。也能夠轉爲布爾值,可是不能轉爲數值。

let sym = Symbol('My symbol');
    String(sym) // 'Symbol(My symbol)'
    sym.toString() // 'Symbol(My symbol)'
    let sym = Symbol();
    Boolean(sym) // true
    !sym  // false
    if (sym) {
    }
    Number(sym) // TypeError
    Console.log(sym + 2) // TypeError
複製代碼

 10.一、 做爲屬性名

  Symbol 值做爲對象屬性名時,不能用點運算符。通常使用方括號。
const mySymbol = Symbol();
    const a = {};
    a.mySymbol = 'Hello!';//此處的mySymbol只能當作是字符串,而不能當作是一個Symbol值
    console.log(a[mySymbol])  // undefined
    consoe.log(a['mySymbol']) // "Hello!"
複製代碼

  在對象的內部,使用 Symbol 值定義屬性時,Symbol 值必須放在方括號之中。

let s = Symbol();
    let obj = {
      [s]: function (arg) { ... }
    };
    obj[s](123);
複製代碼

  Symbol 類型還能夠用於定義一組常量,保證這組常量的值都是不相等的。 Symbol 值做爲屬性名時,該屬性仍是公開屬性,不是私有屬性。 魔術字符串:在代碼之中屢次出現、與代碼造成強耦合的某一個具體的字符串或者數值。利用Symbol來消除。

const shapeType = {
      triangle: Symbol('Triangle')
    };
    
    function getArea(shape, options) {
      let area = 0;
      switch (shape) {
        case shapeType.triangle:
          area = 5 * options.width * options.height;
          break;
      }
      return area;
    }
    getArea(shapeType.triangle, { width: 100, height: 100 });
複製代碼

 十、2 屬性名的遍歷

  Symbol 做爲屬性名,該屬性不會出如今for...in、for...of循環中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。可是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,能夠獲取指定對象的全部 Symbol 屬性名。 能夠利用這個特性,爲對象定義一些非私有的、但又但願只用於內部的方法。

 十、3 Symbol.for()、Symbol.keyFor()

  Symbol.for(「foo」): 接受一個字符串做爲參數,而後搜索有沒有以該參數做爲名稱的 Symbol 值。若是有,就返回這個 Symbol 值,不然就新建並返回一個以該字符串爲名稱的 Symbol 值。

let s1 = Symbol.for('foo');
    let s2 = Symbol.for('foo');
    console.log(s1 === s2)   // true
複製代碼

  Symbol.keyFor: 返回一個已登記的 Symbol 類型值的key。返回一個使用Symbol.for()方法定義的key。

let s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"
    let s2 = Symbol("foo");
    Symbol.keyFor(s2) // undefined
複製代碼

  注意:Symbol.for爲 Symbol 值登記的名字,是全局環境的,能夠在不一樣的 iframe 或 service worker 中取到同一個值。

 十、4 內置的Symbol值

  Symbol.hasInstance: 對象的Symbol.hasInstance屬性,指向一個內部方法。當其餘對象使用instanceof運算符,判斷是否爲該對象的實例時,會調用這個方法。好比,foo instanceof Foo在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)。

class MyClass {
      [Symbol.hasInstance](foo) {
        return foo instanceof Array;
      }
    }
    Console.log([1, 2, 3] instanceof new MyClass()) // trueSymbol.isConcatSpreadable
複製代碼

  Symbol.isConcatSpreadable: 等於一個布爾值,表示該對象用於Array.prototype.concat()時,是否能夠展開。

let arr1 = ['c', 'd'];
    ['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
    arr1[Symbol.isConcatSpreadable] // undefined
    let arr2 = ['c', 'd'];
    arr2[Symbol.isConcatSpreadable] = false;
    ['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
複製代碼

  相似數組的對象正好相反,默認不展開。它的Symbol.isConcatSpreadable屬性設爲true,才能夠展開。

let obj = {length: 2, 0: 'c', 1: 'd'};
    ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
    obj[Symbol.isConcatSpreadable] = true;
    ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
複製代碼

  Symbol.species: 指向一個構造函數。建立衍生對象時,會使用該屬性。下面例子中b、c是a的衍生對象。

class MyArray extends Array {
    	static get [Symbol.species]() { return Array; }
    }
    const a = new MyArray(1, 2, 3);
    const b = a.map(x => x);
    const c = a.filter(x => x > 1);
    b instanceof MyArray // false
    b instanceof Array //true
    c instanceof MyArray // false
複製代碼

  主要的用途: 有些類庫是在基類的基礎上修改的,那麼子類使用繼承的方法時,做者可能但願返回基類的實例,而不是子類的實例。
  還有其餘屬性就不一一例舉了:Symbol.match、Symbol.replace、Symbol.search、Symbol.split、Symbol.iterator、Symbol.toPrimitive、Symbol.toStringTag(toString())、Symbol.unscopables(with)

十一、Set

 11.一、 Set

  一種數據結構,相似於數組,可是成員的值都是惟一的,沒有重複的值。

const s = new Set();
    [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
    for (let i of s) {
      console.log(i);
    }
    // 2 3 5 4
複製代碼

  Set函數能夠接受一個數組(或者具備 iterable 接口的其餘數據結構)做爲參數,用來初始化。

// 例一
    const set = new Set([1, 2, 3, 4, 4]);
    console.log([...set])
    // [1, 2, 3, 4]
    let arr = [3, 5, 2, 2, 5, 5];
    let unique = [...new Set(arr)];
    // [3, 5, 2]
    // 例二
    const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
    items.size // 5
    // 例三
    const set = new Set(document.querySelectorAll('div'));
    set.size // 56
複製代碼

  將set轉爲數組的方式

[...new Set('ababbc')]
複製代碼

  能夠用於去重

[...new Set('ababbc')].join('')  //abc 
複製代碼

  向 Set 加入值的時候,不會發生類型轉換,因此5和"5"是兩個不一樣的值。在 Set 內部,兩個NaN是相等。

 11.二、 Set實例的屬性和方法

  實例屬性:

一、Set.prototype.constructor:構造函數,默認就是Set函數。
二、Set.prototype.size:返回Set實例的成員總數。

  操做方法:

一、add(value):添加某個值,返回 Set 結構自己。
二、delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
三、has(value):返回一個布爾值,表示該值是否爲Set的成員。
四、clear():清除全部成員,沒有返回值。

  Array.from方法能夠將 Set 結構轉爲數組。

const items = new Set([1, 2, 3, 4, 5]);
    //方法1
    const array = Array.from(items);
    //方法2
    const arr=[…items]
複製代碼

  遍歷方法:

一、keys():返回鍵名的遍歷器。
二、values():返回鍵值的遍歷器。
三、entries():返回鍵值對的遍歷器。
四、forEach():使用回調函數遍歷每一個成員。

  因爲 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),因此keys方法和values方法的行爲徹底一致。   Set 結構的實例默承認遍歷,它的默認遍歷器生成函數就是它的values方法。

Set.prototype[Symbol.iterator] === Set.prototype.values
    // true
複製代碼

  這意味着,能夠省略values方法,直接用for...of循環遍歷 Set。

let set = new Set(['red', 'green', 'blue']);
    for (let x of set) {
      console.log(x);
    }
    // red  // green  // blue
複製代碼

  數組的map和filter方法也能夠間接用於 Set 了。

let set = new Set([1, 2, 3]);
    set = new Set([...set].map(x => x * 2));
    // 返回Set結構:{2, 4, 6}
    let set = new Set([1, 2, 3, 4, 5]);
    set = new Set([...set].filter(x => (x % 2) == 0));
    // 返回Set結構:{2, 4}
複製代碼

 11.三、 WeakSet

  WeakSet 結構與 Set 相似,可是,它與 Set 有兩個區別。

首先:WeakSet 的成員只能是對象,而不能是其餘類型的值。

const ws = new WeakSet();
    ws.add(1)
    // TypeError: Invalid value used in weak set
    ws.add(Symbol())
    // TypeError: invalid value used in weak set
複製代碼

其次:WeakSet 中的對象都是弱引用,垃圾回收機制不考慮 WeakSet 對該對象的引用,也就是說,若是其餘對象都再也不引用該對象,那麼垃圾回收機制會自動回收該對象所佔用的內存,不考慮該對象還存在於 WeakSet 之中。 所以,WeakSet 適合臨時存放一組對象。因爲上面這個特色,WeakSet 的成員是不適合引用的,由於它會隨時消失。WeakSet 不可遍歷。
做爲構造函數,WeakSet 能夠接受一個數組或相似數組的對象做爲參數注意,是a數組的成員成爲 WeakSet 的成員,而不是a數組自己。這意味着,數組的成員只能是對象。

const a = [[1, 2], [3, 4]];
    const ws = new WeakSet(a);
    // WeakSet {[1, 2], [3, 4]}
    const b = [3, 4];
    const ws = new WeakSet(b);
    // Uncaught TypeError: Invalid value used in weak set(…)
複製代碼

  有如下幾個實例方法:add()、delete()、has()。

十二、Map

 12.一、Map

  JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),可是傳統上只能用字符串看成鍵。

  Map: 一種數據結構,相似於對象,也是鍵值對的集合,可是「鍵」的範圍不限於字符串,各類類型的值(包括對象)均可以看成鍵。

  做爲構造函數,Map 也能夠接受一個數組做爲參數。該數組的成員是一個表示鍵值對的數組。任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構均可以看成Map構造函數的參數。這就是說,Set和Map均可以用來生成新的 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"
複製代碼

  Map構造函數接受數組做爲參數,實際上執行的是下面的算法。

const items = [
      ['name', '張三'],
      ['title', 'Author']
    ];
    const map = new Map();
    items.forEach(
      ([key, value]) => map.set(key, value)
    );
複製代碼

  若是對同一個鍵屢次賦值,後面的值將覆蓋前面的值。只有對同一個對象的引用,Map 結構纔將其視爲同一個鍵

const map = new Map();
    map.set(1, 'aaa').set(1, 'bbb');
    map.get(1) // "bbb"
    
    const map = new Map();
    map.set(['a'], 555);
    map.get(['a']) // undefined //這兩個[「a」]的內存地址不同
複製代碼

  注意: 雖然NaN不嚴格相等於自身,但 Map 將其視爲同一個鍵。

 12.二、實例的屬性和操做方法:

  size、set()、get()、has()、delete()、clear()

 12.三、遍歷方法

一、 keys():返回鍵名的遍歷器。
二、values():返回鍵值的遍歷器。
三、entries():返回全部成員的遍歷器。
四、forEach():遍歷 Map 的全部成員。

  Map 結構轉爲數組結構,比較快速的方法是使用擴展運算符(...)。

const map = new Map([
      [1, 'one'],
      [2, 'two'],
      [3, 'three'],
    ]);
    [...map.keys()]   // [1, 2, 3]
    [...map.values()]   // ['one', 'two', 'three']
    [...map.entries()]   // [[1,'one'], [2, 'two'], [3, 'three']]
    [...map]   // [[1,'one'], [2, 'two'], [3, 'three']]
複製代碼

  結合數組的map方法、filter方法,能夠實現 Map 的遍歷和過濾(Map 自己沒有map和filter方法)。

const map0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c')
    const map1 = new Map(
      [...map0].filter(([k, v]) => k < 3)
    );
    // 產生 Map 結構 {1 => 'a', 2 => 'b'}
    const map2 = new Map(
      [...map0].map(([k, v]) => [k * 2, '_' + v])
        );
    // 產生 Map 結構 {2 => '_a', 4 => '_b', 6 => '_c'}
複製代碼

 12.四、與其餘數據結構的互相轉換:

  一、 Map與數組的轉換:
const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
    [...myMap] //Map轉數組
    // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
複製代碼

  數組轉Map

new Map([
      [true, 7],
      [{foo: 3}, ['abc']]
    ])
    // Map {
    //   true => 7,
    //   Object {foo: 3} => ['abc']
    // }
複製代碼

  二、 Map與對象的轉換

  Map轉對象

function strMapToObj(strMap) {
      let obj = Object.create(null);
      for (let [k,v] of strMap) {
        obj[k] = v;
      }
      return obj;
    }
    const myMap = new Map() .set('yes', true).set('no', false);
    strMapToObj(myMap)   // { yes: true, no: false }
複製代碼

  對象轉Map

function objToStrMap(obj) {
      let strMap = new Map();
      for (let k of Object.keys(obj)) {
        strMap.set(k, obj[k]);
      }
      return strMap;
    }
    objToStrMap({yes: true, no: false})   // Map {"yes" => true, "no" => false}
複製代碼

  三、 Map與Json的轉換 Map 的鍵名都是字符串,這時能夠選擇轉爲對象 JSON。

function strMapToJson(strMap) {
      return JSON.stringify(strMapToObj(strMap));
    }
    let myMap = new Map().set('yes', true).set('no', false);
    strMapToJson(myMap)
    // '{"yes":true,"no":false}'
複製代碼

  Map 的鍵名有非字符串,這時能夠選擇轉爲數組 JSON。

function mapToArrayJson(map) {
      return JSON.stringify([...map]);
    }
    let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
    mapToArrayJson(myMap)
    // '[[true,7],[{"foo":3},["abc"]]]'
複製代碼

  Json轉Map,全部鍵名都是字符串。

function jsonToStrMap(jsonStr) {
      return objToStrMap(JSON.parse(jsonStr));
    }
    jsonToStrMap('{"yes": true, "no": false}')
    // Map {'yes' => true, 'no' => false}
複製代碼

  Json轉Map,整個 JSON 就是一個數組,且每一個數組成員自己,又是一個有兩個成員的數組。

function jsonToMap(jsonStr) {
      return new Map(JSON.parse(jsonStr));
    }
    jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
    // Map {true => 7, Object {foo: 3} => ['abc']}
複製代碼

 12.五、WeakMap:

  WeakMap與Map的區別有兩點。

首先,WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名。
其次,WeakMap的鍵名所指向的對象,不計入垃圾回收機制。 WeakMap的專用場合就是,它的鍵所對應的對象,可能會在未來消失。WeakMap結構有助於防止內存泄漏。

  注意 ,WeakMap 弱引用的只是鍵名,而不是鍵值。鍵值依然是正常引用。

const wm = new WeakMap();
    let key = {};
    let obj = {foo: 1};
    wm.set(key, obj);
    obj = null;//解除引用,至關於切斷聯繫,可是{foo:1}所佔內存空間依然存在。
    console.log(wm.get(key))
    // Object {foo: 1}
複製代碼

  WeakMap 與 Map 在 API 上的區別主要是兩個,一是沒有遍歷操做(即沒有keys()、values()和entries()方法),也沒有size屬性。WeakMap只有四個方法可用:get()、set()、has()、delete()。

  weakMap的用途:

  一、 就是 DOM 節點做爲鍵名

let myElement = document.getElementById('logo');
    let myWeakmap = new WeakMap();
    myWeakmap.set(myElement, {timesClicked: 0});
    myElement.addEventListener('click', function() {
      let logoData = myWeakmap.get(myElement);
      logoData.timesClicked++;
    }, false);
複製代碼

  二、 部署私有屬性

const _counter = new WeakMap();
    const _action = new WeakMap();
    class Countdown {
      constructor(counter, action) {
        _counter.set(this, counter);
        _action.set(this, action);
      }
      dec() {
        let counter = _counter.get(this);
        if (counter < 1) {
            return;
        }
        _counter.set(this, --counter);
        if (counter === 0) {
          _action.get(this)();
        }
      }
    }
    const c = new Countdown(2, () => console.log('DONE'));
    c.dec()
    c.dec()
    // DONE
複製代碼

 1三、Proxy

  Proxy 用於修改某些操做的默認行爲。 Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。

var proxy = new Proxy(target, handler);
複製代碼

  target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。
  注意:要使得Proxy起做用,必須針對Proxy實例進行操做,而不是針對目標對象進行操做。若是handler沒有設置任何攔截,那就等同於直接通向原對象。

var target = {};
    var handler = {};
    var proxy = new Proxy(target, handler);
    proxy.a = 'b';
    target.a // "b"
複製代碼

  Proxy 實例也能夠做爲其餘對象的原型對象。

var proxy = new Proxy({}, {
      get: function(target, property) {
        return 35;
      }
    });
    let obj = Object.create(proxy);
    obj.time // 35
複製代碼

 13.一、 Proxy 支持的攔截操做一覽,一共 13 種。

  一、 get(target, propKey,receiver):攔截對象屬性的讀取,好比proxy.foo和proxy['foo']。

  二、 set(target, propKey, value, receiver):攔截對象屬性的設置,好比proxy.foo = v或proxy['foo'] = v,返回一個布爾值。

  三、has(target, propKey):攔截propKey in proxy的操做,返回一個布爾值。可是has攔截對for...in循環不生效。

  四、deleteProperty(target, propKey):攔截delete proxy[propKey]的操做,返回一個布爾值。

  五、ownKeys(target):攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。

  六、getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。

  七、defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。

  八、preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值。

  九、getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象。

  十、isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值。不然返回值會被自動轉爲布爾值。

  十一、setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。

  十二、apply(target, object, args):攔截 Proxy 實例做爲函數調用的操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

  1三、construct(target, args):攔截 Proxy 實例做爲構造函數調用的操做,好比new proxy(...args)。回的必須是一個對象,不然會報錯。

 13.二、 Proxy.revocable()

  方法返回一個可取消的 Proxy 實例。
let target = {};
    let handler = {};
    let {proxy, revoke} = Proxy.revocable(target, handler);
    proxy.foo = 123;
    proxy.foo // 123
    revoke();
    proxy.foo // TypeError: Revoked
複製代碼

 13.三、 this問題

  即不作任何攔截的狀況下,也沒法保證與目標對象的行爲一致。主要緣由就是在 Proxy 代理的狀況下,目標對象內部的this關鍵字會指向 Proxy 代理。
const _name = new WeakMap();
    class Person {
      constructor(name) {
        _name.set(this, name);
      }
      get name() {
        return _name.get(this);
      }
    }
    const jane = new Person('Jane');
    console.log(jane.name) // 'Jane'
    const proxy = new Proxy(jane, {});
    console.log(proxy.name) // undefined 
複製代碼

  若是handler沒有設置任何攔截,那就等同於直接通向原對象。由於經過調用構造函數時this指的是Person,而proxy.name獲取name時的this指的是proxy,因此此時取不到值。

  實例:經過攔截使得this綁定原始對象。

const target = new Date('2015-01-01');
    const handler = {
      get(target, prop) {
        if (prop === 'getDate') {
          return target.getDate.bind(target);
        }
        return Reflect.get(target, prop);
      }
    };
    const proxy = new Proxy(target, handler);
    proxy.getDate() // 1
複製代碼

1四、Reflect

  與Proxy對象同樣,也是 ES6 爲了操做對象而提供的新 API。Reflect對象的設計目的有這樣幾個。

  1. 將Object對象的一些明顯屬於語言內部的方法(好比Object.defineProperty),放到Reflect對象上。

  2. 修改某些Object方法的返回結果,讓其變得更合理。

  3. 讓Object操做都變成函數行爲。

// 老寫法
    'assign' in Object // true
    // 新寫法
    Reflect.has(Object, 'assign') // true
複製代碼

  4. Reflect對象的方法與Proxy對象的方法一一對應,只要是Proxy對象的方法,就能在Reflect對象上找到對應的方法。

  5. Reflect對象的靜態方法有13個,跟Proxy的靜態方法時一一對應的。

  1)Reflect.get(target, name, receiver):查找並返回target對象的name屬性,若是沒有該屬性,則返回undefined。

var myObject = {
      foo: 1,
      bar: 2,
      get baz() {
        return this.foo + this.bar;
      },
    }
    console.log(Reflect.get(myObject, 'foo') )// 1
    console.log(Reflect.get(myObject, 'baz') )// 3
    var myReceiverObject = {
      foo: 4,
      bar: 4,
    };
    console.log((Reflect.get(myObject, 'baz', myReceiverObject)) // 8
複製代碼

  若是name屬性部署了讀取函數(getter),則讀取函數的this綁定receiver。

  2)Reflect.set(target, name, value, receiver): Reflect.set方法設置target對象的name屬性等於value。若是name屬性設置了賦值函數,則賦值函數的this綁定receiver。若是第一個參數不是對象,Reflect.set會報錯。

var myObject = {
      foo: 1,
      set bar(value) {
        return this.foo = value;
      },
    }
    
    myObject.foo // 1
    
    Reflect.set(myObject, 'foo', 2);
    myObject.foo // 2
    
    Reflect.set(myObject, 'bar', 3)
    myObject.foo // 3
複製代碼

  3)Reflect.has(obj,name):對應name in obj裏面的in運算符。若是第一個參數不是對象,Reflect.has和in運算符都會報錯。

var myObject = {
      foo: 1,
    };
    // 舊寫法
    'foo' in myObject // true
    // 新寫法
    Reflect.has(myObject, 'foo') // true
複製代碼

….

  實例:利用proxy實現觀察者模式

const queuedObservers = new Set();//觀察者隊列
    const observe = fn => queuedObservers.add(fn);//添加觀察者
    const observable = obj => new Proxy(obj, {set});//添加代理
    function set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      queuedObservers.forEach(observer => observer());
      return result;
    }
    //觀察目標
    const person = observable({
      name: '張三',
      age: 20
    });
    //觀察者
    function print() {
      console.log(`${person.name}, ${person.age}`)
    }
    observe(print);
    person.name = '李四';
複製代碼

 1五、Promise

  是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果。

  Promise對象有如下兩個特色。

  1) 對象的狀態不受外界影響。 Promise對象表明一個異步操做,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。

  2) 一旦狀態改變,就不會再變, 任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能:從pending變爲fulfilled和從pending變爲rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved(已定型)。若是改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。

  優勢: 將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操做更加容易。

  不足:

  一、沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。
  二、若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
  三、當處於pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。

 15.一、 基本用法

const promise = new Promise(function(resolve, reject) {
      // ... some code
      if (/* 異步操做成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });
複製代碼

  resolve函數的做用是,將Promise對象的狀態從「未完成」變爲「成功」(即從 pending 變爲 resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;

  reject函數的做用是,將Promise對象的狀態從「未完成」變爲「失敗」(即從 pending 變爲 rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。

  Promise實例生成之後,能夠用then方法分別指定resolved狀態和rejected狀態的回調函數。

promise.then(function(value) {
      // success
    }, function(error) {
      // failure
    });
複製代碼

  實例:

function timeout(ms) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, ms, 'done');
      });
    }
    timeout(100).then((value) => {
      console.log(value);   //done
    });
複製代碼

  Promise新建後就會當即執行:then方法指定的回調函數,將在當前腳本全部同步任務執行完纔會執行。

let promise = new Promise(function(resolve, reject) {
      console.log('Promise');
      resolve();
    });
    promise.then(function() {
      console.log('resolved.');
    });
    console.log('Hi!');
    順序:// Promise   // Hi!  // resolved
複製代碼

 15.二、 Promise.prototype.then()

  它的做用是爲 Promise 實例添加狀態改變時的回調函數。 前面說過,then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。

  then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。所以能夠採用鏈式寫法,即then方法後面再調用另外一個then方法。

getJSON("/posts.json").then(function(json) {
      return json.post;
    }).then(function(post) {//參數post是上一個then返回的數據-json.post
      // ...
    });
複製代碼

  若是then()返回的是一個Promise,那麼接下來的代碼能夠這麼寫:

getJSON("/post/1.json").then(
      post => getJSON(post.commentURL)
    ).then(
      comments => console.log("resolved: ", comments),//成功
      err => console.log("rejected: ", err)//失敗
    );
複製代碼

 15.三、 Promise.prototype.catch

  方法是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。用於捕獲Promise對象和then()方法指定的回調函數中的錯誤。

  Promise 對象的錯誤具備「冒泡」性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤老是會被下一個catch語句捕獲。 若是 Promise 狀態已經變成resolved,再拋出錯誤是無效的。

const promise = new Promise(function(resolve, reject) {
      resolve('ok');
      throw new Error('test');
    });
    promise.then(function(value) { console.log(value) })
      .catch(function(error) { console.log(error) });
    // ok
複製代碼

  Promise 在resolve語句後面,再拋出錯誤,不會被捕獲,等於沒有拋出。由於 Promise 的狀態一旦改變,就永久保持該狀態,不會再變了。

 15.四、 Promise.prototype.finally()

  用於指定無論 Promise 對象最後狀態如何,都會執行的操做。無論前面的 Promise 是fulfilled仍是rejected,都會執行回調函數callback。

Promise.resolve(2).then(() => {}, () => {})
    // resolve 的值是 undefined
    Promise.resolve(2).finally(() => {})
    // resolve 的值是 2
    Promise.reject(3).then(() => {}, () => {})
    // reject 的值是 undefined
    Promise.reject(3).finally(() => {})
    // reject 的值是 3
複製代碼

 15.五、 Promise.all()

const p = Promise.all([p1, p2, p3]);
複製代碼

  參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。

  (1)只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。

  (2)只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

 15.六、 Promise.race()

const p = Promise.race([p1, p2, p3]);
複製代碼

  只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

 15.七、 Promise.resolve()

  將現有對象轉爲 Promise 對象。

Promise.resolve('foo')
    // 等價於
    new Promise(resolve => resolve('foo'))
複製代碼

  Promise.resolve方法的參數分紅四種狀況。

  (1)參數是一個 Promise 實例。那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。

  (2)參數是一個thenable對象。thenable對象指的是具備then方法的對象,Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。

let thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    let p1 = Promise.resolve(thenable);
    p1.then(function(value) {
      console.log(value);  // 42
    });
複製代碼

  (3)參數不是具備then方法的對象,或根本就不是對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。

const p = Promise.resolve('Hello');
    p.then(function (s){
      console.log(s)
    });
    // Hello
複製代碼

  (4)不帶有任何參數。直接返回一個resolved狀態的 Promise 對象。注意: 當即resolve的 Promise 對象,是在本輪「事件循環」(event loop)的結束時,而不是在下一輪「事件循環」的開始時。setTimeout(fn, 0)在下一輪「事件循環」開始時執行,

setTimeout(function () {
      console.log('three');
    }, 0);
    Promise.resolve().then(function () {
      console.log('two');
    });
    console.log('one');
    // one
    // two
    // three
複製代碼

  setTimeout(fn, 0)在下一輪「事件循環」開始時執行,Promise.resolve()在本輪「事件循環」結束時執行,console.log('one')則是當即執行,所以最早輸出。

 15.八、 Promise.reject()

  返回一個新的 Promise 實例,該實例的狀態爲rejected。
const p = Promise.reject('出錯了');
    // 等同於
    const p = new Promise((resolve, reject) => reject('出錯了'))
    p.then(null, function (s) {
      console.log(s)
    });
    // 出錯了
複製代碼

  注意: Promise.reject()方法的參數,會原封不動地做爲reject的理由,變成後續方法的參數。這一點與Promise.resolve方法不一致。

const thenable = {
      then(resolve, reject) {
        reject('出錯了');
      }
    };
    Promise.reject(thenable)
    .catch(e => {
      console.log(e === thenable) // true
    })
複製代碼

 15.九、 Promise.try()

  Promise.try爲全部操做提供了統一的處理機制。事實上,Promise.try就是模擬try代碼塊,就像promise.catch模擬的是catch代碼塊。
Promise.try(() => database.users.get({id: userId})).then(...).catch(...)
複製代碼

 15.10 實例:加載圖片

const preloadImage = function (path) {
      return new Promise(function (resolve, reject) {
        const image = new Image();
        image.onload  = resolve(src,path);
        image.onerror = reject(src);
        image.src = path;
      });
    };
    preloadImage(「http://www.tj.com/123.jpg」).then(function(img,path){
    //此時的參數path爲undefined,由於此時的path是preloadImage(path)中的形參,是局部變量,在調用結束後他的內存就被釋放了。
    },function(img){})
複製代碼

 1六、Iterator和for…of循環

  Iterator是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署 Iterator接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。 Iterator 的做用有三個:
  1. 爲各類數據結構,提供一個統一的、簡便的訪問接口;
  2. 使得數據結構的成員可以按某種次序排列;
  3. ES6 創造了一種新的遍歷命令for...of循環,Iterator 接口主要供for...of消費。

  ES6 規定,默認的 Iterator接口部署在數據結構的Symbol.iterator屬性上,或者說,一個數據結構只要具備Symbol.iterator屬性,就能夠認爲是「可遍歷」的。

const obj = {
      [Symbol.iterator] : function () {
        return {
          next: function () {
            return {
              value: 1,
              done: true
            };
          }
        };
      }
    };
複製代碼

  原生具有 Iterator 接口的數據結構以下。

  一、Array
  二、Map
  三、Set
  四、String
  五、TypedArray
  六、函數的 arguments 對象
  七、NodeList 對象

  下面是兩個經過調用Symbol.iterator方法來Iterator接口的例子。

//數組的遍歷 部署 Iterator 接口 let iter = arr[Symbol.iterator]();
    let arr = ['a', 'b', 'c'];
    let iter = arr[Symbol.iterator]();
    console.log(iter.next()) // { value: 'a', done: false }
    console.log (iter.next()) // { value: 'b', done: false }
    console.log (iter.next()) // { value: 'c', done: false }
    console.log (iter.next()) // { value: undefined, done: true }
    //字符串的遍歷 部署 Iterator 接口 someString[Symbol.iterator]();
    var someString = "hi";
    typeof someString[Symbol.iterator]
    // "function"
    var iterator = someString[Symbol.iterator]();
    iterator.next()  // { value: "h", done: false }
    iterator.next()  // { value: "i", done: false }
    iterator.next()
複製代碼

  對於相似數組的對象(存在數值鍵名和length屬性),部署 Iterator 接口,有一個簡便方法,就是Symbol.iterator方法直接引用數組的 Iterator 接口。

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    // 或者
    NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
    [...document.querySelectorAll('div')] // 能夠執行了
複製代碼

 16.一、 調用iterator接口的場合

(1) 解構賦值

var set=[1,2,3]
    let [first, ...rest] = set;
複製代碼

(2) 擴展運算符

var str = 'hello';
    console.log([...str]) //  ['h','e','l','l','o']
複製代碼

(3) yield*

let generator = function* () {
      yield 1;
      yield* [2,3,4];
      yield 5;
    };
    
    var iterator = generator();
    
    iterator.next() // { value: 1, done: false }
    iterator.next() // { value: 2, done: false }
    iterator.next() // { value: 3, done: false }
    iterator.next() // { value: 4, done: false }
    iterator.next() // { value: 5, done: false }
    iterator.next() // { value: undefined, done: true }
複製代碼

(4) 其餘一些場合(for…of、Array.form())

  JavaScript 原有的for...in循環,只能得到對象的鍵名,不能直接獲取鍵值。ES6 提供for...of循環,容許遍歷得到鍵值。數組的遍歷器接口只返回具備數字索引的屬性。

var arr = ['a', 'b', 'c', 'd'];
    arr.foo = 'hello';
    //獲取的是鍵名
    for (let a in arr) {
      console.log(a); // 0 1 2 3
    }
    //獲取的是鍵值
    for (let a of arr) {
      console.log(a); // a b c d
    }
複製代碼

  Set 結構遍歷時,返回的是一個值,而 Map 結構遍歷時,返回的是一個數組,該數組的兩個成員分別爲當前 Map 成員的鍵名和鍵值。

let map = new Map().set('a', 1).set('b', 2);
    for (let pair of map) {
      console.log(pair); // ['a', 1]   // ['b', 2]
    }
    for (let [key, value] of map) {
      console.log(key + ' : ' + value); // a : 1   // b : 2
    }
複製代碼

  並非全部相似數組的對象都具備 Iterator 接口,一個簡便的解決方法,就是使用Array.from方法將其轉爲數組。

let arrayLike = { length: 2, 0: 'a', 1: 'b' };
    // 報錯
    for (let x of arrayLike) {
      console.log(x);
    }
    // 正確
    for (let x of Array.from(arrayLike)) {
      console.log(x);
    }
複製代碼

  for...in循環有幾個缺點。

  一、數組的鍵名是數字,可是for...in循環是以字符串做爲鍵名「0」、「1」、「2」等等。
  二、for...in循環不只遍歷數字鍵名,還會遍歷手動添加的其餘鍵,甚至包括原型鏈上的鍵。
  三、某些狀況下,for...in循環會以任意順序遍歷鍵名。

  總之,for...in循環主要是爲遍歷對象而設計的,不適用於遍歷數組。

  for...of循環相比上面幾種作法,有一些顯著的優勢:

  一、有着同for...in同樣的簡潔語法,可是沒有for...in那些缺點。   二、不一樣於forEach方法,它能夠與break、continue和return配合使用。   三、提供了遍歷全部數據結構的統一操做接口。

相關文章
相關標籤/搜索