[科普]ES6一些不常見的小知識

寫做不易,未經做者容許禁止以任何形式轉載!
若是以爲文章不錯,歡迎關注、點贊和分享!
持續分享技術博文,關注微信公衆號 👉🏻 前端LeBron前端

WeakMap

前置知識[深刻淺出]JavaScript GC 垃圾回收機制 node

什麼是WeakMap?

WeakMap是key / value的組合,key只接受對象,不接受基本類型,value能夠爲任意類型。web

方法

  • set(key, value)

在WeakMap中設置一組關聯對象,返回WeakMap對象編程

  • get(key)

返回key的關聯對象,不存在時返回undefined數組

  • has(key)

根據是否有key關聯對象,放回一個Boolean值緩存

  • delete(key)

移除key的關聯對象,以後執行has(key)方法返回false微信

和Map有什麼區別?

  1. Map的key / value,均可以是任意類型
  • WeakMap的key只能是對象,value能夠是任意類型
const name = "LeBron";
const person = {
  name: "LeBron",
  age: 21,
};

let wk = new WeakMap();

wk.set(person, "nice");
console.log(wk.get(person)); // nice
wk.set(name, 1); // TypeError: Invalid value used as weak map key

let map = new Map();
map.set(name, "JS");
map.set(person, "nice");
console.log(map.get(name)); // JS
console.log(map.get(person)); // nice
複製代碼
  1. Map的key / value 是可遍歷的,由於它的 key / value 存放在一個數組中。
  • WeakMap不存在存放 key / value 的數組,因此不可遍歷。
const name = "LeBron";
const person = {
  name: "LeBron",
  age: 21,
};

let wk = new WeakMap();
wk.set(name, "JS");
wk.set(person, "nice");
console.log(wk.keys()); // TypeError: wk.keys is not a function
console.log(wk.values()); // TypeError: wk.values is not a function
console.log(wk.entries());  // TypeError: wk.entries is not a function

let map = new Map();
map.set(person, "nice");
console.log(map.keys()); // [Map Iterator] { 'LeBron', { name: 'LeBron', age: 21 } }
console.log(map.values()); // [Map Iterator] { 'JS', 'nice' }
console.log(map.entries()); // [Map Entries] {
                           // [ 'LeBron', 'JS' ],
                           // [ { name: 'LeBron', age: 21 }, 'nice' ]
                          // }
複製代碼
  1. Map對鍵進行強引用,即便鍵被標記爲null了,因爲Map的key / value數組還在引用,key不會被GC。
  • WeakMap對key進行弱引用,在key被標記爲null了之後。因爲是弱引用,也不存在key / value數組引用,不影響key的GC。markdown

  • 如下程序須要手動GC 啓動方法:node --expose-gc xxx數據結構

Mapapp

function memmorySizeLogger() {
  global.gc();
  const used = process.memoryUsage().heapUsed;
  console.log((used / 1024 / 1024).toFixed(2) + "M");
}

memmorySizeLogger();  // 1.79M

let person = {
  name: "LeBron",
  age: 21,
  tmp: new Array(5 * 1024 * 1024),
};

memmorySizeLogger(); // 41.96M

let map = new Map();
memmorySizeLogger(); // 41.96M

map.set(person, "nice");
memmorySizeLogger(); // 41.96M

person = null;
memmorySizeLogger();  // 41.96M person的內存沒有被回收
複製代碼

若是想在這種狀況下正常GC,標記爲null前需先執行map.delete(person)

WeakMap

function memmorySizeLogger() {
  global.gc();
  const used = process.memoryUsage().heapUsed;
  console.log((used / 1024 / 1024).toFixed(2) + "M");
}

memmorySizeLogger();  // 1.79M

let person = {
  name: "LeBron",
  age: 21,
  tmp: new Array(5 * 1024 * 1024),
};

memmorySizeLogger(); // 41.96M

let wk = new WeakMap();
memmorySizeLogger();  // 41.96M

wk.set(person, "nice");
memmorySizeLogger();  // 41.96M

