淺談ECMAScript新特性

1.新的標(biāo)準(zhǔn)規(guī)范

ECMAScript2015 是 js 的一種的新的標(biāo)準(zhǔn)規(guī)范,就是對 js 的寫法上提出了新的語法要求和寫法格式。

2.ECMAScript 和 javaScript 關(guān)系

ECMAScript 和 JavaScript 的關(guān)系是,前者是后者的規(guī)格,后者是前者的一種實(shí)現(xiàn)。javascript 是 netscape 創(chuàng) 造的并交給了國際標(biāo)準(zhǔn)化組織 ECMA,之所以不叫做 JavaScript 由于商標(biāo)的問題,java 是 sun 公司的商標(biāo),根據(jù) 授權(quán)協(xié)議只有 Netscape 公司可以合法使用 JavaScript 這個名字,另外就是為了體現(xiàn) JavaScript 的標(biāo)準(zhǔn)的制定者 不是 ECMA 所以取名為 ECMAScript。


web.png
node.png

3.ES6 與 ECMAScript 2015 的關(guān)系

ES6 是 ECMA 為 JavaScript 制定的第 6 個版本的標(biāo)準(zhǔn),標(biāo)準(zhǔn)委員會最終決定,標(biāo)準(zhǔn)在每年的 6 月份正式發(fā)布一 次,作為當(dāng)年的正式版本。ECMAscript 2015 是在 2015 年 6 月份發(fā)布的 ES6 的第一個版本。依次類推 ECMAscript 2016 是 ES7, ECMAscript 2017 是 ES8, 后續(xù)版本都以年份為命名

塊級作用域

1.塊級作用域的種類

ECMAScript2015 為 js 提出的第三個作用域,凡是帶{}的都是一個塊級作用域。

if 語句的{},for 循環(huán)中的{},while 中的{},或者是我們單獨(dú)寫的{} try{}catch(error){}這些都提供了塊級作用域。

塊級作用域

為什么需要塊級作用域
  • 內(nèi)層變量會覆蓋外層變量

    var a = 'glh';
    function fn() {
      console.log(a); //undefined
      if (false) {
        var a = 'hello';
      }
    }
    fn();
    

這是因?yàn)?fn 函數(shù)體內(nèi)以及有 var 聲明的變量 a,只是還沒有賦值,默認(rèn)為 undefined。

  • 用來計數(shù)的循環(huán)變量泄露為全局變量

    for (var i = 0; i < 10; i++) {}
    // 10
    console.log(i);
    

let const

1.let

基本用法

ECMAScript2015 新增了let命令,用來聲明變量。它的用法類似于var,但是所聲明的變量,只在let命令所在的代碼塊內(nèi)有效。

let 主要聲明塊級作用域下的成員,這個成員變量的作用域范圍只能在當(dāng)前塊級作用域以及子作用域鏈中。

2.const

const 聲明變量的同時必須要賦值。

const 聲明之后,不允許去修改它的值,這里面的值說的是不允許修改它,是聲明之后不允許重新指向一個新

的內(nèi)存地址,可以去修改內(nèi)存地址中的屬性成員。

數(shù)組

1.數(shù)組解構(gòu)

ES6 允許按照一定模式,從數(shù)組和對象中提取值,對變量進(jìn)行賦值,這被稱為解構(gòu)。

  • 完全解構(gòu) 將數(shù)組中的每一個值都對應(yīng)上相應(yīng)的變量。

    var arr = ['a', 'b', 'c'];
    let [com, ind, work] = arr;
    console.log(work);  // c
    
  • 不完全解構(gòu) 數(shù)組中的部分值對應(yīng)上了相應(yīng)的變量。

    var arr = ['a', 'b', 'c'];
    let [, , work] = arr;
    console.log(work);  // c
    

    注意:模式?jīng)]有匹配上的可以不填,但是必須要加逗號隔開。

  • 解構(gòu)不成功

    右邊的變量的個數(shù)超過了等號左邊中數(shù)組的元素

    let [a, b, c] = [12];
    console.log(b); //undefined
    

    如果解構(gòu)沒有成功,則變量的值是 undefined,如果是展開運(yùn)算的變量則是空數(shù)組。

    let [a, b, ...c] = [12];
    console.log(c); // []
    

2.數(shù)組的擴(kuò)展

  • 展開運(yùn)算符說明

    • 三個點(diǎn)(…)是一個展開運(yùn)算符,其功能為對三個點(diǎn)后面的變量進(jìn)行展開操作

    • 三個點(diǎn)(…)展開運(yùn)算符:只能對具有 Iterator 接口的對象進(jìn)行展開操作

  • 替代 apply()的使用技巧

    我們之前在求一個數(shù)組中的最大值得時候采用得方式是 Math.max.apply(null,[12,34,56,43]) ==56

    var max = Math.max.apply(null, [12, 34, 56, 43]);
    console.log(max); //56
    var max2 = Math.max(...[12, 34, 56, 43]);
    console.log(max2); //56
    
  • Array 類的擴(kuò)展方法

    • Array.from()

    Array.from 方法用于將兩類對象轉(zhuǎn)為真正的數(shù)組:類似數(shù)組的對象(array-like object)和可遍歷(iterable)的對象(包括 ES6 新增的數(shù)據(jù)結(jié)構(gòu) Set 和 Map)

    var arraylike = {
      0: 'a',
      1: 'b',
      2: 'c',
      length: 3,
    };
    var arr = Array.from(arraylike);
    ['a', 'b', 'c'];
    

    Array.from 還可以接受第二個參數(shù),作用類似于數(shù)組的map方法,用來對每個元素進(jìn)行處理,將處理后的值放入返回的數(shù)組

    var arr = Array.from([1, 2, 3], function (x) {
      return x * x;
    });
    console.log(arr); //[1,4,9]
    
    • Array.of 方法用于將一組值,轉(zhuǎn)換為數(shù)組,這個方法的主要目的,是彌補(bǔ)數(shù)組構(gòu)造函數(shù)Array()的不足。因?yàn)閰?shù)個數(shù)的不同,會導(dǎo)致Array()的行為有差異。

      var arr = Array(3); //[emptyx3]
      

      這里面 3 表示數(shù)組中元素的長度。

      var arr = Array(2, 3, 4); //[2,3,4]
      

      當(dāng)Array()里的參數(shù)個數(shù)大于 1 的時候,表示的是數(shù)組元素。

      Array.of()方法不管里面參數(shù)的個數(shù)多少,都將其轉(zhuǎn)為數(shù)組的元素。

      var arr = Array.of(3);
      console.log(arr); //[3]
      

對象

1.對象解構(gòu)賦值

