[科普]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关键字将愈来愈多。


相关文章
相关标签/搜索