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:
committed by
Ian Storm Taylor
parent
fb60d4dfb7
commit
c2a3609a09
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user