2016-02-11 10:44:24 +00:00
|
|
|
/**
|
2016-11-21 01:49:21 +00:00
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
2016-02-11 10:44:24 +00:00
|
|
|
*
|
2018-02-17 02:24:55 +00:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2016-02-11 10:44:24 +00:00
|
|
|
*
|
2018-05-11 02:06:46 +00:00
|
|
|
* @format
|
2016-02-11 10:44:24 +00:00
|
|
|
* @preventMunge
|
|
|
|
* @typechecks
|
|
|
|
*/
|
|
|
|
|
2016-11-21 01:49:21 +00:00
|
|
|
/* eslint-disable no-extend-native, no-shadow-restricted-names */
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
2018-05-10 22:44:52 +00:00
|
|
|
const _shouldPolyfillES6Collection = require('_shouldPolyfillES6Collection');
|
|
|
|
const guid = require('guid');
|
|
|
|
const isNode = require('fbjs/lib/isNode');
|
|
|
|
const toIterator = require('toIterator');
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
module.exports = (function(global, undefined) {
|
|
|
|
// Since our implementation is spec-compliant for the most part we can safely
|
|
|
|
// delegate to a built-in version if exists and is implemented correctly.
|
|
|
|
// Firefox had gotten a few implementation details wrong across different
|
|
|
|
// versions so we guard against that.
|
|
|
|
if (!_shouldPolyfillES6Collection('Map')) {
|
|
|
|
return global.Map;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* == ES6 Map Collection ==
|
|
|
|
*
|
|
|
|
* This module is meant to implement a Map collection as described in chapter
|
|
|
|
* 23.1 of the ES6 specification.
|
|
|
|
*
|
|
|
|
* Map objects are collections of key/value pairs where both the keys and
|
|
|
|
* values may be arbitrary ECMAScript language values. A distinct key value
|
|
|
|
* may only occur in one key/value pair within the Map's collection.
|
|
|
|
*
|
|
|
|
* https://people.mozilla.org/~jorendorff/es6-draft.html#sec-map-objects
|
|
|
|
*
|
|
|
|
* There only two -- rather small -- diviations from the spec:
|
|
|
|
*
|
|
|
|
* 1. The use of frozen objects as keys.
|
|
|
|
* We decided not to allow and simply throw an error. The reason being is
|
|
|
|
* we store a "hash" on the object for fast access to it's place in the
|
|
|
|
* internal map entries.
|
|
|
|
* If this turns out to be a popular use case it's possible to implement by
|
|
|
|
* overiding `Object.freeze` to store a "hash" property on the object
|
|
|
|
* for later use with the map.
|
|
|
|
*
|
|
|
|
* 2. The `size` property on a map object is a regular property and not a
|
|
|
|
* computed property on the prototype as described by the spec.
|
|
|
|
* The reason being is that we simply want to support ES3 environments
|
|
|
|
* which doesn't implement computed properties.
|
|
|
|
*
|
|
|
|
* == Usage ==
|
|
|
|
*
|
|
|
|
* var map = new Map(iterable);
|
|
|
|
*
|
|
|
|
* map.set(key, value);
|
|
|
|
* map.get(key); // value
|
|
|
|
* map.has(key); // true
|
|
|
|
* map.delete(key); // true
|
|
|
|
*
|
|
|
|
* var iterator = map.keys();
|
|
|
|
* iterator.next(); // {value: key, done: false}
|
|
|
|
*
|
|
|
|
* var iterator = map.values();
|
|
|
|
* iterator.next(); // {value: value, done: false}
|
|
|
|
*
|
|
|
|
* var iterator = map.entries();
|
|
|
|
* iterator.next(); // {value: [key, value], done: false}
|
|
|
|
*
|
|
|
|
* map.forEach(function(value, key){ this === thisArg }, thisArg);
|
|
|
|
*
|
|
|
|
* map.clear(); // resets map.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constants
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Kinds of map iterations 23.1.5.3
|
2018-05-10 22:44:52 +00:00
|
|
|
const KIND_KEY = 'key';
|
|
|
|
const KIND_VALUE = 'value';
|
|
|
|
const KIND_KEY_VALUE = 'key+value';
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
// In older browsers we can't create a null-prototype object so we have to
|
|
|
|
// defend against key collisions with built-in methods.
|
2018-05-10 22:44:52 +00:00
|
|
|
const KEY_PREFIX = '$map_';
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
// This property will be used as the internal size variable to disallow
|
|
|
|
// writing and to issue warnings for writings in development.
|
2018-05-10 22:44:52 +00:00
|
|
|
let SECRET_SIZE_PROP;
|
2016-02-11 10:44:24 +00:00
|
|
|
if (__DEV__) {
|
|
|
|
SECRET_SIZE_PROP = '$size' + guid();
|
|
|
|
}
|
|
|
|
|
|
|
|
// In oldIE we use the DOM Node `uniqueID` property to get create the hash.
|
2018-05-10 22:44:52 +00:00
|
|
|
const OLD_IE_HASH_PREFIX = 'IE_HASH_';
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
class Map {
|
|
|
|
/**
|
|
|
|
* 23.1.1.1
|
|
|
|
* Takes an `iterable` which is basically any object that implements a
|
|
|
|
* Symbol.iterator (@@iterator) method. The iterable is expected to be a
|
|
|
|
* collection of pairs. Each pair is a key/value pair that will be used
|
|
|
|
* to instantiate the map.
|
|
|
|
*
|
|
|
|
* @param {*} iterable
|
|
|
|
*/
|
|
|
|
constructor(iterable) {
|
|
|
|
if (!isObject(this)) {
|
|
|
|
throw new TypeError('Wrong map object type.');
|
|
|
|
}
|
|
|
|
|
|
|
|
initMap(this);
|
|
|
|
|
|
|
|
if (iterable != null) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const it = toIterator(iterable);
|
|
|
|
let next;
|
2016-02-11 10:44:24 +00:00
|
|
|
while (!(next = it.next()).done) {
|
|
|
|
if (!isObject(next.value)) {
|
|
|
|
throw new TypeError('Expected iterable items to be pair objects.');
|
|
|
|
}
|
|
|
|
this.set(next.value[0], next.value[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.1
|
|
|
|
* Clears the map from all keys and values.
|
|
|
|
*/
|
|
|
|
clear() {
|
|
|
|
initMap(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.7
|
|
|
|
* Check if a key exists in the collection.
|
|
|
|
*
|
|
|
|
* @param {*} key
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
has(key) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const index = getIndex(this, key);
|
2016-02-11 10:44:24 +00:00
|
|
|
return !!(index != null && this._mapData[index]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.9
|
|
|
|
* Adds a key/value pair to the collection.
|
|
|
|
*
|
|
|
|
* @param {*} key
|
|
|
|
* @param {*} value
|
|
|
|
* @return {map}
|
|
|
|
*/
|
|
|
|
set(key, value) {
|
2018-05-10 22:44:52 +00:00
|
|
|
let index = getIndex(this, key);
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
if (index != null && this._mapData[index]) {
|
|
|
|
this._mapData[index][1] = value;
|
|
|
|
} else {
|
2018-05-11 02:06:46 +00:00
|
|
|
index = this._mapData.push([key, value]) - 1;
|
2016-02-11 10:44:24 +00:00
|
|
|
setIndex(this, key, index);
|
|
|
|
if (__DEV__) {
|
|
|
|
this[SECRET_SIZE_PROP] += 1;
|
|
|
|
} else {
|
|
|
|
this.size += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.6
|
|
|
|
* Gets a value associated with a key in the collection.
|
|
|
|
*
|
|
|
|
* @param {*} key
|
|
|
|
* @return {*}
|
|
|
|
*/
|
|
|
|
get(key) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const index = getIndex(this, key);
|
2016-02-11 10:44:24 +00:00
|
|
|
if (index == null) {
|
|
|
|
return undefined;
|
|
|
|
} else {
|
|
|
|
return this._mapData[index][1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.3
|
|
|
|
* Delete a key/value from the collection.
|
|
|
|
*
|
|
|
|
* @param {*} key
|
|
|
|
* @return {boolean} Whether the key was found and deleted.
|
|
|
|
*/
|
|
|
|
delete(key) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const index = getIndex(this, key);
|
2016-02-11 10:44:24 +00:00
|
|
|
if (index != null && this._mapData[index]) {
|
|
|
|
setIndex(this, key, undefined);
|
|
|
|
this._mapData[index] = undefined;
|
|
|
|
if (__DEV__) {
|
|
|
|
this[SECRET_SIZE_PROP] -= 1;
|
|
|
|
} else {
|
|
|
|
this.size -= 1;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.4
|
|
|
|
* Returns an iterator over the key/value pairs (in the form of an Array) in
|
|
|
|
* the collection.
|
|
|
|
*
|
|
|
|
* @return {MapIterator}
|
|
|
|
*/
|
|
|
|
entries() {
|
|
|
|
return new MapIterator(this, KIND_KEY_VALUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.8
|
|
|
|
* Returns an iterator over the keys in the collection.
|
|
|
|
*
|
|
|
|
* @return {MapIterator}
|
|
|
|
*/
|
|
|
|
keys() {
|
|
|
|
return new MapIterator(this, KIND_KEY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.11
|
|
|
|
* Returns an iterator over the values pairs in the collection.
|
|
|
|
*
|
|
|
|
* @return {MapIterator}
|
|
|
|
*/
|
|
|
|
values() {
|
|
|
|
return new MapIterator(this, KIND_VALUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.3.5
|
|
|
|
* Iterates over the key/value pairs in the collection calling `callback`
|
|
|
|
* with [value, key, map]. An optional `thisArg` can be passed to set the
|
|
|
|
* context when `callback` is called.
|
|
|
|
*
|
|
|
|
* @param {function} callback
|
|
|
|
* @param {?object} thisArg
|
|
|
|
*/
|
|
|
|
forEach(callback, thisArg) {
|
|
|
|
if (typeof callback !== 'function') {
|
|
|
|
throw new TypeError('Callback must be callable.');
|
|
|
|
}
|
|
|
|
|
2018-05-10 22:44:52 +00:00
|
|
|
const boundCallback = callback.bind(thisArg || undefined);
|
|
|
|
const mapData = this._mapData;
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
// Note that `mapData.length` should be computed on each iteration to
|
|
|
|
// support iterating over new items in the map that were added after the
|
|
|
|
// start of the iteration.
|
2018-05-10 22:44:52 +00:00
|
|
|
for (let i = 0; i < mapData.length; i++) {
|
|
|
|
const entry = mapData[i];
|
2016-02-11 10:44:24 +00:00
|
|
|
if (entry != null) {
|
|
|
|
boundCallback(entry[1], entry[0], this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 23.1.3.12
|
|
|
|
Map.prototype[toIterator.ITERATOR_SYMBOL] = Map.prototype.entries;
|
|
|
|
|
|
|
|
class MapIterator {
|
|
|
|
/**
|
|
|
|
* 23.1.5.1
|
|
|
|
* Create a `MapIterator` for a given `map`. While this class is private it
|
|
|
|
* will create objects that will be passed around publicily.
|
|
|
|
*
|
|
|
|
* @param {map} map
|
|
|
|
* @param {string} kind
|
|
|
|
*/
|
|
|
|
constructor(map, kind) {
|
2016-11-21 01:49:21 +00:00
|
|
|
if (!(isObject(map) && map._mapData)) {
|
2016-02-11 10:44:24 +00:00
|
|
|
throw new TypeError('Object is not a map.');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([KIND_KEY, KIND_KEY_VALUE, KIND_VALUE].indexOf(kind) === -1) {
|
|
|
|
throw new Error('Invalid iteration kind.');
|
|
|
|
}
|
|
|
|
|
|
|
|
this._map = map;
|
|
|
|
this._nextIndex = 0;
|
|
|
|
this._kind = kind;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 23.1.5.2.1
|
|
|
|
* Get the next iteration.
|
|
|
|
*
|
|
|
|
* @return {object}
|
|
|
|
*/
|
|
|
|
next() {
|
|
|
|
if (!this instanceof Map) {
|
|
|
|
throw new TypeError('Expected to be called on a MapIterator.');
|
|
|
|
}
|
|
|
|
|
2018-05-10 22:44:52 +00:00
|
|
|
const map = this._map;
|
|
|
|
let index = this._nextIndex;
|
|
|
|
const kind = this._kind;
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
if (map == null) {
|
|
|
|
return createIterResultObject(undefined, true);
|
|
|
|
}
|
|
|
|
|
2018-05-10 22:44:52 +00:00
|
|
|
const entries = map._mapData;
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
while (index < entries.length) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const record = entries[index];
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
index += 1;
|
|
|
|
this._nextIndex = index;
|
|
|
|
|
|
|
|
if (record) {
|
|
|
|
if (kind === KIND_KEY) {
|
|
|
|
return createIterResultObject(record[0], false);
|
|
|
|
} else if (kind === KIND_VALUE) {
|
|
|
|
return createIterResultObject(record[1], false);
|
|
|
|
} else if (kind) {
|
|
|
|
return createIterResultObject(record, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._map = undefined;
|
|
|
|
|
|
|
|
return createIterResultObject(undefined, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We can put this in the class definition once we have computed props
|
|
|
|
// transform.
|
|
|
|
// 23.1.5.2.2
|
|
|
|
MapIterator.prototype[toIterator.ITERATOR_SYMBOL] = function() {
|
|
|
|
return this;
|
2016-11-21 01:49:21 +00:00
|
|
|
};
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper Functions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return an index to map.[[MapData]] array for a given Key.
|
|
|
|
*
|
|
|
|
* @param {map} map
|
|
|
|
* @param {*} key
|
|
|
|
* @return {?number}
|
|
|
|
*/
|
|
|
|
function getIndex(map, key) {
|
|
|
|
if (isObject(key)) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const hash = getHash(key);
|
2016-02-11 10:44:24 +00:00
|
|
|
return map._objectIndex[hash];
|
|
|
|
} else {
|
2018-05-10 22:44:52 +00:00
|
|
|
const prefixedKey = KEY_PREFIX + key;
|
2016-02-11 10:44:24 +00:00
|
|
|
if (typeof key === 'string') {
|
|
|
|
return map._stringIndex[prefixedKey];
|
|
|
|
} else {
|
|
|
|
return map._otherIndex[prefixedKey];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Setup an index that refer to the key's location in map.[[MapData]].
|
|
|
|
*
|
|
|
|
* @param {map} map
|
|
|
|
* @param {*} key
|
|
|
|
*/
|
|
|
|
function setIndex(map, key, index) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const shouldDelete = index == null;
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
if (isObject(key)) {
|
2018-05-10 22:44:52 +00:00
|
|
|
const hash = getHash(key);
|
2016-02-11 10:44:24 +00:00
|
|
|
if (shouldDelete) {
|
|
|
|
delete map._objectIndex[hash];
|
|
|
|
} else {
|
|
|
|
map._objectIndex[hash] = index;
|
|
|
|
}
|
|
|
|
} else {
|
2018-05-10 22:44:52 +00:00
|
|
|
const prefixedKey = KEY_PREFIX + key;
|
2016-02-11 10:44:24 +00:00
|
|
|
if (typeof key === 'string') {
|
|
|
|
if (shouldDelete) {
|
|
|
|
delete map._stringIndex[prefixedKey];
|
|
|
|
} else {
|
|
|
|
map._stringIndex[prefixedKey] = index;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (shouldDelete) {
|
|
|
|
delete map._otherIndex[prefixedKey];
|
|
|
|
} else {
|
|
|
|
map._otherIndex[prefixedKey] = index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Instantiate a map with internal slots.
|
|
|
|
*
|
|
|
|
* @param {map} map
|
|
|
|
*/
|
|
|
|
function initMap(map) {
|
|
|
|
// Data structure design inspired by Traceur's Map implementation.
|
|
|
|
// We maintain an internal array for all the entries. The array is needed
|
|
|
|
// to remember order. However, to have a reasonable HashMap performance
|
|
|
|
// i.e. O(1) for insertion, deletion, and retrieval. We maintain indices
|
|
|
|
// in objects for fast look ups. Indices are split up according to data
|
|
|
|
// types to avoid collisions.
|
|
|
|
map._mapData = [];
|
|
|
|
|
|
|
|
// Object index maps from an object "hash" to index. The hash being a unique
|
|
|
|
// property of our choosing that we associate with the object. Association
|
|
|
|
// is done by ways of keeping a non-enumerable property on the object.
|
|
|
|
// Ideally these would be `Object.create(null)` objects but since we're
|
2018-01-13 06:03:51 +00:00
|
|
|
// trying to support ES3 we'll have to guard against collisions using
|
2016-02-11 10:44:24 +00:00
|
|
|
// prefixes on the keys rather than rely on null prototype objects.
|
|
|
|
map._objectIndex = {};
|
|
|
|
|
|
|
|
// String index maps from strings to index.
|
|
|
|
map._stringIndex = {};
|
|
|
|
|
|
|
|
// Numbers, booleans, undefined, and null.
|
|
|
|
map._otherIndex = {};
|
|
|
|
|
|
|
|
// Unfortunately we have to support ES3 and cannot have `Map.prototype.size`
|
|
|
|
// be a getter method but just a regular method. The biggest problem with
|
|
|
|
// this is safety. Clients can change the size property easily and possibly
|
|
|
|
// without noticing (e.g. `if (map.size = 1) {..}` kind of typo). What we
|
|
|
|
// can do to mitigate use getters and setters in development to disallow
|
|
|
|
// and issue a warning for changing the `size` property.
|
|
|
|
if (__DEV__) {
|
|
|
|
if (isES5) {
|
|
|
|
// If the `SECRET_SIZE_PROP` property is already defined then we're not
|
|
|
|
// in the first call to `initMap` (e.g. coming from `map.clear()`) so
|
|
|
|
// all we need to do is reset the size without defining the properties.
|
|
|
|
if (map.hasOwnProperty(SECRET_SIZE_PROP)) {
|
|
|
|
map[SECRET_SIZE_PROP] = 0;
|
|
|
|
} else {
|
|
|
|
Object.defineProperty(map, SECRET_SIZE_PROP, {
|
|
|
|
value: 0,
|
2018-05-11 02:06:46 +00:00
|
|
|
writable: true,
|
2016-02-11 10:44:24 +00:00
|
|
|
});
|
|
|
|
Object.defineProperty(map, 'size', {
|
2018-05-11 02:06:46 +00:00
|
|
|
set: v => {
|
2016-02-11 10:44:24 +00:00
|
|
|
console.error(
|
|
|
|
'PLEASE FIX ME: You are changing the map size property which ' +
|
2018-05-11 02:06:46 +00:00
|
|
|
'should not be writable and will break in production.',
|
2016-02-11 10:44:24 +00:00
|
|
|
);
|
|
|
|
throw new Error('The map size property is not writable.');
|
|
|
|
},
|
2018-05-11 02:06:46 +00:00
|
|
|
get: () => map[SECRET_SIZE_PROP],
|
2016-02-11 10:44:24 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: Early return to implement immutable `.size` in DEV.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is a diviation from the spec. `size` should be a getter on
|
|
|
|
// `Map.prototype`. However, we have to support IE8.
|
|
|
|
map.size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if something is an object.
|
|
|
|
*
|
|
|
|
* @param {*} o
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
function isObject(o) {
|
|
|
|
return o != null && (typeof o === 'object' || typeof o === 'function');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an iteration object.
|
|
|
|
*
|
|
|
|
* @param {*} value
|
|
|
|
* @param {boolean} done
|
|
|
|
* @return {object}
|
|
|
|
*/
|
|
|
|
function createIterResultObject(value, done) {
|
|
|
|
return {value, done};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Are we in a legit ES5 environment. Spoiler alert: that doesn't include IE8.
|
2018-05-10 22:44:52 +00:00
|
|
|
const isES5 = (function() {
|
2016-02-11 10:44:24 +00:00
|
|
|
try {
|
|
|
|
Object.defineProperty({}, 'x', {});
|
|
|
|
return true;
|
2016-11-21 01:49:21 +00:00
|
|
|
} catch (e) {
|
2016-02-11 10:44:24 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
})();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if an object can be extended.
|
|
|
|
*
|
|
|
|
* @param {object|array|function|regexp} o
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
function isExtensible(o) {
|
|
|
|
if (!isES5) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return Object.isExtensible(o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* IE has a `uniqueID` set on every DOM node. So we construct the hash from
|
|
|
|
* this uniqueID to avoid memory leaks and the IE cloneNode bug where it
|
|
|
|
* clones properties in addition to the attributes.
|
|
|
|
*
|
|
|
|
* @param {object} node
|
|
|
|
* @return {?string}
|
|
|
|
*/
|
|
|
|
function getIENodeHash(node) {
|
2018-05-10 22:44:52 +00:00
|
|
|
let uniqueID;
|
2016-02-11 10:44:24 +00:00
|
|
|
switch (node.nodeType) {
|
|
|
|
case 1: // Element
|
|
|
|
uniqueID = node.uniqueID;
|
|
|
|
break;
|
|
|
|
case 9: // Document
|
|
|
|
uniqueID = node.documentElement.uniqueID;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uniqueID) {
|
2018-05-11 02:06:46 +00:00
|
|
|
return OLD_IE_HASH_PREFIX + uniqueID;
|
2016-02-11 10:44:24 +00:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-10 22:44:52 +00:00
|
|
|
const getHash = (function() {
|
|
|
|
const propIsEnumerable = Object.prototype.propertyIsEnumerable;
|
|
|
|
const hashProperty = guid();
|
|
|
|
let hashCounter = 0;
|
2016-02-11 10:44:24 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the "hash" associated with an object.
|
|
|
|
*
|
|
|
|
* @param {object|array|function|regexp} o
|
|
|
|
* @return {number}
|
|
|
|
*/
|
2018-05-11 02:06:46 +00:00
|
|
|
return function getHash(o) {
|
|
|
|
// eslint-disable-line no-shadow
|
2016-02-11 10:44:24 +00:00
|
|
|
if (o[hashProperty]) {
|
|
|
|
return o[hashProperty];
|
2018-05-11 02:06:46 +00:00
|
|
|
} else if (
|
|
|
|
!isES5 &&
|
|
|
|
o.propertyIsEnumerable &&
|
|
|
|
o.propertyIsEnumerable[hashProperty]
|
|
|
|
) {
|
2016-02-11 10:44:24 +00:00
|
|
|
return o.propertyIsEnumerable[hashProperty];
|
2018-05-11 02:06:46 +00:00
|
|
|
} else if (!isES5 && isNode(o) && getIENodeHash(o)) {
|
2016-02-11 10:44:24 +00:00
|
|
|
return getIENodeHash(o);
|
|
|
|
} else if (!isES5 && o[hashProperty]) {
|
|
|
|
return o[hashProperty];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isExtensible(o)) {
|
|
|
|
hashCounter += 1;
|
|
|
|
if (isES5) {
|
|
|
|
Object.defineProperty(o, hashProperty, {
|
|
|
|
enumerable: false,
|
|
|
|
writable: false,
|
|
|
|
configurable: false,
|
2018-05-11 02:06:46 +00:00
|
|
|
value: hashCounter,
|
2016-02-11 10:44:24 +00:00
|
|
|
});
|
|
|
|
} else if (o.propertyIsEnumerable) {
|
|
|
|
// Since we can't define a non-enumerable property on the object
|
|
|
|
// we'll hijack one of the less-used non-enumerable properties to
|
|
|
|
// save our hash on it. Addiotionally, since this is a function it
|
|
|
|
// will not show up in `JSON.stringify` which is what we want.
|
|
|
|
o.propertyIsEnumerable = function() {
|
|
|
|
return propIsEnumerable.apply(this, arguments);
|
|
|
|
};
|
|
|
|
o.propertyIsEnumerable[hashProperty] = hashCounter;
|
|
|
|
} else if (isNode(o)) {
|
|
|
|
// At this point we couldn't get the IE `uniqueID` to use as a hash
|
|
|
|
// and we couldn't use a non-enumerable property to exploit the
|
|
|
|
// dontEnum bug so we simply add the `hashProperty` on the node
|
|
|
|
// itself.
|
|
|
|
o[hashProperty] = hashCounter;
|
|
|
|
} else {
|
|
|
|
throw new Error('Unable to set a non-enumerable property on object.');
|
|
|
|
}
|
|
|
|
return hashCounter;
|
|
|
|
} else {
|
|
|
|
throw new Error('Non-extensible objects are not allowed as keys.');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
return Map;
|
2016-11-21 01:49:21 +00:00
|
|
|
})(Function('return this')()); // eslint-disable-line no-new-func
|