mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-22 15:02:51 +02:00
allow memoization without arguments for faster lookups (#796)
This commit is contained in:
@@ -77,7 +77,9 @@ class Mark extends new Record(DEFAULTS) {
|
|||||||
|
|
||||||
memoize(Mark.prototype, [
|
memoize(Mark.prototype, [
|
||||||
'getComponent',
|
'getComponent',
|
||||||
])
|
], {
|
||||||
|
takesArguments: true,
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export.
|
* Export.
|
||||||
|
@@ -1806,16 +1806,35 @@ const Node = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
memoize(Node, [
|
memoize(Node, [
|
||||||
'areDescendantsSorted',
|
|
||||||
'getAncestors',
|
|
||||||
'getBlocks',
|
'getBlocks',
|
||||||
'getBlocksAsArray',
|
'getBlocksAsArray',
|
||||||
|
'getCharacters',
|
||||||
|
'getCharactersAsArray',
|
||||||
|
'getFirstText',
|
||||||
|
'getInlines',
|
||||||
|
'getInlinesAsArray',
|
||||||
|
'getKeys',
|
||||||
|
'getLastText',
|
||||||
|
'getMarks',
|
||||||
|
'getOrderedMarks',
|
||||||
|
'getMarksAsArray',
|
||||||
|
'getText',
|
||||||
|
'getTextDirection',
|
||||||
|
'getTexts',
|
||||||
|
'getTextsAsArray',
|
||||||
|
'isLeafBlock',
|
||||||
|
'isLeafInline',
|
||||||
|
], {
|
||||||
|
takesArguments: false
|
||||||
|
})
|
||||||
|
|
||||||
|
memoize(Node, [
|
||||||
|
'areDescendantsSorted',
|
||||||
|
'getAncestors',
|
||||||
'getBlocksAtRange',
|
'getBlocksAtRange',
|
||||||
'getBlocksAtRangeAsArray',
|
'getBlocksAtRangeAsArray',
|
||||||
'getBlocksByType',
|
'getBlocksByType',
|
||||||
'getBlocksByTypeAsArray',
|
'getBlocksByTypeAsArray',
|
||||||
'getCharacters',
|
|
||||||
'getCharactersAsArray',
|
|
||||||
'getCharactersAtRange',
|
'getCharactersAtRange',
|
||||||
'getCharactersAtRangeAsArray',
|
'getCharactersAtRangeAsArray',
|
||||||
'getChild',
|
'getChild',
|
||||||
@@ -1831,23 +1850,15 @@ memoize(Node, [
|
|||||||
'getDescendant',
|
'getDescendant',
|
||||||
'getDescendantAtPath',
|
'getDescendantAtPath',
|
||||||
'getDescendantDecorators',
|
'getDescendantDecorators',
|
||||||
'getFirstText',
|
|
||||||
'getFragmentAtRange',
|
'getFragmentAtRange',
|
||||||
'getFurthestBlock',
|
'getFurthestBlock',
|
||||||
'getFurthestInline',
|
'getFurthestInline',
|
||||||
'getFurthestAncestor',
|
'getFurthestAncestor',
|
||||||
'getFurthestOnlyChildAncestor',
|
'getFurthestOnlyChildAncestor',
|
||||||
'getInlines',
|
|
||||||
'getInlinesAsArray',
|
|
||||||
'getInlinesAtRange',
|
'getInlinesAtRange',
|
||||||
'getInlinesAtRangeAsArray',
|
'getInlinesAtRangeAsArray',
|
||||||
'getInlinesByType',
|
'getInlinesByType',
|
||||||
'getInlinesByTypeAsArray',
|
'getInlinesByTypeAsArray',
|
||||||
'getKeys',
|
|
||||||
'getLastText',
|
|
||||||
'getMarks',
|
|
||||||
'getOrderedMarks',
|
|
||||||
'getMarksAsArray',
|
|
||||||
'getMarksAtRange',
|
'getMarksAtRange',
|
||||||
'getOrderedMarksAtRange',
|
'getOrderedMarksAtRange',
|
||||||
'getMarksAtRangeAsArray',
|
'getMarksAtRangeAsArray',
|
||||||
@@ -1865,11 +1876,7 @@ memoize(Node, [
|
|||||||
'getPreviousBlock',
|
'getPreviousBlock',
|
||||||
'getPreviousSibling',
|
'getPreviousSibling',
|
||||||
'getPreviousText',
|
'getPreviousText',
|
||||||
'getText',
|
|
||||||
'getTextAtOffset',
|
'getTextAtOffset',
|
||||||
'getTextDirection',
|
|
||||||
'getTexts',
|
|
||||||
'getTextsAsArray',
|
|
||||||
'getTextsAtRange',
|
'getTextsAtRange',
|
||||||
'getTextsAtRangeAsArray',
|
'getTextsAtRangeAsArray',
|
||||||
'hasChild',
|
'hasChild',
|
||||||
@@ -1877,10 +1884,10 @@ memoize(Node, [
|
|||||||
'hasNode',
|
'hasNode',
|
||||||
'hasVoidParent',
|
'hasVoidParent',
|
||||||
'isInlineSplitAtRange',
|
'isInlineSplitAtRange',
|
||||||
'isLeafBlock',
|
|
||||||
'isLeafInline',
|
|
||||||
'validate',
|
'validate',
|
||||||
])
|
], {
|
||||||
|
takesArguments: true
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export.
|
* Export.
|
||||||
|
@@ -407,14 +407,21 @@ class Text extends new Record(DEFAULTS) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
memoize(Text.prototype, [
|
memoize(Text.prototype, [
|
||||||
'getDecorations',
|
|
||||||
'getDecorators',
|
|
||||||
'getMarks',
|
'getMarks',
|
||||||
'getMarksAsArray',
|
'getMarksAsArray',
|
||||||
|
], {
|
||||||
|
takesArguments: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
memoize(Text.prototype, [
|
||||||
|
'getDecorations',
|
||||||
|
'getDecorators',
|
||||||
'getMarksAtIndex',
|
'getMarksAtIndex',
|
||||||
'getRanges',
|
'getRanges',
|
||||||
'validate'
|
'validate'
|
||||||
])
|
], {
|
||||||
|
takesArguments: true,
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export.
|
* Export.
|
||||||
|
@@ -3,7 +3,7 @@ import Map from 'es6-map'
|
|||||||
import IS_DEV from '../constants/is-dev'
|
import IS_DEV from '../constants/is-dev'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GLOBAL: True if memoization should is enabled. Only effective in DEV mode.
|
* GLOBAL: True if memoization should is enabled. Only effective when `IS_DEV`.
|
||||||
*
|
*
|
||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
@@ -12,7 +12,7 @@ let ENABLED = true
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* GLOBAL: Changing this cache key will clear all previous cached results.
|
* GLOBAL: Changing this cache key will clear all previous cached results.
|
||||||
* Only effective in DEV mode.
|
* Only effective when `IS_DEV`.
|
||||||
*
|
*
|
||||||
* @type {Number}
|
* @type {Number}
|
||||||
*/
|
*/
|
||||||
@@ -20,9 +20,8 @@ let ENABLED = true
|
|||||||
let CACHE_KEY = 0
|
let CACHE_KEY = 0
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The leaf node of a cache tree. Used to support variable argument length.
|
* 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.
|
||||||
* A unique object, so that native Maps will key it by reference.
|
|
||||||
*
|
*
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
@@ -30,8 +29,8 @@ let CACHE_KEY = 0
|
|||||||
const LEAF = {}
|
const LEAF = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A value to represent a memoized undefined value. Allows efficient
|
* A value to represent a memoized undefined value. Allows efficient value
|
||||||
* value retrieval using Map.get only.
|
* retrieval using Map.get only.
|
||||||
*
|
*
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
@@ -54,7 +53,9 @@ const UNSET = undefined
|
|||||||
* @return {Record}
|
* @return {Record}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function memoize(object, properties) {
|
function memoize(object, properties, options = {}) {
|
||||||
|
const { takesArguments = true } = options
|
||||||
|
|
||||||
for (const property of properties) {
|
for (const property of properties) {
|
||||||
const original = object[property]
|
const original = object[property]
|
||||||
|
|
||||||
@@ -64,63 +65,50 @@ function memoize(object, properties) {
|
|||||||
|
|
||||||
object[property] = function (...args) {
|
object[property] = function (...args) {
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
if (!ENABLED) {
|
// If memoization is disabled, call into the original method.
|
||||||
// Memoization disabled
|
if (!ENABLED) return original.apply(this, args)
|
||||||
return original.apply(this, args)
|
|
||||||
} else if (CACHE_KEY !== this.__cache_key) {
|
// If the cache key is different, previous caches must be cleared.
|
||||||
// Previous caches must be cleared
|
if (CACHE_KEY !== this.__cache_key) {
|
||||||
this.__cache_key = CACHE_KEY
|
this.__cache_key = CACHE_KEY
|
||||||
this.__cache = new Map()
|
this.__cache = new Map()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const keys = [property, ...args]
|
if (!this.__cache) {
|
||||||
this.__cache = this.__cache || new Map()
|
this.__cache = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
const cachedValue = getIn(this.__cache, keys)
|
let cachedValue
|
||||||
|
let keys
|
||||||
|
|
||||||
|
if (takesArguments) {
|
||||||
|
keys = [property, ...args]
|
||||||
|
cachedValue = getIn(this.__cache, keys)
|
||||||
|
} else {
|
||||||
|
cachedValue = this.__cache.get(property)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we've got a result already, return it.
|
||||||
if (cachedValue !== UNSET) {
|
if (cachedValue !== UNSET) {
|
||||||
return cachedValue === UNDEFINED ? undefined : cachedValue
|
return cachedValue === UNDEFINED ? undefined : cachedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise calculate what it should be once and cache it.
|
||||||
const value = original.apply(this, args)
|
const value = original.apply(this, args)
|
||||||
this.__cache = setIn(this.__cache, keys, value)
|
const v = value === undefined ? UNDEFINED : value
|
||||||
|
|
||||||
|
if (takesArguments) {
|
||||||
|
this.__cache = setIn(this.__cache, keys, v)
|
||||||
|
} else {
|
||||||
|
this.__cache.set(property, v)
|
||||||
|
}
|
||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a value at a key path in a tree of Map, creating Maps on the go.
|
|
||||||
*
|
|
||||||
* @param {Map} map
|
|
||||||
* @param {Array} keys
|
|
||||||
* @param {Any} value
|
|
||||||
* @return {Map}
|
|
||||||
*/
|
|
||||||
|
|
||||||
function setIn(map, keys, value) {
|
|
||||||
value = value === undefined ? UNDEFINED : value
|
|
||||||
|
|
||||||
let parentMap = map
|
|
||||||
let childMap
|
|
||||||
for (const key of keys) {
|
|
||||||
childMap = parentMap.get(key)
|
|
||||||
|
|
||||||
// If the path was not created yet...
|
|
||||||
if (childMap === UNSET) {
|
|
||||||
childMap = new Map()
|
|
||||||
parentMap.set(key, childMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
parentMap = childMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// The whole path has been created, so set the value to the bottom most map.
|
|
||||||
childMap.set(LEAF, value)
|
|
||||||
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a value at a key path in a tree of Map.
|
* Get a value at a key path in a tree of Map.
|
||||||
*
|
*
|
||||||
@@ -133,18 +121,42 @@ function setIn(map, keys, value) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function getIn(map, keys) {
|
function getIn(map, keys) {
|
||||||
let childMap
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
childMap = map.get(key)
|
map = map.get(key)
|
||||||
|
if (map === UNSET) return UNSET
|
||||||
if (childMap === UNSET) {
|
|
||||||
return UNSET
|
|
||||||
}
|
}
|
||||||
|
|
||||||
map = childMap
|
return map.get(LEAF)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value at a key path in a tree of Map, creating Maps on the go.
|
||||||
|
*
|
||||||
|
* @param {Map} map
|
||||||
|
* @param {Array} keys
|
||||||
|
* @param {Any} value
|
||||||
|
* @return {Map}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function setIn(map, keys, value) {
|
||||||
|
let parent = map
|
||||||
|
let child
|
||||||
|
|
||||||
|
for (const key of keys) {
|
||||||
|
child = parent.get(key)
|
||||||
|
|
||||||
|
// If the path was not created yet...
|
||||||
|
if (child === UNSET) {
|
||||||
|
child = new Map()
|
||||||
|
parent.set(key, child)
|
||||||
}
|
}
|
||||||
|
|
||||||
return childMap.get(LEAF)
|
parent = child
|
||||||
|
}
|
||||||
|
|
||||||
|
// The whole path has been created, so set the value to the bottom most map.
|
||||||
|
child.set(LEAF, value)
|
||||||
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,6 +167,7 @@ function getIn(map, keys) {
|
|||||||
|
|
||||||
function __clear() {
|
function __clear() {
|
||||||
CACHE_KEY++
|
CACHE_KEY++
|
||||||
|
|
||||||
if (CACHE_KEY >= Number.MAX_SAFE_INTEGER) {
|
if (CACHE_KEY >= Number.MAX_SAFE_INTEGER) {
|
||||||
CACHE_KEY = 0
|
CACHE_KEY = 0
|
||||||
}
|
}
|
||||||
|
@@ -11,12 +11,12 @@ import 'babel-polyfill'
|
|||||||
* Tests.
|
* Tests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import './plugins'
|
|
||||||
import './rendering'
|
import './rendering'
|
||||||
import './schema'
|
import './schema'
|
||||||
import './serializers'
|
import './serializers'
|
||||||
import './transforms'
|
import './transforms'
|
||||||
import './behavior'
|
import './behavior'
|
||||||
|
import './plugins'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset Slate's internal state before each text.
|
* Reset Slate's internal state before each text.
|
||||||
|
@@ -12,7 +12,7 @@ import { resolve } from 'path'
|
|||||||
* Tests.
|
* Tests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
describe.only('plugins', () => {
|
describe('plugins', () => {
|
||||||
const tests = fs.readdirSync(resolve(__dirname, './fixtures'))
|
const tests = fs.readdirSync(resolve(__dirname, './fixtures'))
|
||||||
|
|
||||||
for (const test of tests) {
|
for (const test of tests) {
|
||||||
|
Reference in New Issue
Block a user