Skip to content

Instantly share code, notes, and snippets.

@dfkaye
Last active April 27, 2022 21:04
Show Gist options
  • Save dfkaye/7f8eb53f14f04a03507126ee5b3c33d1 to your computer and use it in GitHub Desktop.
Save dfkaye/7f8eb53f14f04a03507126ee5b3c33d1 to your computer and use it in GitHub Desktop.
Make a custom data structure with non-enumerable iterators.
// 21 March 2022
// Custom data structure with non-enumerable iterators.
// 22 March 2022: make Data, not Iterable, the constructor:
// - callable without the `new` keyword
// - pass instance to Iterable
// - decorate instance with iterators
// - merge data
// 27 April 2022
// - pull up Iterable decoration into the Data constructor.
// - pull out cruft and comments to custom-data-structure.js
// for posterity.
/* factory version */
function Data(data) {
if (!(this instanceof Data)) {
return new Data(data)
}
data = Object(data)
return Object.assign(this, data)
}
// Iterator methods
function entries() {
return Object.entries(this);
}
function values() {
return Object.values(this)
}
function keys() {
return Object.keys(this)
}
function toString() {
return `[object ${this.constructor.name}]`
}
// Descriptors for the iterator methods:
// - all readonly, non-deletable, non-enumerable.
var descriptors = {
[Symbol.iterator]: {
value: function () {
var entries = Object.entries(this);
var i = 0;
return {
next: function () {
return {
value: entries[i],
done: i++ >= entries.length
}
}
}
}
},
entries: { value: entries },
values: { value: values },
keys: { value: keys },
toString: { value: toString }
};
// Decorate the prototype with iterable method descriptors.
Object.defineProperties(Data.prototype, descriptors);
/* test it out */
var object = {
name: 'base',
value: Date.now(),
mission: 'test'
};
var data = Data(object);
console.log( Data(data) === data );
console.log( Data(object) != Data(object) );
console.log( data.entries() );
console.log( data.values() );
console.log( data.keys() );
console.warn( data.toString() );
for (var [k,v] of data) { console.log([k,v]) }
for (var [k,v] of data.entries()) { console.log([k,v]) }
for (var v of data.values()) { console.log(v) }
for (var k of data.keys()) { console.log(k) }
/* prototype iteration tests */
// All properties in data's prototype (Iterable) are non-enumerable,
// so these should not print anything.
var proto = Object.getPrototypeOf(data);
var noop = (p) => console.error("should not see this: ", p)
for (var p in proto) { noop(p) }
proto.entries().forEach(p => noop(p))
proto.values().forEach(p => noop(p))
proto.keys().forEach(p => noop(p))
// 20 March 2022
// Custom data structure with non-enumerable iterators.
// return [key, value] entries
function entries() {
return Object.entries(this)
}
// bind entries() to data prototype's iterator methods
var o = Object.defineProperties({}, {
[Symbol.iterator]: { value: entries },
entries: { value: entries }
});
// data with iterators as prototype
var data = Object.create(o);
// merge new data changes
Object.assign(data, {name: "test"});
// iterate data
var r = []
for (var [k, v] of data.entries()) {
r.push([k,v])
}
console.warn(r);
// Array (2)
// 0: Array [ "name", "test" ]
// length: 1
// show us our iterators in the prototype
Object.getPrototypeOf(data);
// Object { … }
// entries: function entries()
// Symbol(Symbol.iterator): function entries()
// 21 March 2022
// Custom data structure with non-enumerable iterators.
// 22 March 2022: make Data, not Iterable, the constructor:
// - callable without the `new` keyword
// - pass instance to Iterable
// - decorate instance with iterators
// - merge data
// 27 April 2022
// - pull up Iterable decoration into the Data constructor.
/* factory version */
function Data(data) {
if (!(this instanceof Data)) {
return new Data(data)
}
data = Object(data)
// if (data instanceof Iterable) {
// return data
// }
// var iterable = new Iterable(this)
// Object.defineProperty(iterable, "constructor", {
// value: Data
// });
// bind iterator descriptor properties to this instance
// Object.defineProperties(this, descriptors);
// return object with iterator interface as prototype
// var iterable = Object.create(this);
return Object.assign(this, data)
}
// Interface containing iterator methods
// Interface decorates data object with iterator methods
/*
function Iterable(data) {
// if (!(this instanceof Iterable)) {
// return new Iterable
// }
// bind iterator descriptor properties to this instance
Object.defineProperties(data, descriptors);
// return object with iterator interface as prototype
return Object.create(data);
}
*/
// Iterator methods
function entries() {
return Object.entries(this);
}
function values() {
return Object.values(this)
}
function keys() {
return Object.keys(this)
}
// Some metadata
function toString() {
return `[object ${this.constructor.name}]`
}
// Descriptors for the iterator methods, all readonly, non-deletable, non-enumerable.
var descriptors = {
[Symbol.iterator]: {
value: function () {
var entries = Object.entries(this);
var i = 0;
return {
next: function () {
return {
value: entries[i],
done: i++ >= entries.length
}
}
}
}
},
entries: { value: entries },
values: { value: values },
keys: { value: keys },
toString: { value: toString }
};
// Put descriptors on the prototype
Object.defineProperties(Data.prototype, descriptors);
/* test it out */
var object = {
name: 'base',
value: Date.now(),
mission: 'test'
};
var data = Data(object);
console.log( Data(data) === data );
console.log( Data(object) != Data(object) );
console.log( data.entries() );
console.log( data.values() );
console.log( data.keys() );
console.warn( data.toString() );
for (var [k,v] of data) { console.log([k,v]) }
for (var [k,v] of data.entries()) { console.log([k,v]) }
for (var v of data.values()) { console.log(v) }
for (var k of data.keys()) { console.log(k) }
/* prototype iteration tests */
// All properties in data's prototype (Iterable) are non-enumerable,
// so these should not print anything.
var proto = Object.getPrototypeOf(data);
var noop = (p) => console.error("should not see this: ", p)
for (var p in proto) { noop(p) }
proto.entries().forEach(p => noop(p))
proto.values().forEach(p => noop(p))
proto.keys().forEach(p => noop(p))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment