Skip to content

Instantly share code, notes, and snippets.

@hax
Created December 4, 2018 10:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hax/fa6a5c6bd62364b64c4829be68493fd0 to your computer and use it in GitHub Desktop.
Save hax/fa6a5c6bd62364b64c4829be68493fd0 to your computer and use it in GitHub Desktop.
readonly collections
void function () {
'use strict'
const targetSymbol = Symbol()
function createSafeMethod(method) {
return new Proxy(method, {
apply(target, thisArg, argArray) {
return Reflect.apply(target, thisArg[targetSymbol], argArray)
}
})
}
function readOnlyView(collection, safeMethods) {
return new Proxy(collection, {
get(target, key) {
if (key === targetSymbol) return target
const v = Reflect.get(target, key)
if (typeof v === 'function') {
return safeMethods.get(v) || v
}
return v
}
})
}
const safeMethodNames = [
'get', 'has', 'forEach',
Symbol.iterator, 'entries', 'keys', 'values',
]
for (const C of [Map, Set, WeakMap, WeakSet]) {
const safeMethods = new Map(
safeMethodNames
.filter(name => name in C.prototype)
.map(name => C.prototype[name])
.map(method => [method, createSafeMethod(method)])
)
Object.defineProperty(C.prototype, 'readonly', {
get() {
Object.defineProperty(this, 'readonly', {
value: readOnlyView(this, safeMethods),
})
return this.readonly
},
configurable: true,
})
}
}()
import './readonly'
import {test, assert, assertThrow} from './test-util'
test('Map', () => {
const c = new Map([[1, 'A'], [2, 'B'], [3, 'C']])
const r = c.readonly
c.set(4, 'D').delete(1)
assert(() => c.readonly === c.readonly)
assert(() => r.readonly === c.readonly)
assert(() => r instanceof Map)
assert(() => r.size === 3)
assert(() => r.has(4))
assert(() => r.get(2) === 'B')
let ksum = 0, vsum = ''
for (const [k, v] of r) {
ksum += k
vsum += v
}
assert(() => ksum === 9 && vsum === 'BCD')
assert(() => Object.getOwnPropertySymbols(r).length === 0)
assertThrow(() => r.set(1, 'A'))
assertThrow(() => r.delete(1))
assertThrow(() => Map.prototype.clear.call(r))
assertThrow(() => Map.prototype.has.call(r, 4))
assert(() => r.has !== c.has)
assert(() => r.has === new Map().readonly.has)
assert(() => r.has.call(new Map([[1, 'A']]).readonly, 1))
assertThrow(() => r.has.call(new Map([[1, 'A']]), 1))
})
test('Set', () => {
const c = new Set([1, 2, 3])
const r = c.readonly
c.add(4).delete(1)
assert(() => c.readonly === c.readonly)
assert(() => r.readonly === c.readonly)
assert(() => r instanceof Set)
assert(() => r.size === 3)
assert(() => r.has(4))
let sum = 0
for (const x of r) sum += x
assert(() => sum === 9)
assert(() => Object.getOwnPropertySymbols(r).length === 0)
assertThrow(() => r.add(1))
assertThrow(() => r.delete(1))
assertThrow(() => Set.prototype.clear.call(r))
assertThrow(() => Set.prototype.has.call(r, 4))
assert(() => r.has !== c.has)
assert(() => r.has === new Set().readonly.has)
assert(() => r.has.call(new Set([1]).readonly, 1))
assertThrow(() => r.has.call(new Set([1]), 1))
})
test('WeakMap', () => {
const k1 = {}, k2 = {}, k3 = {}, k4 = {}
const c = new WeakMap([[k1, 'A'], [k2, 'B'], [k3, 'C']])
const r = c.readonly
c.set(k4, 'D').delete(k1)
assert(() => c.readonly === c.readonly)
assert(() => r.readonly === c.readonly)
assert(() => r instanceof WeakMap)
assert(() => r.has(k4))
assert(() => r.get(k2) === 'B')
assert(() => Object.getOwnPropertySymbols(r).length === 0)
assertThrow(() => r.set(k1, 'A'))
assertThrow(() => r.delete(k1))
assertThrow(() => WeakMap.prototype.clear.call(r))
assertThrow(() => WeakMap.prototype.has.call(r, k4))
assert(() => r.has !== c.has)
assert(() => r.has === new WeakMap().readonly.has)
assert(() => r.has.call(new WeakMap([[k1, 'A']]).readonly, k1))
assertThrow(() => r.has.call(new WeakMap([[k1, 'A']]), k1))
})
test('WeakSet', () => {
const k1 = {}, k2 = {}, k3 = {}, k4 = {}
const c = new WeakSet([k1, k2, k3])
const r = c.readonly
c.add(k4).delete(k1)
assert(() => c.readonly === c.readonly)
assert(() => r.readonly === c.readonly)
assert(() => r instanceof WeakSet)
assert(() => r.has(k4))
assert(() => Object.getOwnPropertySymbols(r).length === 0)
assertThrow(() => r.add(k1))
assertThrow(() => r.delete(k1))
assertThrow(() => WeakSet.prototype.clear.call(r))
assertThrow(() => WeakSet.prototype.has.call(r, k4))
assert(() => r.has !== c.has)
assert(() => r.has === new WeakSet().readonly.has)
assert(() => r.has.call(new WeakSet([k1]).readonly, k1))
assertThrow(() => r.has.call(new WeakSet([k1]), k1))
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment