Skip to content

Commit

Permalink
improve collections polyfills - O(1) and prevent possible leaking wit…
Browse files Browse the repository at this point in the history
…h frozen keys, correct observable state for object keys, close #134
  • Loading branch information
zloirock committed Nov 22, 2015
1 parent 693767b commit 7dd2b89
Show file tree
Hide file tree
Showing 20 changed files with 237 additions and 135 deletions.
22 changes: 2 additions & 20 deletions library/modules/_collection-strong.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,10 @@ var $ = require('./_')
, forOf = require('./_for-of')
, $iterDefine = require('./_iter-define')
, step = require('./_iter-step')
, ID = require('./_uid')('id')
, $has = require('./_has')
, isObject = require('./_is-object')
, setSpecies = require('./_set-species')
, DESCRIPTORS = require('./_descriptors')
, isExtensible = Object.isExtensible || isObject
, SIZE = DESCRIPTORS ? '_s' : 'size'
, id = 0;

var fastKey = function(it, create){
// return primitive with prefix
if(!isObject(it))return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it;
if(!$has(it, ID)){
// can't set id to frozen object
if(!isExtensible(it))return 'F';
// not necessary to add id
if(!create)return 'E';
// add missing object id
hide(it, ID, ++id);
// return object id with prefix
} return 'O' + it[ID];
};
, fastKey = require('./_meta').fastKey
, SIZE = DESCRIPTORS ? '_s' : 'size';

