jjb-lc-designable
Version:
基于alibaba-designable源码二次封装的表单设计器。
311 lines (305 loc) • 7.54 kB
JavaScript
/**
* A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
* recently used items while discarding least recently used items when its limit
* is reached.
*
* Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
* See README.md for details.
*
* Illustration of the design:
*
* entry entry entry entry
* ______ ______ ______ ______
* | head |.newer => | |.newer => | |.newer => | tail |
* | A | | B | | C | | D |
* |______| <= older.|______| <= older.|______| <= older.|______|
*
* removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
*/
/* eslint-disable */
const NEWER = Symbol('newer');
const OLDER = Symbol('older');
function Entry(key, value) {
this.key = key;
this.value = value;
this[NEWER] = undefined;
this[OLDER] = undefined;
}
export class LRUMap {
constructor(limit, entries) {
if (typeof limit !== 'number') {
// called as (entries)
entries = limit;
limit = 0;
}
this.size = 0;
this.limit = limit;
this.oldest = this.newest = undefined;
this._keymap = new Map();
if (entries) {
this.assign(entries);
if (limit < 1) {
this.limit = this.size;
}
}
}
_markEntryAsUsed(entry) {
if (entry === this.newest) {
// Already the most recenlty used entry, so no need to update the list
return;
}
// HEAD--------------TAIL
// <.older .newer>
// <--- add direction --
// A B C <D> E
if (entry[NEWER]) {
if (entry === this.oldest) {
this.oldest = entry[NEWER];
}
entry[NEWER][OLDER] = entry[OLDER]; // C <-- E.
}
if (entry[OLDER]) {
entry[OLDER][NEWER] = entry[NEWER]; // C. --> E
}
entry[NEWER] = undefined; // D --x
entry[OLDER] = this.newest; // D. --> E
if (this.newest) {
this.newest[NEWER] = entry; // E. <-- D
}
this.newest = entry;
}
assign(entries) {
let entry;
let limit = this.limit || Number.MAX_VALUE;
this._keymap.clear();
const it = entries[Symbol.iterator]();
for (let itv = it.next(); !itv.done; itv = it.next()) {
const e = new Entry(itv.value[0], itv.value[1]);
this._keymap.set(e.key, e);
if (!entry) {
this.oldest = e;
} else {
entry[NEWER] = e;
e[OLDER] = entry;
}
entry = e;
if (limit-- === 0) {
throw new Error('overflow');
}
}
this.newest = entry;
this.size = this._keymap.size;
}
get(key) {
// First, find our cache entry
const entry = this._keymap.get(key);
if (!entry) {
return;
} // Not cached. Sorry.
// As <key> was found in the cache, register it as being requested recently
this._markEntryAsUsed(entry);
return entry.value;
}
set(key, value) {
let entry = this._keymap.get(key);
if (entry) {
// update existing
entry.value = value;
this._markEntryAsUsed(entry);
return this;
}
// new entry
this._keymap.set(key, entry = new Entry(key, value));
if (this.newest) {
// link previous tail to the new tail (entry)
this.newest[NEWER] = entry;
entry[OLDER] = this.newest;
} else {
// we're first in -- yay
this.oldest = entry;
}
// add new entry to the end of the linked list -- it's now the freshest entry.
this.newest = entry;
++this.size;
if (this.size > this.limit) {
// we hit the limit -- remove the head
this.shift();
}
return this;
}
shift() {
// todo: handle special case when limit == 1
const entry = this.oldest;
if (entry) {
if (this.oldest[NEWER]) {
// advance the list
this.oldest = this.oldest[NEWER];
this.oldest[OLDER] = undefined;
} else {
// the cache is exhausted
this.oldest = undefined;
this.newest = undefined;
}
// Remove last strong reference to <entry> and remove links from the purged
// entry being returned:
entry[NEWER] = entry[OLDER] = undefined;
this._keymap.delete(entry.key);
--this.size;
return [entry.key, entry.value];
}
}
find(key) {
const e = this._keymap.get(key);
return e ? e.value : undefined;
}
has(key) {
return this._keymap.has(key);
}
delete(key) {
const entry = this._keymap.get(key);
if (!entry) {
return;
}
this._keymap.delete(entry.key);
if (entry[NEWER] && entry[OLDER]) {
// relink the older entry with the newer entry
entry[OLDER][NEWER] = entry[NEWER];
entry[NEWER][OLDER] = entry[OLDER];
} else if (entry[NEWER]) {
// remove the link to us
entry[NEWER][OLDER] = undefined;
// link the newer entry to head
this.oldest = entry[NEWER];
} else if (entry[OLDER]) {
// remove the link to us
entry[OLDER][NEWER] = undefined;
// link the newer entry to head
this.newest = entry[OLDER];
} else {
// if(entry[OLDER] === undefined && entry.newer === undefined) {
this.oldest = this.newest = undefined;
}
this.size--;
return entry.value;
}
clear() {
// Not clearing links should be safe, as we don't expose live links to user
this.oldest = this.newest = undefined;
this.size = 0;
this._keymap.clear();
}
keys() {
return new KeyIterator(this.oldest);
}
values() {
return new ValueIterator(this.oldest);
}
entries() {}
forEach(fun, thisObj) {
if (typeof thisObj !== 'object') {
thisObj = this;
}
let entry = this.oldest;
while (entry) {
fun.call(thisObj, entry.value, entry.key, this);
entry = entry[NEWER];
}
}
toJSON() {
const s = new Array(this.size);
let i = 0;
let entry = this.oldest;
while (entry) {
s[i++] = {
key: entry.key,
value: entry.value
};
entry = entry[NEWER];
}
return s;
}
toString() {
let s = '';
let entry = this.oldest;
while (entry) {
s += String(entry.key) + ':' + entry.value;
entry = entry[NEWER];
if (entry) {
s += ' < ';
}
}
return s;
}
[Symbol.iterator]() {
return new EntryIterator(this.oldest);
}
}
class EntryIterator {
constructor(oldestEntry) {
this.entry = oldestEntry;
}
[Symbol.iterator]() {
return this;
}
next() {
const ent = this.entry;
if (ent) {
this.entry = ent[NEWER];
return {
done: false,
value: [ent.key, ent.value]
};
} else {
return {
done: true,
value: undefined
};
}
}
}
class KeyIterator {
constructor(oldestEntry) {
this.entry = oldestEntry;
}
[Symbol.iterator]() {
return this;
}
next() {
const ent = this.entry;
if (ent) {
this.entry = ent[NEWER];
return {
done: false,
value: ent.key
};
} else {
return {
done: true,
value: undefined
};
}
}
}
class ValueIterator {
constructor(oldestEntry) {
this.entry = oldestEntry;
}
[Symbol.iterator]() {
return this;
}
next() {
const ent = this.entry;
if (ent) {
this.entry = ent[NEWER];
return {
done: false,
value: ent.value
};
} else {
return {
done: true,
value: undefined
};
}
}
}