解構(gòu)不僅可以用于數(shù)組,還可以用于對象。對象的解構(gòu)與數(shù)組有一個重要的不同。數(shù)組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。

let { name, work } = { name: 'glh', work: 'code' };
console.log(name, work); // glh code

2.對象的擴(kuò)展

  • 對象的簡寫

    • 當(dāng)變量名和屬性名同名式,省略同名的屬性值

      const foo = 'bar';
      const baz = { foo };
      // 等同于
      const baz = { foo: foo };
      
    • 省略方法中的 function

      const obj = {
        method() {
          return 'hello';
        },
      };
      // 等同于
      const obj = {
        method: function () {
          return 'hello';
        },
      };
      
    • 屬性的賦值器(setter)和取值器(getter)

      const obj = {
        name: 'glh',
        get com() {
          return this.name;
        },
        set work(value) {
          this.name = this.name + value;
        },
      };
      console.log(obj.com); // glh
      obj.work = 'hello';
      console.log(obj.name); //glhhello
      
    • 屬性名表達(dá)式

      es5 中定義對象的屬性有兩種方法,一種是用標(biāo)識符(本質(zhì)為字符串)做屬性,一種是用表達(dá)式做屬性

      方法一;
      obj.name = 'a';
      // 方法二
      obj['name'] = 'a';
      
      var obj = {
        name: 'a',
      };
      

      如果使用大括號定義對象,那么在 es5 中只能使用標(biāo)識符定義屬性。

      var obj = {
        name: 'a',
      };
      

      但是 ECMAScript2015 在使用大括號定義對象的時候,允許使用表達(dá)式定義屬性,把表達(dá)式放在方括號中。

      let name = 'a';
      const obj = {
        [name]: 'glh',
      };
      console.log(obj); //{a: "glh"}
      

3.擴(kuò)展運(yùn)算符

  • 用于對象的解構(gòu)

    • 對象的解構(gòu)賦值用于從一個對象取值,相當(dāng)于將目標(biāo)對象自身的所有可遍歷的(enumerable)、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。

      let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
      console.log(z); //{a: 3, b: 4}
      

      上面代碼中,變量z是解構(gòu)賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵(ab),將它們連同值一起拷貝過來。

      注意: 1.解構(gòu)賦值必須是最后一個參數(shù)。2.解構(gòu)賦值的拷貝是淺拷貝。

  • 用于擴(kuò)展運(yùn)算

    • 對象的擴(kuò)展運(yùn)算符(...)用于取出參數(shù)對象的所有可遍歷屬性,拷貝到當(dāng)前對象之中。

      let z = { name: 'a', work: 'b' };
      let n = { ...z };
      n; // { name: "a", work: "b" }
      

字符串

