@s8/static-tm
Version:
Static module for Trinity Mirror partner.
256 lines (241 loc) • 8.85 kB
JavaScript
/**
* Copyright 2012 Eric Wendelin - MIT License
*
* es6-map-shim.js is a DESTRUCTIVE shim that follows the latest Map specification
* as closely as possible. It is destructive in the sense that it overrides native implementations.
*
* IMPORTANT: Currently, get(), set(), has() and delete() are all O(n) operations.
* Normally, they would be O(1). Therefore it is NOT recommended to use this with
* a large dataset or in any production environment.
*
* This library assumes ES5 functionality: Object.create, Object.defineProperty,
* Array.indexOf, Function.bind and others.
*/
(function(module) {
function Map(iterable) {
var _items = [];
var _keys = [];
var _values = [];
// Object.is polyfill, courtesy of @WebReflection
var is = Object.is || function(a, b) {
return a === b ?
a !== 0 || 1 / a == 1 / b :
a != a && b != b;
};
// More reliable indexOf, courtesy of @WebReflection
var betterIndexOf = function(value) {
if(value != value || value === 0) {
for(var i = this.length; i-- && !is(this[i], value););
} else {
i = [].indexOf.call(this, value);
}
return i;
};
/**
* MapIterator used for iterating over all entries in given map.
*
* @param map {Map} to iterate
* @param kind {String} identifying what to yield. Possible values
* are 'keys', 'values' and 'keys+values'
* @constructor
*/
var MapIterator = function MapIterator(map, kind) {
var _index = 0;
return Object.create({}, {
next: {
value: function() {
// check if index is within bounds
if (_index < map.items().length) {
switch(kind) {
case 'keys': return map.keys()[_index++];
case 'values': return map.values()[_index++];
case 'keys+values': return [].slice.call(map.items()[_index++]);
default: throw new TypeError('Invalid iterator type');
}
}
// TODO: make sure I'm interpreting the spec correctly here
throw new Error('Stop Iteration');
}
},
iterator: {
value: function() {
return this;
}
},
toString: {
value: function() {
return '[object Map Iterator]';
}
}
});
};
var _set = function(key, value) {
// check if key exists and overwrite
var index = betterIndexOf.call(_keys, key);
if (index > -1) {
_items[index][1] = value;
_values[index] = value;
} else {
_items.push([key, value]);
_keys.push(key);
_values.push(value);
}
};
var setItem = function(item) {
if (item.length !== 2) {
throw new TypeError('Invalid iterable passed to Map constructor');
}
_set(item[0], item[1]);
};
// FIXME: accommodate any class that defines an @@iterator method that returns
// an iterator object that produces two element array-like objects
if (Array.isArray(iterable)) {
iterable.forEach(setItem);
} else if (iterable !== undefined) {
throw new TypeError('Invalid Map');
}
return Object.create(MapPrototype, {
/**
* @return {Array} all entries in the Map, in order
*/
items:{
value:function() {
return [].slice.call(_items);
}
},
/**
* @return {Array} all keys in the Map, in order
*/
keys:{
value:function() {
return [].slice.call(_keys);
}
},
/**
* @return {Array} all values in the Map, in order
*/
values:{
value:function() {
return [].slice.call(_values);
}
},
/**
* Given a key, indicate whether that key exists in this Map.
*
* @param key {Object} expected key
* @return {Boolean} true if key in Map
*/
has:{
value:function(key) {
// TODO: double-check how spec reads about null values
var index = betterIndexOf.call(_keys, key);
return index > -1;
}
},
/**
* Given a key, retrieve the value associated with that key (or undefined).
*
* @param key {Object}
* @return {Object} value associated with key or undefined
*/
get:{
value:function(key) {
var index = betterIndexOf.call(_keys, key);
return index > -1 ? _values[index] : undefined;
}
},
/**
* Add or overwrite entry associating key with value. Always returns undefined.
*
* @param key {Object} anything
* @param value {Object} also anything
*/
set:{
value: _set
},
/**
* Return the number of entries in this Map.
*
* @return {Number} number of entries
*/
size:{
get:function() {
return _items.length;
}
},
/**
* Remove all entries in this Map. Returns undefined.
*/
clear:{
value:function() {
_keys.length = _values.length = _items.length = 0;
}
},
/**
* Delete entry with given key, if it exists.
*
* @param key {Object} any possible key
* @return {Boolean} true if an entry was deleted
*/
'delete':{
value:function(key) {
var index = betterIndexOf.call(_keys, key);
if (index > -1) {
_keys.splice(index, 1);
_values.splice(index, 1);
_items.splice(index, 1);
return true;
}
return false;
}
},
/**
* Given a callback function and optional context, invoke the callback on all
* entries in this Map.
*
* @param callbackFn {Function}
*/
forEach:{
value:function(callbackfn /*, thisArg*/) {
if (typeof callbackfn != 'function') {
throw new TypeError('Invalid callback function given to forEach');
}
function tryNext() {
try {
return iter.next();
} catch(e) {
return undefined;
}
}
var iter = this.iterator();
var current = tryNext();
var next = tryNext();
while(current !== undefined) {
callbackfn.apply(arguments[1], [current[1], current[0], this]);
current = next;
next = tryNext();
}
}
},
/**
* Return a MapIterator object for this map.
*/
iterator:{
value: function() {
return new MapIterator(this, 'keys+values');
}
},
toString:{
value: function() {
return '[Object Map]';
}
}
});
}
var notInNode = module == 'undefined';
var window = notInNode ? this : global;
var module = notInNode ? {} : exports;
var MapPrototype = Map.prototype;
Map.prototype = MapPrototype = Map();
window.Map = module.Map = window.Map || Map;
}.call(this, typeof exports));