专栏首页前端小作坊ECMAScript 6之WeakMap

ECMAScript 6之WeakMap

ECMAScript 6之WeakMap

ECMAScript 6中加入了很多新的特性,其中有一个有用的API:WeakMap。Nicholas的博文做了详细的介绍。这也是一篇关于WeakMap的笔记。


简介

WeakMapMap(另一个ES6的新API)都是键值对象,有着类似的API:setgethasdelete。为了更好的解释WeakMap,先谈下Javascript中的键值对象。JS中几乎所有可写的对象就可以当作一个键值对象,例如:

var map = {};
map['key'] = 'value';

但是这样简单的键值对象达不到一个效果:它的键“不能”是对象。之所以打引号是因为并不是不能用对象,而是对象会转换成字符串,如下:

var map = {};
var key = {};
map[key] = 'value';
map['[object Object]'] === 'value'; // true

于是ES6就提供了MapWeakMap的API,可以实现键是对象的情况。但是这引来了另外一个问题,Map是可以通过keys方法来获取其所有的键。这就需要map保留一个对键的强引用,于是就导致了引用释放上的不方便,稍微大意一些就容易造成内存泄漏。

于是又提供了WeakMap,它的键只能是一个非空(null)对象,而Map的键则还可以是除对象外的原始类型。WeakMap的重要特点在于:对键的引用是弱引用,而不是一般的强引用。看如下例子:

var map = new WeakMap(),
element = document.querySelector(".element");

// 对象element作为键,"Original"作为值
map.set(element, "Original");

// 可以通过element获得值
var value = map.get(element);
console.log(value);             // "Original"

// 下面删除对element的强引用
element.parentNode.removeChild(element);
element = null;

// element dom对象被释放
// 已经无法通过element获取“Original”值

WeakMap的键只能是非空(null)对象,所以如果你在get方法里面传入null或是undefined之类的值,就会报错:“value is not a non-null object”。它的另外一个特点就是无法获取所有的键,更无法获取size之类的值。

WeakMap一个典型的应用场景就是对于jQuery这样的库,需要维护一个dom列表,存储对应每个dom的数据。这时候使用它就可以达到“dom在文档中被移除的时候,自动释放dom对象”。

当然ES6目前还没有普及,支持的浏览器仅有Firefox和Chrome。在chrome中你需要到chrome://flags,并且启用“Experimental JavaScript Features”。所以我们需要fallback。

Fallback

Gozala提供了一个很精妙的fallback方案!接下来对它的代码做解读。要实现WeakMap有几个关注点:

  1. 键一定是非空对象
  2. 键无法被获取到
  3. WeakMap不能保留对键的强引用

因为这个方案是基于ECMAScript 5的,所以使用了Object.freezeObject.create之类的API。WeakMap的初始化函数如下:

function WeakMap() {
  /**
  Implementation of harmony `WeakMaps`, in ES5\. This implementation will
  work only with keys that have configurable `valueOf` property (which is
  a default for all non-frozen objects).
  http://wiki.ecmascript.org/doku.php?id=harmony:weak_maps
  **/

  var privates = Name()
  return Object.freeze(Object.create(WeakMap.prototype, {
    has: {
      value: function has(object) {
        return 'value' in privates(object)
      },
      configurable: true,
      enumerable: false,
      writable: true
    },
    get: {
      value: function get(key, fallback) {
        return privates(guard(key)).value || fallback
      },
      configurable: true,
      enumerable: false,
      writable: true
    },
    set: {
      value: function set(key, value) {
        privates(guard(key)).value = value
      },
      configurable: true,
      enumerable: false,
      writable: true
    },
    'delete': {
      value: function set(key) {
        return delete privates(guard(key)).value
      },
      configurable: true,
      enumerable: false,
      writable: true
    }
  }))
}

使用了Object.create创建了一个继承自WeakMap的新对象,并设置了has,get,set,delete方法。(其实我不太理解delete是个关键字,为什么标准要把它作为WeakMap的方法名之一?)然后使用Object.freeze确保对象无法被改变。避免有人通过map.a = 'string'这样或其他方式去修改对象。

其中的关键点在于var privates = Name()一句。privates方法是用来“依据键获取键值”。

因为不能保存键的强引用所以需要将键值保存在键上。即WeakMap仅仅是提供了一个“键”与“值”之间的接口,通过WeakMap来设置或获取键对应值。这么一来就容易理解了,具体源代码如下:

