前端日拱一卒D10——ES6筆記之新特性篇

前言

餘爲前端菜鳥,感姿式水平匱乏,難觀前端之大局。遂決定循前端知識之脈絡,以興趣爲引,輔以幾分堅持,望於己能解惑致知、於同道能助力一二,豈不美哉。前端

本系列代碼及文檔均在 此處java

依然很忙,繼續啃老本。。。git

lesson1 Symbol

概述

  • javaScript第七種原始數據類型Symbolgithub

    let s = Symbol('foo')數組

    經過Symbol函數生成,每一個Symbol類型的變量值都獨一無二,做爲一種相似於字符串的數據結構,能夠避免變量名衝突數據結構

    Symbol函數接收一個參數用於描述該Symbol實例,不影響生成的Symbol實例的值app

    Symbol值不能與其餘類型進行運算(模板字符串中也不能夠),能夠顯示轉爲字符串和布爾值(String(), Boolean())less

  • Symbol做爲屬性名函數

    • 不能用.,由於.是去取字符串對應屬性名
    • 在對象中使用做爲屬性名時,需使用[s]不然也會被當作字符串
  • Symbol.for, Symbol.keyForui

    let s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"
    let s2 = Symbol("foo"); // 先搜索全局,已存在該key則返回已存在的
    Symbol.keyFor(s2) // undefined
    複製代碼

內置Symbol值

  • Symbol.hasInstance

    對象的Symbol.hasInstance屬性指向一個內部方法,其餘對象使用instanceOf判斷實例時,會調用這個內部方法

    class Even {
      static [Symbol.hasInstance](obj) {
        return Number(obj) % 2 === 0;
      }
    }
    1 instanceOf Even
    複製代碼
  • Symbol.isConcatSpreadable

    表示該對象用於Array.prototype.concat()時是否能夠展開,數組默承認展開,默認值爲undefined,對象默認不可展開

  • Symbol.species

    指向當前對象的構造函數,創造實例時會調用這個方法,即便用該屬性返回的函數做爲構造函數

    static get [Symbol.species]() {
      return this;
    }
    複製代碼
  • Symbol.match, Symbol.replace, Symbol.split, Symbol.search

  • Symbol.iterator

    指向該對象的默認遍歷器方法

    對象進行for...of循環時,會調用Symbol.iterator方法,返回該對象的默認遍歷器

    詳見後續章節

  • Symbol.toPrimitive

  • Symbol.toStringTag 指向一個方法,在該對象上調用Object.prototype.toString()時,若是該屬性存在,則他的返回值會出如今toString方法返回的字符串之中,好比[Object Array]

    新增內置對象舉個例子:JSON[Symbol.toStringTag]:'JSON'

lesson2 Set和Map

Set

基本

  • Set構造函數生成,成員值惟一,(判斷與===區別在於NaN),兩個空對象視爲不一樣

  • 實例屬性和方法

    • 屬性: Set.prototype.constructor, Set.prototype.size
    • 方法: add(value), delete(value), has(value), clear()
    Array.from能夠將Set轉爲數組
    // 數組去重
    function dedupe(array) {
      return Array.from(new Set(array));
      // return [...new Set(array)]
    }
    複製代碼
  • 遍歷

    • keys(), values(), entries(), forEach() 方法
    • 遍歷順序爲插入順序,或可用於設置指定順序的回調函數
    • Set的鍵名鍵值相同
    • Set默承認遍歷,默認遍歷器生成函數是values方法,這意味着,能夠省略values方法,直接用for...of循環遍歷 Set Set.prototype[Symbol.iterator] === Set.prototype.values
    • ...內部使用for ... of,故可使用[...Set],轉爲數組後能夠方便使用數組方法如map和filter

WeakSet

  • 成員只能爲對象
  • 弱引用,垃圾回收機制對對象引用計數時不考慮WeakSet中對對象的引用
  • new WeakSet() 能夠接收任何具備 Iterable 接口的對象做爲參數,但必須注意加入WeakSet的成員必須爲對象
  • WeakSet有如下三個方法:add(value), delete(value), has(value),沒有size屬性,不可遍歷(沒有forEach和clear方法)

Map

基本

  • 鍵值對的集合(Hash結構),鍵和對象不同,不侷限於字符串。

  • 任何具備 Iterator 接口、且每一個成員都是一個雙元素的數組的數據結構均可以做爲Map構造函數的參數

  • 只有對同一個對象的引用或者嚴格相等的簡單類型(包括NaN)纔會生成同樣的Map

  • 實例屬性和方法

    • 屬性: Map.prototype.constructor, Map.prototype.size
    • 方法: set(), get(), delete(value), has(value), clear()
  • 遍歷

    • 相似上述Set的遍歷
    • Map 結構的默認遍歷器接口(Symbol.iterator屬性),就是entries方法

與其餘數據結構轉換

  • 數組,對象,JSON互轉
    function strMapToObj(strMap) {
      let obj = Object.create(null);
      for (let [k,v] of strMap) {
        obj[k] = v;
      }
      return obj;
    }
    複製代碼

WeakMap

  • 鍵名只能爲對象

  • WeakMap的鍵名所指向的對象,不計入垃圾回收機制

  • WeakMap有如下三個方法:get, set, delete(value), has(value),沒有size屬性,不可遍歷(沒有forEach和clear方法)

lesson3 Proxy

觀察

  • 舉個栗子 當你爲對象a賦值a.b=c時,你但願在b屬性賦值時有一個範圍大小的校驗,超出範圍拋錯,這個時候咱們可能會想到重載set方法,好比:
    let a = {}
    Object.defineProperty(a, 'b', {
      set(x) {
        if (x>100) {
          throw new RangeError('invalid range')
        }
        this.b = x
      }
    })
    複製代碼
    動手之後發現一個問題...這樣會棧溢出,由於在set內再set了b的值,無限循環...變通一下:
    let a = {}
    Object.defineProperty(a, 'b', {
      get(x) {
        return this.c
      }
      set(x) {
        if (x>100) {
          throw new RangeError('invalid range')
        }
        this.c = x
      }
    })
    複製代碼
    然而總要這麼寫感受很麻煩,並且若是是對一類屬性進行操做時,重複寫很不必,換用Proxy寫法:
    let a = {}
    let handler = {
      set(obj, prop, value, receiver) {
        if (prop === 'b') {
          if (value>100) {
            throw new RangeError('invalid range')
          }
        }
        obj[prop] = value
      }
    }
    let proxy = new Proxy(a, handler)
    複製代碼
    看起來也舒服多了,並且能夠根據屬性名在set方法內作判斷,更可擴展

庖丁解牛

  • 代理proxy

    let target = {};
    let handler = {};
    let proxy = new Proxy(target, handler);
    // 將代理的全部內部方法轉發至目標
    proxy.a = 1 => target.a = 1;
    target.b = 4 => proxy.b = 4;
    target !== proxy
    target.__proto__ === proxy.__proto__
    // 應在代理對象上操做,代理才能生效
    handler = {get(){return 12}}
    target.v // undefined
    proxy.v // 12
    複製代碼
  • Proxy支持的攔截操做

    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
    deleteProperty(target, propKey) // delete proxy[propKey]
    ownKeys(target) // Object.getOwnPropertyNames(proxy), Object.getOwnPropertySymbols(proxy), Object.keys(proxy)
    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(...)
    construct(target, args) // new proxy(...args)
    複製代碼
  • 代理句柄handler 句柄對象的方法能夠複寫代理的內部方法,具體爲上述的14種。

    • 舉個🌰

      function Tree() {
        return new Proxy({}, handler);
      }
      var handler = {
        get: function (target, key, receiver) {
          if (!(key in target)) {
            target[key] = Tree();  // 自動建立一個子樹
          }
          return Reflect.get(target, key, receiver);
        }
      }
      var tree = new Tree()
      tree.branch1.branch2.twig = "green"
      複製代碼
    • 再來個🌰

      // 實現對in操做符隱藏屬性
      var handler = {
        has (target, key) {
          if (key[0] === '_') {
            return false;
          }
          return key in target;
        }
      };
      var target = { _prop: 'foo', prop: 'foo' };
      var proxy = new Proxy(target, handler);
      '_prop' in proxy // false
      複製代碼
    • 特別注意

      若是目標對象不可擴展或者目標對象的屬性不可寫或者不可配置時,代理不能生效,可能會報錯

      需注意一些特定的方法對返回值有要求,不如重寫isExtensible方法時,返回值與目標對象的isExtensible屬性應一致,不然會報錯

      利用代理重寫能夠作不少事情好比隱藏屬性、對某些屬性、操做符屏蔽、攔截內在方法而且加上本身想要的邏輯處理去獲得預期結果等

