1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-26 08:34:28 +02:00

Use weakmap to prevent memory leak and remove indirect __cache__key (#2471)

* Use global weakmap

* Remove redudant ?:

* Use local Weakmap for objects

* Code refactor

* Annotation refactor

* Annotation refactor

* Annotation refactor

* Fix annotation
This commit is contained in:
Jinxuan Zhu
2018-12-11 15:04:18 -05:00
committed by Ian Storm Taylor
parent fb60d4dfb7
commit c2a3609a09

View File

@@ -1,3 +1,5 @@
/* global WeakMap, Map, Symbol */
/** /**
* GLOBAL: True if memoization should is enabled. * GLOBAL: True if memoization should is enabled.
* *
@@ -6,31 +8,32 @@
let ENABLED = true let ENABLED = true
/**
* GLOBAL: Changing this cache key will clear all previous cached results.
*
* @type {Number}
*/
let CACHE_KEY = 0
/** /**
* The leaf node of a cache tree. Used to support variable argument length. A * The leaf node of a cache tree. Used to support variable argument length. A
* unique object, so that native Maps will key it by reference. * unique object, so that native Maps will key it by reference.
* *
* @type {Object} * @type {Symbol}
*/ */
const LEAF = {} const LEAF = Symbol('LEAF')
/** /**
* A value to represent a memoized undefined value. Allows efficient value * The node of a cache tree for a WeakMap to store cache visited by objects
* retrieval using Map.get only.
* *
* @type {Object} * @type {Symbol}
*/ */
const UNDEFINED = {} const STORE_KEY = Symbol('STORE_KEY')
/**
* Values to represent a memoized undefined and null value. Allows efficient value
* retrieval using Map.get only.
*
* @type {Symbol}
*/
const UNDEFINED = Symbol('undefined')
const NULL = Symbol('null')
/** /**
* Default value for unset keys in native Maps * Default value for unset keys in native Maps
@@ -40,6 +43,14 @@ const UNDEFINED = {}
const UNSET = undefined const UNSET = undefined
/**
* Global Store for all cached values
*
* @type {WeakMap}
*/
let memoizeStore = new WeakMap()
/** /**
* Memoize all of the `properties` on a `object`. * Memoize all of the `properties` on a `object`.
* *
@@ -60,20 +71,14 @@ function memoize(object, properties) {
// If memoization is disabled, call into the original method. // If memoization is disabled, call into the original method.
if (!ENABLED) return original.apply(this, args) if (!ENABLED) return original.apply(this, args)
// If the cache key is different, previous caches must be cleared. if (!memoizeStore.has(this)) {
if (CACHE_KEY !== this.__cache_key) { memoizeStore.set(this, {
this.__cache_key = CACHE_KEY noArgs: {},
this.__cache = new Map() // eslint-disable-line no-undef,no-restricted-globals hasArgs: {},
this.__cache_no_args = {} })
} }
if (!this.__cache) { const { noArgs, hasArgs } = memoizeStore.get(this)
this.__cache = new Map() // eslint-disable-line no-undef,no-restricted-globals
}
if (!this.__cache_no_args) {
this.__cache_no_args = {}
}
const takesArguments = args.length !== 0 const takesArguments = args.length !== 0
@@ -82,9 +87,9 @@ function memoize(object, properties) {
if (takesArguments) { if (takesArguments) {
keys = [property, ...args] keys = [property, ...args]
cachedValue = getIn(this.__cache, keys) cachedValue = getIn(hasArgs, keys)
} else { } else {
cachedValue = this.__cache_no_args[property] cachedValue = noArgs[property]
} }
// If we've got a result already, return it. // If we've got a result already, return it.
@@ -97,9 +102,9 @@ function memoize(object, properties) {
const v = value === undefined ? UNDEFINED : value const v = value === undefined ? UNDEFINED : value
if (takesArguments) { if (takesArguments) {
this.__cache = setIn(this.__cache, keys, v) setIn(hasArgs, keys, v)
} else { } else {
this.__cache_no_args[property] = v noArgs[property] = v
} }
return value return value
@@ -119,12 +124,23 @@ function memoize(object, properties) {
*/ */
function getIn(map, keys) { function getIn(map, keys) {
for (const key of keys) { for (let key of keys) {
map = map.get(key) if (key === undefined) {
key = UNDEFINED
} else if (key === null) {
key = NULL
}
if (typeof key === 'object') {
map = map[STORE_KEY] && map[STORE_KEY].get(key)
} else {
map = map[key]
}
if (map === UNSET) return UNSET if (map === UNSET) return UNSET
} }
return map.get(LEAF) return map[LEAF]
} }
/** /**
@@ -137,23 +153,40 @@ function getIn(map, keys) {
*/ */
function setIn(map, keys, value) { function setIn(map, keys, value) {
let parent = map let child = map
let child
for (const key of keys) { for (let key of keys) {
child = parent.get(key) if (key === undefined) {
key = UNDEFINED
// If the path was not created yet... } else if (key === null) {
if (child === UNSET) { key = NULL
child = new Map() // eslint-disable-line no-undef,no-restricted-globals
parent.set(key, child)
} }
parent = child if (typeof key !== 'object') {
if (!child[key]) {
child[key] = {}
}
child = child[key]
continue
}
if (!child[STORE_KEY]) {
child[STORE_KEY] = new WeakMap()
}
if (!child[STORE_KEY].has(key)) {
const newChild = {}
child[STORE_KEY].set(key, newChild)
child = newChild
continue
}
child = child[STORE_KEY].get(key)
} }
// The whole path has been created, so set the value to the bottom most map. // The whole path has been created, so set the value to the bottom most map.
child.set(LEAF, value) child[LEAF] = value
return map return map
} }
@@ -164,11 +197,7 @@ function setIn(map, keys, value) {
*/ */
function resetMemoization() { function resetMemoization() {
CACHE_KEY++ memoizeStore = new WeakMap()
if (CACHE_KEY >= Number.MAX_SAFE_INTEGER) {
CACHE_KEY = 0
}
} }
/** /**