1.字符串模板

  • 傳統(tǒng)的字符串里不能使用換行符,必須使用轉(zhuǎn)義符\n 替代,字符串模板里可以使用。模板字符串(template string)是增強(qiáng)版的字符串,用反引號(`)標(biāo)識。它可以當(dāng)作普通字符串使用,也可以用來定義多行字符串,或者在字符串中嵌入變量。

  • 模板字符串中嵌入變量,需要將變量名寫在${}之中。大括號內(nèi)部可以放入任意的 JavaScript 表達(dá)式,可以進(jìn)行運(yùn)算,以及引用對象屬性

    var name = 'glh';
    var newName = `歡迎來到${name}`;
    console.log(newName);
    

2.標(biāo)簽?zāi)0?/h4>
  • 模板字符串的功能,不僅僅是上面這些。它可以緊跟在一個函數(shù)名后面,該函數(shù)將被調(diào)用來處理這個模板字符串。這被稱為“標(biāo)簽?zāi)0濉惫δ堋?/p>

    console.log`hello`;
    等同于;
    console.log(['hello']);
    

    標(biāo)簽?zāi)0迤鋵?shí)不是模板,而是函數(shù)調(diào)用的一種特殊形式。“標(biāo)簽”指的就是函數(shù),緊跟在后面的模板字符串就是它的參數(shù)。

    注意:如果模板字符里面有變量,就不是簡單的調(diào)用了,而是會將模板字符串先處理成多個參數(shù),再調(diào)用函數(shù)。

const name = 'Tony';
const age = 18;

// const string = `My name is ${name}, Age is ${age}`;
// console.log(string);

// 標(biāo)簽函數(shù)
// const str = console.log`My name is ${name}, Age is ${age}`;

// 標(biāo)簽函數(shù)作用是對字符串加工 返回一個新值。利用這個特性可以做語言切換(國際化),模板引擎插件
const Tag = (string, ...rest) => {
  console.log(string);
  console.log(rest);
  return string.join();
};
const result = Tag`My name is ${name}, Age is ${age}`;
console.log(result);
  

3.擴(kuò)展的方法

  • 字符串實(shí)例的方法

    • includes()

      返回布爾值,表示是否找到了參數(shù)字符串

      const name = 'glh';
      const a = `Error: foo is not defined: ${name}`;
      var b = a.includes('Err');
      console.log(b); // true
      
    • startsWith()

      返回布爾值,表示參數(shù)字符串是否在原字符串的頭部

      const name = 'glh';
      const a = `Error: foo is not defined: ${name}`;
      const b = a.startsWith('Err');
      console.log(b); / /true
      
    • endsWith()

      返回布爾值,表示參數(shù)字符串是否在原字符串的尾部

      const name = 'glh';
      const a = `Error: foo is not defined: ${name}`;
      const b = a.endsWith('glh');
      console.log(b); // true
      

函數(shù)

參數(shù)默認(rèn)值

  • ES6 允許為函數(shù)的參數(shù)設(shè)置默認(rèn)值,即直接寫在參數(shù)定義的后面。

    function fn(a, b = 'glh ') {
      console.log(a + b);
    }
    fn('hello'); //hello glh
    
  • 注意:

    • 參數(shù)變量是默認(rèn)聲明的,所以不能用letconst再次聲明.
    • 使用參數(shù)默認(rèn)值時,函數(shù)不能有同名參數(shù)
  • 參數(shù)默認(rèn)值的位置

    • 通常情況下,定義了默認(rèn)值的參數(shù),應(yīng)該是函數(shù)的尾參數(shù)。因?yàn)檫@樣比較容易看出來,到底省略了哪些參數(shù)。如果非尾部的參數(shù)設(shè)置默認(rèn)值,實(shí)際上這個參數(shù)是沒法省略的。

      function f(x = 1, y) {
        return [x, y];
      }
      
      f() // [1, undefined]
      f(2) // [2, undefined]
      f(, 1) // 報錯
      

rest 參數(shù)

  • ES6 引入 rest 參數(shù)(形式為...變量名),用于獲取函數(shù)的多余參數(shù),這樣就不需要使用arguments對象了。rest 參數(shù)搭配的變量是一個數(shù)組,該變量將多余的參數(shù)放入數(shù)組中。

    function add(...values) {
      console.log(values);
    }
    add(2, 5, 3); // [2, 5, 3]
    
  • rest 參數(shù)和函數(shù)中的參數(shù)解構(gòu)有什么區(qū)別

    • rest 參數(shù)是發(fā)生在函數(shù)的定義階段,函數(shù)的額參數(shù)解構(gòu)是發(fā)生在函數(shù)的調(diào)用階段
    • 二者互為逆運(yùn)算
function add(...values) {
  //這是rest參數(shù)
  console.log(values);
}
add(2, 5, 3); // [2, 5, 3]

var arr = [1, 2, 3];
function fn(a, b, c) {
  console.log(a + b + c);
}
fn(...arr); //6  這是參數(shù)的解構(gòu)

箭頭函數(shù)

  • ES6 允許使用“箭頭”(=>)定義函數(shù)。
var f = v => v;
// 等同于
var f = function (v) {
  return v;
};

如果箭頭函數(shù)不需要參數(shù)或需要多個參數(shù),就使用一個圓括號代表參數(shù)部分。

var f = () => 5;
// 等同于
var f = function () {
  return 5;
};

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function (num1, num2) {
  return num1 + num2;
};

如果箭頭函數(shù)的代碼塊部分多于一條語句,就要使用大括號將它們括起來,并且使用return語句返回。

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

由于大括號被解釋為代碼塊,所以如果箭頭函數(shù)直接返回一個對象,必須在對象外面加上括號,否則會報錯。

let getItem = id => { id: id, name: "Temp" }; //報錯

let getItem = id => ({ id: id, name: "Temp" });//不報錯
  • 箭頭函數(shù)有幾個使用注意點(diǎn)
    • 函數(shù)體內(nèi)的this對象,就是定義時所在的對象,而不是調(diào)用時所在的對象。
    • 不可以當(dāng)作構(gòu)造函數(shù),也就是說,不可以使用new命令,否則會拋出一個錯誤。
    • 不可以使用arguments對象,該對象在函數(shù)體內(nèi)不存在。如果要用,可以用 rest 參數(shù)代替。
var name = "web"
var obj={
    name: "a",
    fn(){
        var t = setTimeout(function() {
            console.log(this.name)// web  this是window(瀏覽器環(huán)境)
        },1000)
    }
}
obj.fn()
-----------------------------------
var name = "web"
var obj={
    name: "a",
    fn(){
        var t = setTimeout(() => {
            console.log(this.name)// a this是obj
        },1000)
    }
}
obj.fn()

Object

Object.assign()

  • Object.assign方法用于對象的合并,將源對象(source)的所有可枚舉屬性,復(fù)制到目標(biāo)對象(target)。

    const target = {
      a: 123,
      b: 123,
    };
    const sourcel = {
      a: 456,
      c: 456,
    };
    const result = Object.assign(target, sourcel);
    console.log(target); //{a: 456, b: 123, c: 456}
    console.log(target === result); //true
    

    如果目標(biāo)對象與源對象有同名屬性,則后面的屬性會覆蓋前面的屬性。且assign()的返回值就是第一個對象。

    如果有多個源對象有同名屬性,依然是后面的會覆蓋前面的屬性

    const target = { a: 1, b: 1 };
    const source1 = { b: 2, c: 2 };
    const source2 = { c: 3 };
    Object.assign(target, source1, source2);
    target; // {a:1, b:2, c:3}
    
  • 利用Object.assign()復(fù)制一個對象,且其中一個對象的修改不會影響到另一個對象

    const source = {
      a: 123,
    };
    var obj = Object.assign({}, source);
    obj.a = 456;
    console.log(obj); //{a: 456}
    console.log(source); //{a: 123}
    

Object.is()

  • Object.is就是用來比較兩個值是否嚴(yán)格相等,與嚴(yán)格比較運(yùn)算符(===)的行為基本一致。

    ES5 比較兩個值是否相等,只有兩個運(yùn)算符:相等運(yùn)算符(==)和嚴(yán)格相等運(yùn)算符(===)。它們都有缺點(diǎn),前者會自動轉(zhuǎn)換數(shù)據(jù)類型,后者的NaN不等于自身,以及+0等于-0。JavaScript 缺乏一種運(yùn)算,在所有環(huán)境中,只要兩個值是一樣的,它們就應(yīng)該相等。

    console.log(Object.is(+0, -0)); //false
    console.log(+0 === -0); //true
    
    console.log(Object.is(NaN, NaN)); //true
    console.log(NaN === NaN); //false
    

Proxy

概述

Proxy 可以理解成一個快遞員,我們發(fā)快遞還是接受快遞,都需要這個快遞員充當(dāng)一個代理的作用。ES6 原生提供 Proxy 構(gòu)造函數(shù),用來生成 Proxy 實(shí)例,這個實(shí)例就是一個代理對象(快遞員)。

 var proxy = new Proxy(target, handler);

目標(biāo)對象

這個代理對象有兩個參數(shù),一個是代理的目標(biāo)對象,第二個也是一個對象,它是配置對象,用來定制代理的攔截行為。

const person = {
  name: 'glh',
  age: 20,
};
const personProxy = new Proxy(person, {
  get(target, property) {
    console.log(target, property); //person{name:"glh",age:20}
    return 100;
  },
  set() {},
});

配置對象

  • 配置對象中一般有兩個方法getset,get是用來攔截對目標(biāo)對象屬性的訪問請求。get方法中有兩個參數(shù),第一個參數(shù)是目標(biāo)對象,第二個參數(shù)是訪問的那個屬性。

    const person = {
      name: 'glh',
      age: 20,
    };
    const personProxy = new Proxy(person, {
      get(target, property) {
        console.log(target, property);
        return 100;
      },
      set() {},
    });
    
    console.log(personProxy.name); //100
    

注意,這個get方法的返回值就是我們獲取的這個屬性的返回值。

  • 這個get方法中有三個參數(shù),一個是代理的目標(biāo)對象,一個是代理的處理對象,第三個參數(shù)是 proxy 實(shí)例本身,且第三個參數(shù)是可選參數(shù)。

    const person = {
      name: 'glh',
      age: 20,
    };
    const personProxy = new Proxy(person, {
      get(target, property, o) {
        console.log(o); //proxy {name:"glh",age:20}
        return property in target ? target[property] : undefined;
      },
      set() {},
    });
    
    console.log(personProxy.age); //20
    
  • 這個set方法用來攔截某個屬性的賦值操作,可以接受四個參數(shù),依次為目標(biāo)對象、屬性名、屬性值和 Proxy 實(shí)例本身,其中最后一個參數(shù)可選

    const person = {
      name: 'glh',
      age: 20,
    };
    const personProxy = new Proxy(person, {
      get(target, property, o) {
        return property in target ? target[property] : undefined;
      },
      set(obj, pro, value, o) {
        console.log(obj, pro, value, o);
      },
    });
    
    console.log((personProxy.name = 'x'));
    //{name: "glh", age: 20} "name" "x" Proxy {name: "zce", age: 20}
    

    可以去設(shè)置一些屬性或修改

    const person = {
      name: 'glh',
      age: 20,
    };
    const personProxy = new Proxy(person, {
      get(target, property, o) {
        return property in target ? target[property] : undefined;
      },
      set(target, pro, value, o) {
        //可以做一些內(nèi)部校驗(yàn)
        target[pro] = value;
      },
    });
    console.log((personProxy.name = 'hello'));
    person; // {name:"glh",age:20}
    

Reflect

概述

Reflect對象與Proxy對象一樣,也是 ES6 為了操作對象而提供的新 API。Reflect對象的設(shè)計目的有這樣幾個。

  • Object對象的一些明顯屬于語言內(nèi)部的方法(比如Object.defineProperty),放到Reflect對象上?,F(xiàn)階段,某些方法同時在ObjectReflect對象上部署,未來的新方法將只部署在Reflect對象上。也就是說,從Reflect對象上可以拿到語言內(nèi)部的方法

  • 修改某些Object方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false

    // 老寫法
    try {
      Object.defineProperty(target, property, attributes);
      // success
    } catch (e) {
      // failure
    }
    
    // 新寫法
    if (Reflect.defineProperty(target, property, attributes)) {
      // success
    } else {
      // failure
    }
    
  • Object操作都變成函數(shù)行為。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。

    // 老寫法
    'assign' in Object; // true
    
    // 新寫法
    Reflect.has(Object, 'assign'); // true
    

靜態(tài)方法

  • Reflect.get

    • Reflect.get(target, name, receiver),Reflect.get方法查找并返回target對象的name屬性,如果沒有該屬性,則返回undefined

      var myObject = {
        foo: 1,
        bar: 2,
        get baz() {
          return this.foo + this.bar;
        },
      };
      
      Reflect.get(myObject, 'foo'); // 1
      Reflect.get(myObject, 'bar'); // 2
      Reflect.get(myObject, 'baz'); // 3
      
    • 如果name屬性部署了讀取函數(shù)(getter),則讀取函數(shù)的this綁定receiver。

      var myObject = {
        foo: 1,
        bar: 2,
        get baz() {
          return this.foo + this.bar;
        },
      };
      
      var myReceiverObject = {
        foo: 4,
        bar: 4,
      };
      
      Reflect.get(myObject, 'baz', myReceiverObject); // 8
      
    • 如果第一個參數(shù)不是對象,Reflect.get方法會報錯。

      Reflect.get(1, 'foo'); // 報錯
      Reflect.get(false, 'foo'); // 報錯
      
  • Reflect.set

    • Reflect.set(target, name, value, receiver),Reflect.set方法設(shè)置target對象的name屬性等于value

      var myObject = {
        foo: 1,
      };
      
      myObject.foo; // 1
      
      Reflect.set(myObject, 'foo', 2);
      myObject.foo; // 2
      
    • 如果name屬性設(shè)置了賦值函數(shù),則賦值函數(shù)的this綁定receiver。

      var myObject = {
        foo: 4,
        set bar(value) {
          return (this.foo = value);
        },
      };
      
      var myReceiverObject = {
        foo: 0,
      };
      
      Reflect.set(myObject, 'bar', 1, myReceiverObject);
      myObject.foo; // 4
      myReceiverObject.foo; // 1
      
  • Reflect.has

    • Reflect.has(obj, name),Reflect.has方法對應(yīng)name in obj里面的in運(yùn)算符

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

      如果Reflect.has()方法的第一個參數(shù)不是對象,會報錯。

  • Reflect.deleteProperty

    • Reflect.deleteProperty(obj, name),Reflect.deleteProperty方法等同于delete obj[name],用于刪除對象的屬性

      const myObj = { foo: 'bar' };
      
      // 舊寫法
      delete myObj.foo;
      
      // 新寫法
      Reflect.deleteProperty(myObj, 'foo');
      

      該方法返回一個布爾值。如果刪除成功,或者被刪除的屬性不存在,返回true;刪除失敗,被刪除的屬性依然存在,返回false。如果Reflect.deleteProperty()方法的第一個參數(shù)不是對象,會報錯.

Promise

概述

Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大。它由社區(qū)最早提出和實(shí)現(xiàn),ES6 將其寫進(jìn)了語言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對象。

所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進(jìn)行處理。

promise 特點(diǎn)

Promise對象有以下兩個特點(diǎn)。

(1)對象的狀態(tài)不受外界影響。Promise對象代表一個異步操作,有三種狀態(tài):pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失?。V挥挟惒讲僮鞯慕Y(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。

(2)一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結(jié)果。Promise對象的狀態(tài)改變,只有兩種可能:從pending變?yōu)?code>fulfilled和從pending變?yōu)?code>rejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再變了,會一直保持這個結(jié)果,這時就稱為 resolved(已定型)。如果改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果。這與事件(Event)完全不同,事件的特點(diǎn)是,如果你錯過了它,再去監(jiān)聽,是得不到結(jié)果的。

有了Promise對象,就可以將異步操作以同步操作的流程表達(dá)出來,避免了層層嵌套的回調(diào)函數(shù)。此外,Promise對象提供統(tǒng)一的接口,使得控制異步操作更加容易。

promise 使用方法

  • ES6 規(guī)定,Promise對象是一個構(gòu)造函數(shù),用來生成Promise實(shí)例。

  • Promise構(gòu)造函數(shù)接受一個函數(shù)作為參數(shù),該函數(shù)的兩個參數(shù)分別是resolvereject。它們是兩個函數(shù),由 JavaScript 引擎提供,不用自己部署。

    var p = new Promise(function (resolve, reject) {
      if (true) {
        resolve(data);
      } else {
        reject(data);
      }
    });
    

    resolve函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸?pending 變?yōu)?resolved),在異步操作成功時調(diào)用,并將異步操作的結(jié)果,作為參數(shù)傳遞出去;reject函數(shù)的作用是,將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸?pending 變?yōu)?rejected),在異步操作失敗時調(diào)用,并將異步操作報出的錯誤,作為參數(shù)傳遞出去。

  • Promise實(shí)例生成以后,可以用then方法分別指定resolved狀態(tài)和rejected狀態(tài)的回調(diào)函數(shù)。

    p.then(
      function (value) {
        // success業(yè)務(wù)處理
      },
      function (error) {
        // failure
      }
    );
    

    then方法可以接受兩個回調(diào)函數(shù)作為參數(shù)。第一個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)?code>resolved時調(diào)用,第二個回調(diào)函數(shù)是Promise對象的狀態(tài)變?yōu)?code>rejected時調(diào)用。其中,第二個函數(shù)是可選的,不一定要提供。這兩個函數(shù)都接受Promise對象傳出的值作為參數(shù)。

    • 看一個簡單的例子

      function time(ms) {
        return new Promise((resolve, reject) => {
          setTimeout(resolve, ms);
        });
      }
      time(1000).then(value => {
        console.log(value);
      });
      
  • Promise 新建后就會立即執(zhí)行。

    let promise = new Promise(function (resolve, reject) {
      console.log('Promise');
      resolve();
    });
    
    promise.then(function () {
      console.log('resolved.');
    });
    
    console.log('Hi!');
    //Promise
    //Hi
    //resolved
    

class

概述

ES6 提供了更接近傳統(tǒng)語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關(guān)鍵字,可以定義類。基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已。上面的代碼用 ES6 的class改寫,就是下面這樣。

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

基本介紹

  • constructor()

    • constructor方法是類的默認(rèn)方法,通過new命令生成對象實(shí)例時,自動調(diào)用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認(rèn)添加。
  • 類的實(shí)例

    • 生成類的實(shí)例的寫法,與 ES5 完全一樣,也是使用new命令。前面說過,如果忘記加上new,像函數(shù)那樣調(diào)用Class,將會報錯。

      class Point {
        // ...
      }
      // 報錯
      var point = Point(2, 3);
      // 正確
      var point = new Point(2, 3);
      
    • 與 ES5 一樣,實(shí)例的屬性除非顯式定義在其本身(即定義在this對象上),否則都是定義在原型上(即定義在class上)。

      class Point {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
      
        toString() {
          return '(' + this.x + ', ' + this.y + ')';
        }
      }
      
      var point = new Point(2, 3);
      
      point.toString(); // (2, 3)
      
      point.hasOwnProperty('x'); // true
      point.hasOwnProperty('y'); // true
      point.hasOwnProperty('toString'); // false
      point.__proto__.hasOwnProperty('toString'); // true
      
    • 與 ES5 一樣,類的所有實(shí)例共享一個原型對象

      var p1 = new Point(2, 3);
      var p2 = new Point(3, 2);
      
      p1.__proto__ === p2.__proto__;
      //true
      
  • getter 和 setter

    • 與 ES5 一樣,在“類”的內(nèi)部可以使用getset關(guān)鍵字,對某個屬性設(shè)置存值函數(shù)和取值函數(shù),攔截該屬性的存取行為。

      class MyClass {
        constructor() {
          // ...
        }
        get prop() {
          return 'getter';
        }
        set prop(value) {
          console.log('setter: ' + value);
        }
      }
      
      let inst = new MyClass();
      
      inst.prop = 123;
      // setter: 123
      
      inst.prop;
      // 'getter'
      
  • 屬性表達(dá)式

    • 類的屬性名,可以采用表達(dá)式

      let methodName = 'getArea';
      
      class Square {
        constructor(length) {
          // ...
        }
      
        [methodName]() {
          // ...
        }
      }
      

      上面代碼中,Square類的方法名getArea,是從表達(dá)式得到的

static

  • 類相當(dāng)于實(shí)例的原型,所有在類中定義的方法,都會被實(shí)例繼承。如果在一個方法前,加上static關(guān)鍵字,就表示該方法不會被實(shí)例繼承,而是直接通過類來調(diào)用,這就稱為“靜態(tài)方法”

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    Foo.classMethod(); // 'hello'
    
    var foo = new Foo();
    foo.classMethod();
    // TypeError: foo.classMethod is not a function
    

    解說:上面代碼中,Foo類的classMethod方法前有static關(guān)鍵字,表明該方法是一個靜態(tài)方法,可以直接在Foo類上調(diào)用(Foo.classMethod()),而不是在Foo類的實(shí)例上調(diào)用。如果在實(shí)例上調(diào)用靜態(tài)方法,會拋出一個錯誤,表示不存在該方法。

  • 注意,如果靜態(tài)方法包含this關(guān)鍵字,這個this指的是類,而不是實(shí)例

    class Foo {
      static bar() {
        this.baz();
      }
      static baz() {
        console.log('hello');
      }
      baz() {
        console.log('world');
      }
    }
    
    Foo.bar(); // hello
    

    解說:上面代碼中,靜態(tài)方法bar調(diào)用了this.baz,這里的this指的是Foo類,而不是Foo的實(shí)例,等同于調(diào)用Foo.baz。另外,從這個例子還可以看出,靜態(tài)方法可以與非靜態(tài)方法重名。

  • 父類的靜態(tài)方法,可以被子類繼承

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {}
    
    Bar.classMethod(); // 'hello'
    
  • 靜態(tài)屬性

    靜態(tài)屬性指的是 Class 本身的屬性,即Class.propName,而不是定義在實(shí)例對象(this)上的屬性

    ES6 明確規(guī)定,Class 內(nèi)部只有靜態(tài)方法,沒有靜態(tài)屬性。現(xiàn)在有一個提案提供了類的靜態(tài)屬性,寫法是在實(shí)例屬性的前面,加上static關(guān)鍵字。

    class MyClass {
      static myStaticProp = 42;
    
      constructor() {
        console.log(MyClass.myStaticProp); // 42
      }
    }
    

繼承

  • 簡介

    • Class 可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。

      class Point {}
      
      class ColorPoint extends Point {}
      

      解說:上面代碼定義了一個ColorPoint類,該類通過extends關(guān)鍵字,繼承了Point類的所有屬性和方法。但是由于沒有部署任何代碼,所以這兩個類完全一樣,等于復(fù)制了一個Point

    • 子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時會報錯。這是因?yàn)樽宇愖约旱?code>this對象,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法,然后再對其進(jìn)行加工,加上子類自己的實(shí)例屬性和方法。如果不調(diào)用super方法,子類就得不到this對象。

      class Point {
        /* ... */
      }
      
      class ColorPoint extends Point {
        constructor() {}
      }
      
      let cp = new ColorPoint(); // ReferenceError
      
    • 在子類的構(gòu)造函數(shù)中,只有調(diào)用super之后,才可以使用this關(guān)鍵字,否則會報錯。這是因?yàn)樽宇悓?shí)例的構(gòu)建,基于父類實(shí)例,只有super方法才能調(diào)用父類實(shí)例。

      class Point {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
      }
      
      class ColorPoint extends Point {
        constructor(x, y, color) {
          this.color = color; // ReferenceError
          super(x, y);
          this.color = color; // 正確
        }
      }
      
  • super

    • super這個關(guān)鍵字,既可以當(dāng)作函數(shù)使用,也可以當(dāng)作對象使用。在這兩種情況下,它的用法完全不同

      • 第一種情況,super作為函數(shù)調(diào)用時,代表父類的構(gòu)造函數(shù)。ES6 要求,子類的構(gòu)造函數(shù)必須執(zhí)行一次super函數(shù)。

        class A {}
        
        class B extends A {
          constructor() {
            super();
          }
        }
        

        解說:上面代碼中,子類B的構(gòu)造函數(shù)之中的super(),代表調(diào)用父類的構(gòu)造函數(shù)。這是必須的,否則 JavaScript 引擎會報錯。super雖然代表了父類A的構(gòu)造函數(shù),但是返回的是子類B的實(shí)例,即super內(nèi)部的this指的是B的實(shí)例,因此super()在這里相當(dāng)于A.prototype.constructor.call(this)`

      • 第二種情況,super作為對象時,在普通方法中,指向父類的原型對象;在靜態(tài)方法中,指向父類

        class A {
          p() {
            return 2;
          }
        }
        
        class B extends A {
          constructor() {
            super();
            console.log(super.p()); // 2
          }
        }
        
        let b = new B();
        
      • 由于super指向父類的原型對象(prototype),所以定義在父類實(shí)例上的方法或?qū)傩?,是無法通過super調(diào)用的

        class A {
          constructor() {
            this.p = 2;
          }
        }
        
        class B extends A {
          get m() {
            return super.p;
          }
        }
        
        let b = new B();
        b.m; // undefined
        