var getEntry = function(that, key){
// fast case
Expand Down
47 changes: 22 additions & 25 deletions library/modules/_collection-weak.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
'use strict';
var hide = require('./_hide')
, redefineAll = require('./_redefine-all')
var redefineAll = require('./_redefine-all')
, getWeak = require('./_meta').getWeak
, anObject = require('./_an-object')
, isObject = require('./_is-object')
, strictNew = require('./_strict-new')
, forOf = require('./_for-of')
, createArrayMethod = require('./_array-methods')
, $has = require('./_has')
, WEAK = require('./_uid')('weak')
, isExtensible = Object.isExtensible || isObject
, arrayFind = createArrayMethod(5)
, arrayFindIndex = createArrayMethod(6)
, id = 0;

// fallback for frozen keys
var frozenStore = function(that){
// fallback for uncaught frozen keys
var uncaughtFrozenStore = function(that){
return that._l || (that._l = new FrozenStore);
};
var FrozenStore = function(){
var UncaughtFrozenStore = function(){
this.a = [];
};
var findFrozen = function(store, key){
var findUncaughtFrozen = function(store, key){
return arrayFind(store.a, function(it){
return it[0] === key;
});
};
FrozenStore.prototype = {
UncaughtFrozenStore.prototype = {
get: function(key){
var entry = findFrozen(this, key);
var entry = findUncaughtFrozen(this, key);
if(entry)return entry[1];
},
has: function(key){
return !!findFrozen(this, key);
return !!findUncaughtFrozen(this, key);
},
set: function(key, value){
var entry = findFrozen(this, key);
var entry = findUncaughtFrozen(this, key);
if(entry)entry[1] = value;
else this.a.push([key, value]);
},
Expand All @@ -52,35 +50,34 @@ module.exports = {
var C = wrapper(function(that, iterable){
strictNew(that, C, NAME);
that._i = id++; // collection id
that._l = undefined; // leak store for frozen objects
that._l = undefined; // leak store for uncaught frozen objects
if(iterable != undefined)forOf(iterable, IS_MAP, that[ADDER], that);
});
redefineAll(C.prototype, {
// 23.3.3.2 WeakMap.prototype.delete(key)
// 23.4.3.3 WeakSet.prototype.delete(value)
'delete': function(key){
if(!isObject(key))return false;
if(!isExtensible(key))return frozenStore(this)['delete'](key);
return $has(key, WEAK) && $has(key[WEAK], this._i) && delete key[WEAK][this._i];
var data = getWeak(key);
if(data === true)return uncaughtFrozenStore(this)['delete'](key);
return data && $has(data, this._i) && delete data[this._i];
},
// 23.3.3.4 WeakMap.prototype.has(key)
// 23.4.3.4 WeakSet.prototype.has(value)
has: function has(key){
if(!isObject(key))return false;
if(!isExtensible(key))return frozenStore(this).has(key);
return $has(key, WEAK) && $has(key[WEAK], this._i);
var data = getWeak(key);
if(data === true)return uncaughtFrozenStore(this).has(key);
return data && $has(data, this._i);
}
});
return C;
},
def: function(that, key, value){
if(!isExtensible(anObject(key))){
frozenStore(that).set(key, value);
} else {
$has(key, WEAK) || hide(key, WEAK, {});
key[WEAK][that._i] = value;
} return that;
var data = getWeak(anObject(key), true);
if(data === true)uncaughtFrozenStore(that).set(key, value);
else data[that._i] = value;
return that;
},
frozenStore: frozenStore,
WEAK: WEAK
ufstore: uncaughtFrozenStore
};
2 changes: 2 additions & 0 deletions library/modules/_collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
var $ = require('./_')
, global = require('./_global')
, $export = require('./_export')
, meta = require('./_meta')
, fails = require('./_fails')
, hide = require('./_hide')
, redefineAll = require('./_redefine-all')
Expand All @@ -23,6 +24,7 @@ module.exports = function(NAME, wrapper, methods, common, IS_MAP, IS_WEAK){
// create collection constructor
C = common.getConstructor(wrapper, NAME, IS_MAP, ADDER);
redefineAll(C.prototype, methods);
meta.NEED = true;
} else {
C = wrapper(function(target, iterable){
strictNew(target, C, NAME);
Expand Down
53 changes: 53 additions & 0 deletions library/modules/_meta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
var META = require('./_uid')('meta')
, isObject = require('./_is-object')
, has = require('./_has')
, setDesc = require('./_').setDesc
, id = 0;
var isExtensible = Object.isExtensible || function(){
return true;
};
var FREEZE = !require('./_fails')(function(){
return isExtensible(Object.preventExtensions({}));
});
var setMeta = function(it){
setDesc(it, META, {value: {
i: 'O' + ++id, // object ID
w: {} // weak collections IDs
}});
};
var fastKey = function(it, create){
// return primitive with prefix
if(!isObject(it))return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it;
if(!has(it, META)){
// can't set metadata to uncaught frozen object
if(!isExtensible(it))return 'F';
// not necessary to add metadata
if(!create)return 'E';
// add missing metadata
setMeta(it);
// return object ID
} return it[META].i;
};
var getWeak = function(it, create){
if(!has(it, META)){
// can't set metadata to uncaught frozen object
if(!isExtensible(it))return true;
// not necessary to add metadata
if(!create)return false;
// add missing metadata
setMeta(it);
// return hash weak collections IDs
} return it[META].w;
};
// add metadata on freeze-family methods calling
var onFreeze = function(it){
if(FREEZE && meta.NEED && isExtensible(it) && !has(it, META))setMeta(it);
return it;
};
var meta = module.exports = {
KEY: META,
NEED: false,
fastKey: fastKey,
getWeak: getWeak,
onFreeze: onFreeze
};
5 changes: 3 additions & 2 deletions library/modules/es6.object.freeze.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// 19.1.2.5 Object.freeze(O)
var isObject = require('./_is-object');
var isObject = require('./_is-object')
, meta = require('./_meta').onFreeze;

require('./_object-sap')('freeze', function($freeze){
return function freeze(it){
return $freeze && isObject(it) ? $freeze(it) : it;
return $freeze && isObject(it) ? $freeze(meta(it)) : it;
};
});
5 changes: 3 additions & 2 deletions library/modules/es6.object.prevent-extensions.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// 19.1.2.15 Object.preventExtensions(O)
var isObject = require('./_is-object');
var isObject = require('./_is-object')
, meta = require('./_meta').onFreeze;

require('./_object-sap')('preventExtensions', function($preventExtensions){
return function preventExtensions(it){
return $preventExtensions && isObject(it) ? $preventExtensions(it) : it;
return $preventExtensions && isObject(it) ? $preventExtensions(meta(it)) : it;
};
});
5 changes: 3 additions & 2 deletions library/modules/es6.object.seal.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// 19.1.2.17 Object.seal(O)
var isObject = require('./_is-object');
var isObject = require('./_is-object')
, meta = require('./_meta').onFreeze;

require('./_object-sap')('seal', function($seal){
return function seal(it){
return $seal && isObject(it) ? $seal(it) : it;
return $seal && isObject(it) ? $seal(meta(it)) : it;
};
});
3 changes: 2 additions & 1 deletion library/modules/es6.symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var $ = require('./_')
, DESCRIPTORS = require('./_descriptors')
, $export = require('./_export')
, redefine = require('./_redefine')
, META = require('./_meta').KEY
, $fails = require('./_fails')
, shared = require('./_shared')
, setToStringTag = require('./_set-to-string-tag')
Expand Down Expand Up @@ -100,7 +101,7 @@ var $getOwnPropertyNames = function getOwnPropertyNames(it){
, result = []
, i = 0
, key;
while(names.length > i)if(!has(AllSymbols, key = names[i++]) && key != HIDDEN)result.push(key);
while(names.length > i)if(!has(AllSymbols, key = names[i++]) && key != HIDDEN && key != META)result.push(key);
return result;
};
var $getOwnPropertySymbols = function getOwnPropertySymbols(it){
Expand Down
42 changes: 27 additions & 15 deletions library/modules/es6.weak-map.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,52 @@
'use strict';
var $ = require('./_')
var each = require('./_').each
, redefine = require('./_redefine')
, getWeak = require('./_meta').getWeak
, assign = require('./_object-assign')
, weak = require('./_collection-weak')
, isObject = require('./_is-object')
, has = require('./_has')
, frozenStore = weak.frozenStore
, WEAK = weak.WEAK
, isExtensible = Object.isExtensible || isObject
, tmp = {};
, isExtensible = Object.isExtensible
, uncaughtFrozenStore = weak.ufstore
, tmp = {}
, InternalMap;

// 23.3 WeakMap Objects
var $WeakMap = require('./_collection')('WeakMap', function(get){
return function WeakMap(){ return get(this, arguments.length > 0 ? arguments[0] : undefined); };
}, {
var wrapper = function(get){
return function WeakMap(){
return get(this, arguments.length > 0 ? arguments[0] : undefined);
};
};

var methods = {
// 23.3.3.3 WeakMap.prototype.get(key)
get: function get(key){
if(isObject(key)){
if(!isExtensible(key))return frozenStore(this).get(key);
if(has(key, WEAK))return key[WEAK][this._i];
var data = getWeak(key);
if(data === true)return uncaughtFrozenStore(this).get(key);
return data ? data[this._i] : undefined;
}
},
// 23.3.3.5 WeakMap.prototype.set(key, value)
set: function set(key, value){
return weak.def(this, key, value);
}
}, weak, true, true);
};

// 23.3 WeakMap Objects
var $WeakMap = require('./_collection')('WeakMap', wrapper, methods, weak, true, true);

// IE11 WeakMap frozen keys fix
if(new $WeakMap().set((Object.freeze || Object)(tmp), 7).get(tmp) != 7){
$.each.call(['delete', 'has', 'get', 'set'], function(key){
InternalMap = weak.getConstructor(wrapper);
assign(InternalMap.prototype, methods);
each.call(['delete', 'has', 'get', 'set'], function(key){
var proto = $WeakMap.prototype
, method = proto[key];
redefine(proto, key, function(a, b){
// store frozen objects on leaky map
// store frozen objects on internal weakmap shim
if(isObject(a) && !isExtensible(a)){
var result = frozenStore(this)[key](a, b);
if(!this._f)this._f = new InternalMap;
var result = this._f[key](a, b);
return key == 'set' ? this : result;
// store all the rest on native weakmap
} return method.call(this, a, b);
Expand Down
22 changes: 2 additions & 20 deletions modules/_collection-strong.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,10 @@ var $ = require('./_')
, forOf = require('./_for-of')
, $iterDefine = require('./_iter-define')
, step = require('./_iter-step')
, ID = require('./_uid')('id')
, $has = require('./_has')
, isObject = require('./_is-object')
, setSpecies = require('./_set-species')
, DESCRIPTORS = require('./_descriptors')
, isExtensible = Object.isExtensible || isObject
, SIZE = DESCRIPTORS ? '_s' : 'size'
, id = 0;

var fastKey = function(it, create){
// return primitive with prefix
if(!isObject(it))return typeof it == 'symbol' ? it : (typeof it == 'string' ? 'S' : 'P') + it;
if(!$has(it, ID)){
// can't set id to frozen object
if(!isExtensible(it))return 'F';
// not necessary to add id
if(!create)return 'E';
// add missing object id
hide(it, ID, ++id);
// return object id with prefix
} return 'O' + it[ID];
};
, fastKey = require('./_meta').fastKey
, SIZE = DESCRIPTORS ? '_s' : 'size';

var getEntry = function(that, key){
// fast case
Expand Down
Loading

0 comments on commit 7dd2b89

Please sign in to comment.