飯後甜點

  • Proxy.revocable

    返回一個對象,proxy屬性對應Proxy實例,revoke屬性爲revoke方法能夠取消Proxy實例

    ```js
    let {proxy, revoke} = Proxy.revocable(target, handler);
    proxy.foo = 1
    revoke()
    proxy.foo // TypeError: Revoked
    ```
    複製代碼
  • this問題

    • 代理之後目標對象內部的this指向的是Proxy實例而不是目標對象
    • 有時候可能由於this指向問題致使代理達不到預期效果
      // jane的name屬性實際存儲在外部的WeakMap對象的_name上,致使後續取不到值
      const _name = new WeakMap();
      class Person {
        constructor(name) {
          _name.set(this, name);
        }
        get name() {
          return _name.get(this);
        }
      }
      const jane = new Person('Jane');
      jane.name // 'Jane'
      const proxy = new Proxy(jane, {});
      proxy.name // undefined
      複製代碼
    • 某些原生對象的部分屬性須要this指向原生對象時才能獲取,如Date.getDate(),此時proxy get時須要注意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
      複製代碼

進階

  • TO BE CONTINUED!

lesson4 Reflect

初識

  • Reflect對象與Proxy對象同樣,是爲了操做對象而提供的新API,存在的緣由以下:
    • 將Object對象的一些內部方法添加到Reflect對象上,且之後的新方法都部署到Reflect對象上,完成分離
    • 讓對象操做變成函數行爲
    • 修改Object對象一些內部方法在出錯時的返回
    • Proxy覆寫對象方法時,提供一個Reflect對象用來獲取原始方法,以設置默認值,再此基礎上再作功能添加和修改

揭面

  • 靜態方法

    對應於Proxy可覆寫的方法,有13個靜態方法

  • 注意

    • Proxy和Reflect聯用的時候要當心,可能一個攔截會觸發另外一個攔截
    let p = {
      a: 'a'
    };
    let handler = {
      set(target, key, value, receiver) {
        console.log('set');
        Reflect.set(target, key, value, receiver)
      },
      defineProperty(target, key, attribute) {
        console.log('defineProperty');
        Reflect.defineProperty(target, key, attribute);
      }
    };
    let obj = new Proxy(p, handler);
    obj.a = 'A';
    // set
    // defineProperty
    複製代碼
    在Reflect.set傳入receiver的時候觸發了Proxy.defineProperty,不傳入receiver時不會觸發defineProperty攔截
    複製代碼
    • 對於參數的要求、轉換和報錯處理

🌰

  • 使用Proxy實現觀察者模式
    const person = observable({
      name: '張三',
      age: 20
    });
    function print() {
      console.log(`${person.name}, ${person.age}`)
    }
    observe(print);
    person.name = '李四';
    // 輸出
    // 李四, 20
    /**************************/
    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;
    }
    複製代碼

lesson5 遍歷器Iterator

碰見

  • why Iterator

    js中數據集合的概念愈來愈多,若是能有一種統一的訪問方式將是極好的。Iterator的設計就基於此,經過爲相應數據結構部署iterator接口讓該數據結構可經過統一的方式:for...of遍歷

  • 遍歷過程:

    • 建立一個指針對象指向當前數據結構的初始位置(遍歷器對象實際爲一個指針對象)
    • 調用指針對象的next方法,直到指向數據結構的結束位置
    // 遍歷器生成函數
    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length ?
            {value: array[nextIndex++]} :
            {done: true};
        }
      };
    }
    複製代碼
  • 一種數據結構,只要部署了Iterator接口,就視爲可遍歷的

相識