Set

基本用法

  • ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set。它類似于數(shù)組,但是成員的值都是唯一的,沒有重復(fù)的值。

  • Set本身是一個構(gòu)造函數(shù),用來生成 Set 數(shù)據(jù)結(jié)構(gòu)。

  • Set函數(shù)可以接受一個數(shù)組(或者具有 iterable 接口的其他數(shù)據(jù)結(jié)構(gòu))作為參數(shù),用來初始化。

    const set = new Set([1, 2, 3, 4, 4]);
    console.log(set); //[1,2,3,4]
    
    • 數(shù)組去重

      [...new Set([1, 2, 3, 2, 4, 5])]; //[1,2,3,4,5]
      
    • 字符串去重

      [...new Set('ababbc')].join(''); //"abc"
      

屬性和方法

  • Set 結(jié)構(gòu)的實(shí)例有以下屬性。

    • Set.prototype.constructor:構(gòu)造函數(shù),默認(rèn)就是Set函數(shù)。
    • Set.prototype.size:返回Set實(shí)例的成員總數(shù)。
  • Set 實(shí)例的方法分為兩大類

    • 操作方法(用于操作數(shù)據(jù))

      • Set.prototype.add(value):添加某個值,返回 Set 結(jié)構(gòu)本身

        const items = new Set([]);
        items.add(1).add(2).add(3);
        console.dir(items); //[1,2,3]
        
      • Set.prototype.delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功

        const items = new Set([12, 23, 34]);
        var b = items.delete(12);
        console.log(b); //true
        console.log(items); //Set(2) {23, 34}
        
      • Set.prototype.has(value):返回一個布爾值,表示該值是否為Set的成員。

      • Set.prototype.clear():清除所有成員,沒有返回值

        const items = new Set([12, 23, 34]);
        var b = items.clear(12);
        console.log(b); //undefined
        console.log(items); //Set(0) {}
        
    • 遍歷方法(用于遍歷成員)

      • Set.prototype.keys():返回鍵名的遍歷器

      • Set.prototype.values():返回鍵值的遍歷器

      • Set.prototype.entries()

        let set = new Set(['red', 'green', 'blue']);
        for (let item of set.keys()) {
          console.log(item);
        }
        // red
        // green
        // blue
        for (let item of set.values()) {
          console.log(item);
        }
        // red
        // green
        // blue
        for (let item of set.entries()) {
          console.log(item);
        }
        // ["red", "red"]
        // ["green", "green"]
        // ["blue", "blue"]
        
      • Set.prototype.forEach()

        let set = new Set([1, 4, 9]);
        set.forEach((value, key) => console.log(key + ' : ' + value));
        // 1 : 1
        // 4 : 4
        // 9 : 9
        