person = null;
memmorySizeLogger();  // 1.96M person的內存被回收
複製代碼

Why WeakMap?

  • 在JS裏的Map API共用兩個數組(key、value),設置的key、value都會加到這兩個數組的末尾,並對key產生引用。當從map取值時,須要遍歷全部的key,而後經過索引從value數組中取出相應index的值。
    • 缺點一:

賦值、搜索都是O(n)複雜度

  • 缺點二:

使用Map容易出現內存泄漏,由於數組一直引用着每一個key和value,致使沒法正常GC。

  • WeakMap對key進行弱引用,不影響正常GC
    • key被GC後失效
  • 若是你要往對象上添加數據,又不想幹擾垃圾回收機制,就可使用 WeakMap
    • 若是須要遍歷 / 迭代,則須要使用Map

應用場景

保存DOM節點數據

let domData = new WeakMap();

let dom = document.getElementById("xxx");

const anyDomData = getDomData(dom);
domData.set(dom, anyDomData);
console.log(domData.get(dom)); 

dom.parentNode.removeChild(dom);
dom = null;
複製代碼

緩存相關數據

let cache = new WeakMap();

class HandleCache {
  get(key) {
    if (cache.has(key)) {
      return cache.get(key);
    } else {
      return undefined;
    }
  }
  set(key, value) {
    cache.set(key, value)
  }
  delete(key){
    cache.delete(key)
  }
}
複製代碼

封裝私有屬性

let privateData = new WeakMap();

class Person{
  constructor(name, age){
    privateData.set(this,{name, age});
  }
  getData(){
    return privateData.get(this);
  }
}
複製代碼

WeakSet

前置知識[深刻淺出]JavaScript GC 垃圾回收機制

什麼是WeakSet

WeakSet對象是一些對象值的集合,而且其中的每一個對象只能出現一次,在WeakSet集合中是惟一的

方法

  • add(value)

在該WeakSet對象中添加一個新的元素value

  • delete(value)

在該WeakSet對象中刪除value這個元素後,has方法會返回false。

  • has(value)

返回一個Boolean值,表示給定的value值是否存在這個WeakSet中

和Set有什麼區別

  1. Set的value能夠是任何值,WeakSet的值只能是對象
const name = "LeBron";
const age = 21;
const person = {
  name: "LeBron",
  age: 21,
};

const ws = new WeakSet();
const set = new Set();

set.add(name);
set.add(age);
set.add(person); 

ws.add(person);
ws.add(name); // TypeError: Invalid value used in weak set
ws.add(age);    // TypeError: Invalid value used in weak set
複製代碼
  1. Set是可遍歷的,WeakSet不可遍歷
  • Set存在一個數組存放value的值,引用原對象,故可遍歷
  • WeakSet不存這樣的數組,故不可遍歷
const name = "LeBron";
const age = 21;
const person = {
  name: "LeBron",
  age: 21,
};

const ws = new WeakSet();
const set = new Set();

set.add(name);
set.add(age);
set.add(person);
console.log(set.values()); // { 'LeBron', 21, { name: 'LeBron', age: 21 } }

ws.add(person);
ws.add(name); 
ws.add(age);   
console.log(set.values());  // TypeError: ws.values is not a function
複製代碼
  1. Set影響GC,而WeakSet不影響
  • 如下程序須要手動GC 啓動方法:node --expose-gc xxx

Set存在values數組,在原value指向null後,values數組仍對value的值存在強引用,影響正常GC

function memmorySizeLogger() {
  global.gc();
  const used = process.memoryUsage().heapUsed;
  console.log((used / 1024 / 1024).toFixed(2) + "M");
}

memmorySizeLogger(); // 1.79M

let person = {
  name: "LeBron",
  age: 21,
  tmp: new Array(5 * 1024 * 1024),
};

memmorySizeLogger(); // 41.96M

const set = new Set();
set.add(person);

memmorySizeLogger(); // 41.96M

person = null;