默認Iterator接口

  • 默認的Iterator接口部署在[Symbol.iterator]屬性上,Symbol.iterator屬性鍵爲Symbol對象,值爲一個函數,即遍歷器生成函數,執行該函數會返回一個遍歷器對象,該對象具備一個next方法,調用該方法能夠返回{value, done}對象,表明了當前成員的信息
  • 部分數據結構如Array、Set、Map、String等已經部署了Iterator接口,對象則須要手動添加這樣的方法實現Iterator接口
  • 對於非線性數據結構,Iterator接口實際上就是一種線性轉換,下例爲class實現遍歷器
    class RangeIterator {
      constructor(start, stop) {
        this.value = start;
        this.stop = stop;
      }
      [Symbol.iterator]() { return this; }
      next() {
        var value = this.value;
        if (value < this.stop) {
          this.value++;
          return {done: false, value: value};
        }
        return {done: true, value: undefined};
      }
    }
    function range(start, stop) {
      return new RangeIterator(start, stop);
    }
    for (var value of range(0, 3)) {
      console.log(value); // 0, 1, 2
    }
    複製代碼

舉個🌰

  • 實現指針

    function Node(value) {
      this.value = value;
      this.next = null
    }
    // for...of時會調用改遍歷器生成函數
    Node.prototype[Symbol.iterator]= function() {
      // 返回的遍歷器對象
      var iterator = {
        next: next
      }
      // 當前成員
      var current = this
      next() {
        if(current) {
          var value = current.value;
          // 移動指針
          current = current.next;
          return { done: false, value: value };
        }
        return { done: true, value: undefined };
      }
      return iterator
    }
    // 新建對象,由於在原型上實現的遍歷器生成函數,因此每一個實例都實現了遍歷器接口
    var one = new Node(1);
    var two = new Node(2);
    var three = new Node(3);
    // 當前成員的next指向下一個成員,在next方法中實現指針移動
    one.next = two;
    two.next = three;
    // 對對象使用for...of時,去查找[Symbol.iterator]屬性,找到後循環調用next方法,直到返回值得done屬性爲true
    for (var i of one){
      console.log(i); // 1, 2, 3
    }
    複製代碼
  • 若是Symbol.iterator方法對應的不是遍歷器生成函數,則會報錯

在何方

調用場合

  • 解構賦值

  • 擴展運算符

    任何實現了Iterator接口(可遍歷)的數據結構均可以經過...將其轉化爲數組

  • yield*後跟一個可遍歷數據結構時,會調用該結構的遍歷器接口

  • for...of, Array.from, Map, Set, Promise.all(), Promise.race()

字符串、數組等的遍歷器

  • 字符串

    for...of可以正確識別32位UTF-16字符

    var someString = "hi";
    typeof someString[Symbol.iterator]
    // "function"
    var iterator = someString[Symbol.iterator]();
    // 固然你也能夠修改Symbol.iterator方法達到你想要的遍歷結果
    iterator.next()  // { value: "h", done: false }
    iterator.next()  // { value: "i", done: false }
    iterator.next()  // { value: undefined, done: true }
    複製代碼
  • return, throw方法

    這兩個方法都是在設置遍歷器生成函數時可選的,通常配合generator使用,因此下次再說

  • 數組

    • for ... of 只返回具備數字索引的鍵值的值
    • 類數組對象可使用數組的默認遍歷生成器達到遍歷效果(要求是數字索引以及具備length屬性)
  • Map, Set

    // Map遍歷返回的是數組[k, v],Set返回的是值
    for (let [key, value] of map) {
      console.log(key + ' : ' + value);
    }
    複製代碼
  • 類數組對象

    利用Array.from將其轉化爲數組,再使用數組的遍歷器接口用for...of實現遍歷

for ... of 注意

  • 能夠結合break、continue、return使用
  • 提供了多種數據結構的統一訪問方式
  • 相比for ... in,後者遍歷的是鍵,且鍵名爲字符串,還可能會遍歷原型上的鍵

雖發表於此,卻畢竟爲一人之言,又是每日學有所得之筆記,內容未必詳實,看官老爺們還望海涵。

相關文章
相關標籤/搜索