Map

概述

JavaScript 的對象(Object),本質(zhì)上是鍵值對的集合(Hash 結(jié)構(gòu)),但是傳統(tǒng)上只能用字符串當(dāng)作鍵。這給它的使用帶來了很大的限制。為了解決這個問題,ES6 提供了 Map 數(shù)據(jù)結(jié)構(gòu)。它類似于對象,也是鍵值對的集合,但是“鍵”的范圍不限于字符串,各種類型的值(包括對象)都可以當(dāng)作鍵。也就是說,Object 結(jié)構(gòu)提供了“字符串—值”的對應(yīng),Map 結(jié)構(gòu)提供了“值—值”的對應(yīng),是一種更完善的 Hash 結(jié)構(gòu)實(shí)現(xiàn)。如果你需要“鍵值對”的數(shù)據(jù)結(jié)構(gòu),Map 比 Object 更合適。

基本用法

  • 作為構(gòu)造函數(shù),Map 也可以接受一個數(shù)組作為參數(shù)。該數(shù)組的成員是一個個表示鍵值對的數(shù)組

    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"
    

屬性和方法

  • size 屬性,size屬性返回 Map 結(jié)構(gòu)的成員總數(shù)。

    const map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    
    map.size; // 2
    
  • Map.prototype.set(key, value) set方法設(shè)置鍵名key對應(yīng)的鍵值為value,然后返回整個 Map 結(jié)構(gòu)。如果key已經(jīng)有值,則鍵值會被更新,否則就新生成該鍵, set()方法返回的是 set 對象可以采用鏈?zhǔn)綄懛?/p>

    const m = new Map();
    
    m.set('edition', 6); // 鍵是字符串
    m.set(262, 'standard'); // 鍵是數(shù)值
    m.set(undefined, 'nah'); // 鍵是 undefined
    
  • Map.prototype.get(key) get方法讀取key對應(yīng)的鍵值,如果找不到key,返回undefined。

    const m = new Map();
    
    const hello = function () {
      console.log('hello');
    };
    m.set(hello, 'Hello ES6!'); // 鍵是函數(shù)
    
    m.get(hello); // Hello ES6!
    
  • Map.prototype.has(key)has方法返回一個布爾值,表示某個鍵是否在當(dāng)前 Map 對象之中

    const m = new Map();
    
    m.set('edition', 6);
    m.set(262, 'standard');
    m.set(undefined, 'nah');
    
    m.has('edition'); // true
    m.has('years'); // false
    m.has(262); // true
    m.has(undefined); // true
    
  • Map.prototype.delete(key)delete方法刪除某個鍵,返回true。如果刪除失敗,返回false。

    const m = new Map();
    m.set(undefined, 'nah');
    m.has(undefined); // true
    
    m.delete(undefined);
    m.has(undefined); // false
    
  • Map.prototype.clear()clear方法清除所有成員,沒有返回值

    let map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    
    map.size; // 2
    map.clear();
    map.size; // 0
    