function defineNamespace(object, namespace) {
  /**
  Utility function takes `object` and `namespace` and overrides `valueOf`
  method of `object`, so that when called with a `namespace` argument,
  `private` object associated with this `namespace` is returned. If argument
  is different, `valueOf` falls back to original `valueOf` property.
  **/

  // Private inherits from `object`, so that `this.foo` will refer to the
  // `object.foo`. Also, original `valueOf` is saved in order to be able to
  // delegate to it when necessary.
  var privates = Object.create(object), base = object.valueOf
  Object.defineProperty(object, 'valueOf', { value: function valueOf(value) {
    // If `this` or `namespace` is not associated with a `privates` being
    // stored we fallback to original `valueOf`, otherwise we return privates.
    return value != namespace || this != object ? base.apply(this, arguments)
                                              : privates
  }, configurable: true })
  return privates
}

function Name() {
  /**
  Desugared implementation of private names proposal. API is different as
  it's not possible to implement API proposed for harmony with in ES5\. In
  terms of provided functionality it supposed to be same.
  http://wiki.ecmascript.org/doku.php?id=strawman:private_names
  **/

  var namespace = {}
  return function name(object) {
    var privates = object.valueOf(namespace)
    return privates !== object ? privates : defineNamespace(object, namespace)
  }
}

function guard(key) {
  /**
  Utility function to guard WeakMap methods from keys that are not
  a non-null objects.
  **/

  if (key !== Object(key)) throw TypeError("value is not a non-null object")
  return key
}

代码中guard方法用于验证键是否是非空对象,又通过重写valueOf方法来获取键对应的存储值的对象。即是如下的引用关系:

key -> valueOf --(namespace)--> privates --> value

其中的关键点是namespace是一个空对象。为什么呢?因为一个空对象是无法模仿的、是唯一的,如果改用字符串则很容易被知道字符串值的人直接通过key.valueOf('namespace')方法获取到privates。

所以实现WeakMap的核心是“如何构造一个唯一的无法伪造的属性名”。

Nicolas还在Gozala方案的评论中提到了另外一种实现,具体见http://code.google.com/p/es-lab/source/browse/trunk/src/ses/WeakMap.js。我还没有仔细看~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 原型链上的DOM Attributes

    Chrome开发小组最近发表声明他们正在將DOM properties移动到原型链中。这个更新将会在Chrome 43(2015年4月发布beta版本)中实现。...

    mmzhou
  • CSS Counters

    CSS Counters是一个很有意思的特性,它配合 content 属性和伪元素可以实现自动编号的效果。它是CSS2.1提出的标准,主流浏览器对它的支持很好,...

    mmzhou
  • 中文排版二三事

    前段时间一直在折腾中文排版相关的事情,自认为结果还算不错。故开源之,即是Entry.css。这是一个可配置的、更适合阅读的中文文章样式库,可以用来快速搭建中文博...

    mmzhou
  • Facebook面试官:如何突围大厂算法面试?

    作为一名程序员,你肯定想过:编程最本质的知识是什么?很多人都会说是算法与数据结构。

    谭庆波
  • 【JavaWeb】97:Redis五大数据类型

    因为Redis是非关系型数据库,它是不支持sql语言的,所以其有特有的命令需要我们去学习。

    刘小爱
  • etl作业部署与调度——taskctl管理概述

    TASKCTL是一款功能全面的作业自动化调度技术管理工具。所谓作业,是指部署在网络中不同计算机上的各种程序或系统命令。通过TASKCTL,可以快速将这些作业组织...

    taskctl官方频道
  • 一文讲解单片机、ARM、MUC、DSP、FPGA、嵌入式错综复杂的关系!

    首先,“嵌入式”这是个概念,准确的定义没有,各个书上都有各自的定义。但是主要思想是一样的,就是相比较PC机这种通用系统来说,嵌入式系统是个专用系统,结构精简,在...

    单片机技术宅
  • 【ES6基础】迭代器(iterator)

    迭代器(iterator)是一个结构化的模式,用于从源以一次一个的方式提取数据。迭代器的使用可以极大地简化数据操作,于是ES6也向JS中添加了这个迭代器特性。新...

    前端达人
  • 美团点评春招实习面经

    牛客网
  • python3学习之元组

    T.index(value, [start, [stop]]) 返回值是出现value的第一个index

    py3study

扫码关注云+社区

领取腾讯云代金券