memmorySizeLogger();  // 41.96M
複製代碼

WeakSet不存在這樣的數組,故不影響正常GC

function memmorySizeLogger() {
  global.gc();
  const used = process.memoryUsage().heapUsed;
  console.log((used / 1024 / 1024).toFixed(2) + "M");
}

memmorySizeLogger(); // 1.79M

let person = {
  name: "LeBron",
  age: 21,
  tmp: new Array(5 * 1024 * 1024),
};

memmorySizeLogger(); // 41.96M

const ws = new WeakSet();
ws.add(person);

memmorySizeLogger();  // 41.96M

person = null;

memmorySizeLogger();  // 1.96M
複製代碼

應用場景

檢測循環引用

遞歸調用自身的函數須要一種經過跟蹤哪些對象已被處理,來應對循環數據結構的方法

// 對 傳入的subject對象 內部存儲的全部內容執行回調
function execRecursively(fn, subject, _refs = null){
        if(!_refs)
                _refs = new WeakSet();

        // 避免無限遞歸
        if(_refs.has(subject))
                return;

        fn(subject);
        if("object" === typeof subject){
                _refs.add(subject);
                for(let key in subject)
                        execRecursively(fn, subject[key], _refs);
        }
}

const foo = {
        foo: "Foo",
        bar: {
                bar: "Bar"
        }
};

foo.bar.baz = foo; // 循環引用!
execRecursively(obj => console.log(obj), foo);
複製代碼

Reflect

Reflect譯爲反射,是一個內置的新的全局對象,它提供攔截JavaScript操做的方法。這些方法與Proxy handler的方法相同。Reflect不是一個函數對象,是靜態的相似工具函數,相似Math,所以它是不可構造的

Reflect的靜態方法

具體用法參考:Reflect MDN文檔

  • Reflect.apply()

  • Reflect.construct()

  • Reflect.defineProperty()

  • Reflect.deleteProperty()

  • Reflect.get()

  • Reflect.getOwnPropertyDescriptor()

  • Reflect.getPrototypeOf()

  • Reflect.has()

  • Reflect.isExtensible()

  • Reflect.ownKeys()

  • Reflect.preventExtensions()

  • Reflect.set()

  • Reflect.setPrototypeOf()

這些方法與Proxy handler的方法的命名相同,其中的一些方法與Object的方法相同,儘管兩者之間存在着某些細微的差異

  • 有什麼不一樣?
    • Reflect的靜態方法進行相應操做會返回一個Boolean值
      • 操做成功返回true
      • 操做失敗返回false
    • 將常規的命令式操做轉換成了函數式操做,編程方式增長了元編程。
      • 例如delete、賦值、判斷等
    • 命令式操做失敗通常會報錯,而Reflect不會,返回一個Boolean值判斷是否成功。
  • 內置對象中已經存在了一些反射API,Reflect將他們聚合起來並進行了優化

什麼是元編程?

  • 元編程即對編程語言進行編程
  • 例如Proxy對象能夠進行代理,攔截get、set操做
  • 而在程序中獲取的是你編程後的值。
  • Reflect就是一種反射,調用的是處理事後的各內置對象上的方法
    • 因此各內置對象的方法改變後,Reflect調用的方法也是改變了的
    • 相似於封裝了一層

Reflect的優勢

  1. 優化命名空間

你會發現JS的內置反射方法散落在各處,Reflect將他們很好地組織了起來。

  1. 加強代碼的健壯性

使用Reflect進行操做不容易拋出異常、線程阻塞,使代碼更健壯地運行。

  1. 爲何不直接掛在Object上?
  • 反射的對象不只針對於Object,還可能針對函數
    • 例如apply,調用Object.apply(myFunc)仍是挺奇怪的
  • 用一個單一的對象保存內置方法可以保證JavaScript代碼其餘對象的純淨性
    • 這樣要優於直接反射掛載到構造函數或者原形上

    • 更優於直接使用全局變量,這樣JS關鍵字將愈來愈多。


相關文章
相關標籤/搜索