1
0
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:
Ian Storm Taylor
2017-05-05 17:44:53 -07:00
committed by GitHub
parent c7cc980732
commit 7a5dfe6fda
6 changed files with 112 additions and 83 deletions

View File

@@ -77,7 +77,9 @@ class Mark extends new Record(DEFAULTS) {
memoize(Mark.prototype, [ memoize(Mark.prototype, [
'getComponent', 'getComponent',
]) ], {
takesArguments: true,
})
/** /**
* Export. * Export.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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
} }

View File

@@ -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.

View File

@@ -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) {