-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12990 from mmun/weak-map
[BUGFIX beta] Add private WeakMap
- Loading branch information
Showing
3 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { assert } from 'ember-metal/debug'; | ||
import { GUID_KEY } from 'ember-metal/utils'; | ||
import { | ||
peekMeta, | ||
meta as metaFor | ||
} from 'ember-metal/meta'; | ||
|
||
let id = 0; | ||
function UNDEFINED() {} | ||
|
||
/* | ||
* @private | ||
* @class Ember.WeakMap | ||
* | ||
* A partial polyfill for [WeakMap](http://www.ecma-international.org/ecma-262/6.0/#sec-weakmap-objects). | ||
* | ||
* There is a small but important caveat. This implementation assumes that the | ||
* weak map will live longer (in the sense of garbage collection) than all of its | ||
* keys, otherwise it is possible to leak the values stored in the weak map. In | ||
* practice, most use cases satisfy this limitation which is why it is included | ||
* in ember-metal. | ||
*/ | ||
export default function WeakMap() { | ||
assert( | ||
'Invoking the WeakMap constructor with arguments is not supported at this time', | ||
arguments.length === 0 | ||
); | ||
|
||
this._id = GUID_KEY + (id++); | ||
} | ||
|
||
/* | ||
* @method get | ||
* @param key {Object | Function} | ||
* @return {Any} stored value | ||
*/ | ||
WeakMap.prototype.get = function(obj) { | ||
let meta = peekMeta(obj); | ||
if (meta) { | ||
let map = meta.readableWeak(); | ||
if (map) { | ||
if (map[this._id] === UNDEFINED) { | ||
return undefined; | ||
} | ||
|
||
return map[this._id]; | ||
} | ||
} | ||
}; | ||
|
||
/* | ||
* @method set | ||
* @param key {Object | Function} | ||
* @param value {Any} | ||
* @return {WeakMap} the weak map | ||
*/ | ||
WeakMap.prototype.set = function(obj, value) { | ||
assert( | ||
'Uncaught TypeError: Invalid value used as weak map key', | ||
obj && (typeof obj === 'object' || typeof obj === 'function') | ||
); | ||
|
||
if (value === undefined) { | ||
value = UNDEFINED; | ||
} | ||
|
||
metaFor(obj).writableWeak()[this._id] = value; | ||
|
||
return this; | ||
}; | ||
|
||
/* | ||
* @method has | ||
* @param key {Object | Function} | ||
* @return {boolean} if the key exists | ||
*/ | ||
WeakMap.prototype.has = function(obj) { | ||
let meta = peekMeta(obj); | ||
if (meta) { | ||
let map = meta.readableWeak(); | ||
if (map) { | ||
return map[this._id] !== undefined; | ||
} | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
/* | ||
* @method delete | ||
* @param key {Object | Function} | ||
* @return {boolean} if the key was deleted | ||
*/ | ||
WeakMap.prototype.delete = function(obj) { | ||
if (this.has(obj)) { | ||
delete metaFor(obj).writableWeak()[this._id]; | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import WeakMap from 'ember-metal/weak_map'; | ||
|
||
QUnit.module('Ember.WeakMap'); | ||
|
||
QUnit.test('has weakMap like qualities', function(assert) { | ||
let map = new WeakMap(); | ||
let map2 = new WeakMap(); | ||
|
||
let a = {}; | ||
let b = {}; | ||
let c = {}; | ||
|
||
assert.strictEqual(map.get(a), undefined); | ||
assert.strictEqual(map.get(b), undefined); | ||
assert.strictEqual(map.get(c), undefined); | ||
|
||
assert.strictEqual(map2.get(a), undefined); | ||
assert.strictEqual(map2.get(b), undefined); | ||
assert.strictEqual(map2.get(c), undefined); | ||
|
||
assert.strictEqual(map.set(a, 1), map, 'map.set should return itself'); | ||
assert.strictEqual(map.get(a), 1); | ||
assert.strictEqual(map.set(b, undefined), map); | ||
assert.strictEqual(map.set(a, 2), map); | ||
assert.strictEqual(map.get(a), 2); | ||
assert.strictEqual(map.set(b, undefined), map); | ||
|
||
assert.strictEqual(map2.get(a), undefined); | ||
assert.strictEqual(map2.get(b), undefined); | ||
assert.strictEqual(map2.get(c), undefined); | ||
|
||
assert.strictEqual(map.set(c, 1), map); | ||
assert.strictEqual(map.get(c), 1); | ||
assert.strictEqual(map.get(a), 2); | ||
assert.strictEqual(map.get(b), undefined); | ||
|
||
assert.strictEqual(map2.set(a, 3), map2); | ||
assert.strictEqual(map2.set(b, 4), map2); | ||
assert.strictEqual(map2.set(c, 5), map2); | ||
|
||
assert.strictEqual(map2.get(a), 3); | ||
assert.strictEqual(map2.get(b), 4); | ||
assert.strictEqual(map2.get(c), 5); | ||
|
||
assert.strictEqual(map.get(c), 1); | ||
assert.strictEqual(map.get(a), 2); | ||
assert.strictEqual(map.get(b), undefined); | ||
}); | ||
|
||
QUnit.test('invoking the WeakMap constructor with arguments is not supported at this time', function(assert) { | ||
expectAssertion(function() { | ||
new WeakMap([[{}, 1]]); | ||
}, /Invoking the WeakMap constructor with arguments is not supported at this time/); | ||
}); | ||
|
||
QUnit.test('that error is thrown when using a primitive key', function(assert) { | ||
let map = new WeakMap(); | ||
|
||
expectAssertion(function() { | ||
map.set('a', 1); | ||
}, /Uncaught TypeError: Invalid value used as weak map key/); | ||
|
||
expectAssertion(function() { | ||
map.set(1, 1); | ||
}, /Uncaught TypeError: Invalid value used as weak map key/); | ||
|
||
expectAssertion(function() { | ||
map.set(true, 1); | ||
}, /Uncaught TypeError: Invalid value used as weak map key/); | ||
|
||
expectAssertion(function() { | ||
map.set(null, 1); | ||
}, /Uncaught TypeError: Invalid value used as weak map key/); | ||
|
||
expectAssertion(function() { | ||
map.set(undefined, 1); | ||
}, /Uncaught TypeError: Invalid value used as weak map key/); | ||
}); | ||
|
||
QUnit.test('that .has and .delete work as expected', function(assert) { | ||
let map = new WeakMap(); | ||
let a = {}; | ||
let b = {}; | ||
let foo = { id: 1, name: 'My file', progress: 0 }; | ||
|
||
assert.strictEqual(map.set(a, foo), map); | ||
assert.strictEqual(map.get(a), foo); | ||
assert.strictEqual(map.has(a), true); | ||
assert.strictEqual(map.has(b), false); | ||
assert.strictEqual(map.delete(a), true); | ||
assert.strictEqual(map.has(a), false); | ||
assert.strictEqual(map.delete(a), false); | ||
assert.strictEqual(map.set(a, undefined), map); | ||
assert.strictEqual(map.has(a), true); | ||
assert.strictEqual(map.delete(a), true); | ||
assert.strictEqual(map.delete(a), false); | ||
assert.strictEqual(map.has(a), false); | ||
}); |