遍歷

  • Map.prototype.keys():返回鍵名的遍歷器。

  • Map.prototype.values():返回鍵值的遍歷器

  • Map.prototype.entries():返回所有成員的遍歷器

  • Map.prototype.forEach():遍歷 Map 的所有成員

    const map = new Map([
      ['F', 'no'],
      ['T', 'yes'],
    ]);
    
    for (let key of map.keys()) {
      console.log(key);
    }
    // "F"
    // "T"
    
    for (let value of map.values()) {
      console.log(value);
    }
    // "no"
    // "yes"
    
    for (let item of map.entries()) {
      console.log(item[0], item[1]);
    }
    // "F" "no"
    // "T" "yes"
    
    // 或者
    for (let [key, value] of map.entries()) {
      console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"
    map.forEach(function (value, key, map) {
      console.log('Key: %s, Value: %s', key, value);
    });
    

    forEach方法還可以接受第二個參數(shù),用來綁定this。

Symbol

概述

ES5 的對象屬性名都是字符串,這容易造成屬性名的沖突。比如,你使用了一個他人提供的對象,但又想為這個對象添加新的方法(mixin 模式),新方法的名字就有可能與現(xiàn)有方法產(chǎn)生沖突。如果有一種機(jī)制,保證每個屬性的名字都是獨(dú)一無二的就好了,這樣就從根本上防止屬性名的沖突。這就是 ES6 引入Symbol的原因。ES6 引入了一種新的原始數(shù)據(jù)類型Symbol,表示獨(dú)一無二的值。它是 JavaScript 語言的第七種數(shù)據(jù)類型,前六種是:undefined、null、布爾值(Boolean)、字符串(String)、數(shù)值(Number)、對象(Object)。Symbol 值通過Symbol函數(shù)生成。這就是說,對象的屬性名現(xiàn)在可以有兩種類型,一種是原來就有的字符串,另一種就是新增的 Symbol 類型。凡是屬性名屬于 Symbol 類型,就都是獨(dú)一無二的,可以保證不會與其他屬性名產(chǎn)生沖突。

var obj = {
  say: 'a',
};
var say = Symbol(); //say 是symbol類型
obj[say] = 'web';
console.log(obj); //{say: "a", Symbol(): "web"}

語法

  • Symbol函數(shù)前不能使用new命令,否則會報錯

  • Symbol函數(shù)可以接受一個字符串作為參數(shù),表示對 Symbol 實(shí)例的描述,主要是為了在控制臺顯示,或者轉(zhuǎn)為字符串時,比較容易區(qū)分

  • 每一個 Symbol 值都是不相等的,這意味著 Symbol 值可以作為標(biāo)識符,用于對象的屬性名,就能保證不會出現(xiàn)同名的屬性

    var a = Symbol();
    var b = Symbol();
    console.log(a === b); //false
    var a = Symbol('a');
    var b = Symbol('b');
    console.log(a === b); //false
    

    注意:Symbol函數(shù)的參數(shù)只是表示對當(dāng)前 Symbol 值的描述,因此相同參數(shù)的Symbol函數(shù)的返回值是不相等的

    var a = Symbol('a');
    var b = Symbol('a');
    console.log(a === b); //false
    
  • Symbol 值不能與其他類型的值進(jìn)行運(yùn)算,會報錯

    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 值作為對象屬性名時,不能用點(diǎn)運(yùn)算符

    const mySymbol = Symbol();
    const a = {};
    a.mySymbol = 'Hello!';
    console.log(a[mySymbol]); //undefined
    console.log(a['mySymbol']); //hello
    
  • Symbol 作為屬性名,遍歷對象的時候,該屬性不會出現(xiàn)在for...in、for...of循環(huán)中,也不會被Object.keys()Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols()方法,可以獲取指定對象的所有 Symbol 屬性名。該方法返回一個數(shù)組,成員是當(dāng)前對象的所有用作屬性名的 Symbol 值。

    const obj = {};
    let a = Symbol('a');
    let b = Symbol('b');
    obj[a] = 'Hello';
    obj[b] = 'World';
    const objectSymbols = Object.getOwnPropertySymbols(obj);
    console.log(objectSymbols); //[Symbol(a), Symbol(b)]
    
  • 有時,我們希望重新使用同一個 Symbol 值,Symbol.for()方法可以做到這一點(diǎn)。它接受一個字符串作為參數(shù),然后搜索有沒有以該參數(shù)作為名稱的 Symbol 值。如果有,就返回這個 Symbol 值,否則就新建一個以該字符串為名稱的 Symbol 值,并將其注冊到全局。

    let s1 = Symbol.for('foo');
    let s2 = Symbol.for('foo');
    
    s1 === s2; // true
    

    Symbol.for()Symbol()這兩種寫法,都會生成新的 Symbol。它們的區(qū)別是,前者會被登記在全局環(huán)境中供搜索,后者不會。Symbol.for()不會每次調(diào)用就返回一個新的 Symbol 類型的值,而是會先檢查給定的key是否已經(jīng)存在,如果不存在才會新建一個值。比如,如果你調(diào)用Symbol.for("cat")30 次,每次都會返回同一個 Symbol 值,但是調(diào)用Symbol("cat")30 次,會返回 30 個不同的 Symbol 值

    Symbol.for('bar') === Symbol.for('bar');
    // true
    
    Symbol('bar') === Symbol('bar');
    

    由于Symbol()寫法沒有登記機(jī)制,所以每次調(diào)用都會返回一個不同的值。Symbol.for()為 Symbol 值登記的名字,是全局環(huán)境的,不管有沒有在全局環(huán)境運(yùn)行

    function foo() {
      return Symbol.for('bar');
    }
    
    const x = foo();
    const y = Symbol.for('bar');
    console.log(x === y); // true
    

可迭代接口

Iterater 的概念

  • 簡單介紹

    JavaScript 原有的表示“集合”的數(shù)據(jù)結(jié)構(gòu),主要是數(shù)組(Array)和對象(Object),ES6 又添加了MapSet。這樣就有了四種數(shù)據(jù)集合,用戶還可以組合使用它們,定義自己的數(shù)據(jù)結(jié)構(gòu),比如數(shù)組的成員是Map,Map的成員是對象。這樣就需要一種統(tǒng)一的接口機(jī)制,來處理所有不同的數(shù)據(jù)結(jié)構(gòu)。

    遍歷器(Iterator)就是這樣一種機(jī)制。它是一種接口,為各種不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的訪問機(jī)制。任何數(shù)據(jù)結(jié)構(gòu)只要部署 Iterator 接口,就可以完成遍歷操作(即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員)。

    Iterator 的作用有三個:一是為各種數(shù)據(jù)結(jié)構(gòu),提供一個統(tǒng)一的、簡便的訪問接口;二是使得數(shù)據(jù)結(jié)構(gòu)的成員能夠按某種次序排列;三是 ES6 創(chuàng)造了一種新的遍歷命令for...of循環(huán),Iterator 接口主要供for...of消費(fèi)。

  • Iterator 的遍歷過程

    • 創(chuàng)建一個指針對象,指向當(dāng)前數(shù)據(jù)結(jié)構(gòu)的起始位置。也就是說,遍歷器對象本質(zhì)上,就是一個指針對象。

    • 第一次調(diào)用指針對象的next方法,可以將指針指向數(shù)據(jù)結(jié)構(gòu)的第一個成員

    • 第二次調(diào)用指針對象的next方法,指針就指向數(shù)據(jù)結(jié)構(gòu)的第二個成員

    • 不斷調(diào)用指針對象的next方法,直到它指向數(shù)據(jù)結(jié)構(gòu)的結(jié)束位置

      每一次調(diào)用next方法,都會返回數(shù)據(jù)結(jié)構(gòu)的當(dāng)前成員的信息。具體來說,就是返回一個包含valuedone兩個屬性的對象。其中,value屬性是當(dāng)前成員的值,done屬性是一個布爾值,表示遍歷是否結(jié)束。

      簡單的Iterator遍歷器的實(shí)現(xiàn);
      var it = easyIterator(['a', 'b']);
      
      it.next(); // { value: "a", done: false }
      it.next(); // { value: "b", done: false }
      it.next(); // { value: undefined, done: true }
      
      function easyIterator(array) {
        var nextIndex = 0;
        return {
          next: function () {
            return nextIndex < array.length
              ? { value: array[nextIndex++], done: false }
              : { value: undefined, done: true };
          },
        };
      }
      

Iterater 接口

  • 字符串 數(shù)組 set map arguments 都有 iterater 接口,nodelist 集合,都可以用 for of 遍歷

    var st = 'glh';
    for (i of st) {
      console.log(i); // g l h
    }
    var arr = [1, 2];
    for (v of arr) {
      console.log(v); //1 2
    }
    function fn(a, b, c) {
      for (i of arguments) {
        console.log(i); //1 2 3
      }
    }
    fn(1, 2, 3);
    

Modules

概述

  • JavaScript 一直沒有模塊(module)體系,無法將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。其他語言都有這項(xiàng)功能,比如 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,但是 JavaScript 任何這方面的支持都沒有,這對開發(fā)大型的、復(fù)雜的項(xiàng)目形成了巨大障礙。ES6 在語言標(biāo)準(zhǔn)的層面上,實(shí)現(xiàn)了模塊功能,而且實(shí)現(xiàn)得相當(dāng)簡單,完全可以取代 CommonJS 和 AMD 規(guī)范,成為瀏覽器和服務(wù)器通用的模塊解決方案。

語法

  • export

    • export命令用于規(guī)定模塊的對外接口

    • 一個模塊就是一個獨(dú)立的文件。該文件內(nèi)部的所有變量,外部無法獲取。如果你希望外部能夠讀取模塊內(nèi)部的某個變量,就必須使用export關(guān)鍵字輸出該變量。下面是一個 JS 文件,里面使用export命令輸出變量

      //demo.js
      export var firstName = 'Michael';
      export var lastName = 'Jackson';
      export var year = 1958;
      //或者
      var firstName = 'Michael';
      var lastName = 'Jackson';
      var year = 1958;
      
      export { firstName, lastName, year };
      
  • import

    • import命令用于輸入其他模塊提供的功能

    • import命令接受一對大括號,里面指定要從其他模塊導(dǎo)入的變量名。大括號里面的變量名,必須與被導(dǎo)入模塊(profile.js)對外接口的名稱相同

      // main.js
      import { firstName, lastName, year } from './profile.js';
      
      function setName(element) {
        element.textContent = firstName + ' ' + lastName;
      }
      
  • export default

    • 為了給用戶提供方便,就要用到export default命令,為模塊指定默認(rèn)輸出

    • 本質(zhì)上,export default就是輸出一個叫做default的變量或方法,然后系統(tǒng)允許你為它取任意名字

    • import命令后面,不再使用大括號

    • export default命令用于指定模塊的默認(rèn)輸出。顯然,一個模塊只能有一個默認(rèn)輸出,因此export default命令只能使用一次

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

瀏覽器端加載實(shí)現(xiàn)

  • 瀏覽器加載 ES6 模塊,也使用標(biāo)簽,但是要加入type="module"屬性

    // 01.js
    export var a = 123;
    
    //demo.html
    <script type="module">import {a} from "./01.js"; console.log(a)//123</script>
    
  • 腳本異步加載

    <script src="path/to/myModule.js" defer></script>
    <script src="path/to/myModule.js" async></script>
    

    解說:上面代碼中,標(biāo)簽打開deferasync屬性,腳本就會異步加載。渲染引擎遇到這一行命令,就會開始下載外部腳本,但不會等它下載和執(zhí)行,而是直接執(zhí)行后面的命令。defer 與 async 的區(qū)別是:defer 要等到整個頁面在內(nèi)存中正常渲染結(jié)束(DOM 結(jié)構(gòu)完全生成,以及其他腳本執(zhí)行完成),才會執(zhí)行;async 一旦下載完,渲染引擎就會中斷渲染,執(zhí)行這個腳本以后,再繼續(xù)渲染。一句話,defer 是“渲染完再執(zhí)行”,async 是“下載完就執(zhí)行”。另外,如果有多個 defer 腳本,會按照它們在頁面出現(xiàn)的順序加載,而多個 async 腳本是不能保證加載順序的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容