Defining an indexer for an object - javascript

One can make an object iterable by implementing [Symbol.iterator].
But how can one override the behavior of the [] operator?
For example i have a an object which has an array inside of it and i want to be able to access that given an index like obj[3].
is that possible?
example
const SignalArray = (data = []) => {
...
return {
add,
remove,
onAdd,
onRemove,
onSort,
removeOnAdd,
removeOnRemove,
removeOnSort,
[Symbol.iterator]() {
return {
next: () => {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
index = 0;
return { done: true };
}
}
}
}
}
}

how can one override the behavior of the [] operator?
Only via Proxy, added in ES2015. You'd provide a get trap and handle the property keys you want to handle.
Here's an example where we check for property names that can be successfully coerced to numbers and return the number * 2:
const o = new Proxy({}, {
get(target, prop, receiver) {
const v = +prop;
if (!isNaN(v)) {
return v * 2;
}
return Reflect.get(...arguments);
}
});
o.x = "ex";
console.log(o[2]); // 4
console.log(o[7]); // 14
console.log(o.x); // "ex"
If you want to override setting the array element, you'd use set trap. There are several other traps available as well. For instance, in a reply to a comment, you said:
...if you hate es6 classes and want to write a wrapper around an array that gives it extra functionality, like an observable array for example...
...that would probably involve a set trap and overriding various mutator methods.

Related

Check if array of objects has changed

I have an array that could contain objects. Objects can either be added to it or have a property modified. I want to check if the array has changed at all (could be element(s) added or simply just have one object have a key changed), and then update the DB based on the potential change.
Just wanna know if what I have will cover all cases and/or if there is a better way to do it.
const origArrayCopy = JSON.stringify(origArray);
someFnThatPotentiallyChanges(origArray);
if (origArrayCopy !== JSON.stringify(origArray)) {
updateDB(arr);
} else {
console.log('NO DIFF');
}
And here's a jsFiddle I created to test around with https://jsfiddle.net/j4eqwmp6/
Converting the object to a string using stringify should account for deep-nested changes, right? Any insights on this implementation and is there now a more appropriate way to do it?
Using JSON.stringify is certainly a possibility.
An alternative, is to wrap the object (array) in a proxy, and do that for every nested object as well. Then trap all actions that mutate those objects.
Here is how that could look:
function monitor(obj, cb) {
if (Object(obj) !== obj) return obj;
for (let key of Object.keys(obj)) {
obj[key] = monitor(obj[key], cb);
}
return new Proxy(obj, {
defineProperty(...args) {
cb();
return Reflect.defineProperty(...args);
},
deleteProperty(...args) {
cb();
return Reflect.deleteProperty(...args);
},
set(...args) {
cb();
return Reflect.set(...args);
}
});
};
// Example array
let origArray = [{x: 1}, { child: { y: 1} }];
// Activate the proxy:
let dirty = false;
origArray = monitor(origArray, () => dirty = true);
// Perform a mutation
origArray[1].child.y++;
console.log(dirty); // true
console.log(origArray);

How add getters to all objects

How can I add getters (or prototype/method) to all object.
I have an object that look like:
foo.bar.text
//or
foo.bar.child.text
text - is an array of strings, but I need only one of them.
Each time when I get this value, I want get only one fixed index (this index saved in other variable).
So what I need in result:
foo.text = ['a','b']
foo.bar.text = ['c','d']
foo.bar.someChild.text = [null, undefined]
x = 1;
// here we make some magic
console.log(foo.text) // b
console.log(foo.bar.text) // d
console.log(foo.bar.someChild.text) // undefined
So if any object contains an array text, if we try get it, we get not array but some defined item from it.
Manually point on item I can't so foo.bar.text[x] is not an option.
Name of array and variable that we get is optional, for example we can save array in fullText and try get text. As if text = fullText[x].
Can somebody advice how I can implement this, getter, setter, prototype?
Update
Proxy seems is my option, thanks for advice!
I would suggest you apply Proxy recursively to the foo object.
// the code is handwriting without test
var handler = {
get: (target, prop) => {
if (prop === 'text') {
if (target[prop] instanceof Array) {
return target[prop][x];
} else {
// dealing with non-array value
}
} else if (typeof target[prop] === 'object') {
return new Proxy(target[prop], handler);
} else {
return target[prop];
}
}
}
var newFoo = new Proxy(foo, handler);

understanding the iterator protocol

In the notes it states:
The iterable protocol allows JavaScript objects to define or customize
their iteration behavior, such as what values are looped over in a
for..of construct.
I don’t see what benefit this has when I can already use: Object.degineProperty to make something enumerable.
function withValue(value) {
var d = withValue.d || (
withValue.d = {
enumerable: false,
writeable: false,
configuration: false,
value: null
}
)
// other code;
}
What benefit do these protocols have? If this is just some new syntax to appease the new for…of loop, what benefit does it have other than simply checking the length and seeing if its ran out of items in the “list”.
Think of Iterable as an interface. You can be assured implementations contain an Symbol.iterator property, which implements a next() method. If you implement yourself, you can produce the values you want to iterate over at runtime. As a simple example, produce a list and decide later how many (or which, or whatever criteria) you would like to iterate over:
function List (...args) {
this.getOnly = function (limit) (
const effectiveLimit = Math.min(args.length, limit + 1);
const iterable = {
[Symbol.iterator]() {
let count = 0;
const iterator = {
next() {
if (count < effectiveLimit) {
return { value: args[count++] };
} else {
return { done: true };
}
}
};
return iterator;
}
}
return iterable;
};
}
const list = List(0, 1, 2, 3, 4);
for (const x of list.getOnly(3)) {
console.log(x);
}
// returns 0, 1, 2
If you use a Generator function, which implements the Iterable interface, the same gets really simple:
function List (...args) {
this.getOnly = function* (limit) {
const effectiveLimit = Math.min(args.length, limit + 1);
for (let count = 0; count < effectiveLimit; count++) {
yield args[count];
}
}
}
More examples of what you can do with Iterables are listed here.

Constructing a sequence/linked list through inheritance recursively in JavaScript

Background: I'm reading the online book Eloquent JavaScript and one of the exercises in Chapter 6 mentions that a "Sequence" can behave similarly to a linked list. Here is the link to the exercise, and I've copied a bit of relevant text:
Another solution is to avoid changing state in the object. You can expose a method for getting the current element (without advancing any counter) and another for getting a new sequence that represents the remaining elements after the current one (or a special value if the end of the sequence is reached).
I am trying to build a Sequence by recursively calling the implementing class's (ArraySeq) constructor within the interface class. However when running a test in node, I'm getting TypeError: Cannot read property '0' of undefined at Sequence.ArraySeq.
I've copied and pasted my (incomplete) implementation:
/**
* Sequence interface
*/
function Sequence(current, rest) {
this.current = current;
this.rest = rest;
}
Object.defineProperty(Sequence.prototype, "end", {
get: function() {
return this.rest === undefined;
}
});
Sequence.prototype.next = function() {
return this.rest;
};
/**
* Array implementation of sequence
*/
function ArraySeq(array) {
if (array === []) {
Sequence.call(undefined, undefined);
} else {
Sequence.call(array[0], new ArraySeq(array.slice(1)));
}
}
ArraySeq.prototype = Object.create(Sequence.prototype);
/**
* Logs all elements in a Sequence
*/
function logSequence(sequence) {
while (sequence.rest !== undefined) {
console.log(sequence.current);
sequence = sequence.rest;
}
}
logSequence(new ArraySeq([1, 2]));
Thank you for reading this far, any help or guidance is greatly appreciated!
As I noted in comments:
array.splice[1] will give you undefined. You want array.slice(1) - the whole array without the first element: slice, not splice. array.splice(1) will delete the second element from the array and return that element - not what you want.
You wrote Sequence as a constructor, but you are not calling it as a constructor. Instead of Sequence.call, use new Sequence.
Conversely, you are calling new ArraySeq, but ArraySeq does not look like a constructor. Use just ArraySeq, and make it return stuff (return new Sequence...).
Use if (!array.length) to test for array being non-empty. array === [], and even array == [], can never return true, because objects (and thus arrays) are compared based on object identity, not equality, and you just created a new one (so there is no chance it is the same object as something that already existed).
And of course, ArraySequence is not defined; that was supposed to be ArraySeq, right?
With those changes, your code works. EDIT: However, the exercise wants ArraySeq to be an object, so still a bit more work... First of all, "interface" is not an object. It is just how an object should behave. My go at the exercise would be:
function ArraySeq(array) {
this.array = array;
this.index = 0;
}
Object.defineProperty(ArraySeq.prototype, "end", {
get: function() {
return this.index >= this.array.length;
}
});
Object.defineProperty(ArraySeq.prototype, "next", {
get: function() {
return this.array[this.index++];
}
});
/**
* Logs all elements in a Sequence
*/
function logSequence(sequence) {
while (!sequence.end) {
console.log(sequence.next);
}
}
logSequence(new ArraySeq([1, 2]));
The "interface" here is .end and .next. If you want to go the route of your quote, then it changes slightly. The interface here is .end, .rest and .value:
function ArraySeq(array) {
this.array = array;
}
Object.defineProperty(ArraySeq.prototype, "end", {
get: function() {
return this.array.length == 0;
}
});
Object.defineProperty(ArraySeq.prototype, "rest", {
get: function() {
return new ArraySeq(this.array.slice(1));
}
});
Object.defineProperty(ArraySeq.prototype, "value", {
get: function() {
return this.array[0];
}
});
/**
* Logs all elements in a Sequence
*/
function logSequence(sequence) {
while (!sequence.end) {
console.log(sequence.value);
sequence = sequence.rest;
}
}
logSequence(new ArraySeq([1, 2]));
first, splice is method.
array.splice(1) instead of array.splice[1].
and use array.length == 0 in array === [].
if two object is different object, then === operator treated as false even all element is same.

How to create a memoize function

I am stumped with this memoize problem. I need to create a function that will check to see if a value has already been calculated for a given argument, return the previous result, or run the calculation and return that value.
I have spent hours on this and while I am new to JS. I cannot get my head around how to do this. I cannot use any built in functions and would really like to understand what I need to do.
Here is what I have so far, which is so wrong it feels like pseudo-code at this point. I have searched existing memoize questions out here but I cannot seem to make any solution work yet. Any help is much appreciated.
myMemoizeFunc = function(passedFunc) {
var firstRun = passedFunc;
function check(passedFunc){
if(firstRun === undefined){
return passedFunc;
}else{return firstRun;}
}
};
Sorry, I should have been more clear. Here are my specific requirements:
myMemoizeFunc must return a function that will check if the calculation has already been calculated for the given arg and return that val if possible. The passedFunc is a function that holds the result of a calculation.
I understand this may seem like a duplicate, but I am marking as not so, as I am having some serious difficulty understanding what I should do here, and need further help than is given in other posts.
This is what my thought process is bringing me towards but again, I am way off.
myMemoizeFunc = function(passedFunc) {
var allValues = [];
return function(){
for(var i = 0; i < myValues.length; i++){
if(myValues[i] === passedFunc){
return i;
}
else{
myValues.push(passedFunc);
return passedFunc;
}
}
}
};
I should not be returning i or passedFunc here, but what else could I do within the if/else while checking for a value? I have been looking at this problem for so long, I am starting to implement code that is ridiculous and need some fresh advice.
I think the main trick for this is to make an object that stores arguments that have been passed in before as keys with the result of the function as the value.
For memoizing functions of a single argument, I would implement it like so:
var myMemoizeFunc = function (passedFunc) {
var cache = {};
return function (x) {
if (x in cache) return cache[x];
return cache[x] = passedFunc(x);
};
};
Then you could use this to memoize any function that takes a single argument, say for example, a recursive function for calculating factorials:
var factorial = myMemoizeFunc(function(n) {
if(n < 2) return 1;
return n * factorial(n-1);
});
Consider this an extension on the answer of Peter Olson.
For a variable number of arguments you could use something like this.
Note: This example is not optimal if you intent to pass complex arguments (arrays, objects, functions). Be sure to read further and not copy/paste blindly.
function memo(fn) {
const cache = {};
function get(args) {
let node = cache;
for (const arg of args) {
if (!("next" in node)) node.next = new Map();
if (!node.next.has(arg)) node.next.set(arg, {});
node = node.next.get(arg);
}
return node;
}
return function (...args) {
const cache = get(args);
if ("item" in cache) return cache.item;
cache.item = fn(...args);
return cache.item;
}
}
This builds the following cache tree structure:
const memoizedFn = memo(fn);
memoizedFn();
memoizedFn(1);
memoizedFn(1, 2);
memoizedFn(2, 1);
cache = {
item: fn(),
next: Map{ // <- Map contents depicted as object
1: {
item: fn(1),
next: Map{
2: { item: fn(1, 2) }
}
},
2: {
next: Map{
1: { item: fn(2, 1) }
}
}
}
}
This solution leaks memory when passing complex arguments (arrays, object, functions) that are no longer referenced afterwards.
memoizedFn({ a: 1 })
Because { a: 1 } is not referenced after the memoizedFn call it would normally be garbage collected. However now it can't be because cache still holds a reference. It can only be garbage collected once memoizedFn itself is garbage collected.
I showed the above first because it shows the base concept and demonstrates the cache structure in a somewhat simple form. To clean up cache that would normally be garbage collected we should use a WeakMap instead of a Map for complex objects.
For those unfamiliar with WeakMap, the keys are a "weak" reference. This means that the keys do not count towards active references towards an object. Once an object is no longer referenced (not counting weak references) it will be garbage collected. This will in turn remove the key/value pair from the WeakMap instance.
const memo = (function () {
const primitives = new Set([
"undefined",
"boolean",
"number",
"bigint",
"string",
"symbol"
]);
function typeOf(item) {
const type = typeof item;
if (primitives.has(type)) return "primitive";
return item === null ? "primitive" : "complex";
}
const map = {
"primitive": Map,
"complex": WeakMap
};
return function (fn) {
const cache = {};
function get(args) {
let node = cache;
for (const arg of args) {
const type = typeOf(arg);
if (!(type in node)) node[type] = new map[type];
if (!node[type].has(arg)) node[type].set(arg, {});
node = node[type].get(arg);
}
return node;
}
return function (...args) {
const cache = get(args);
if ("item" in cache) return cache.item;
cache.item = fn(...args);
return cache.item;
}
}
})();
const fib = memo((n) => {
console.log("fib called with", n);
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
});
// heavy operation with complex object
const heavyFn = memo((obj) => {
console.log("heavyFn called with", obj);
// heavy operation
return obj.value * 2;
});
// multiple complex arguments
const map = memo((iterable, mapFn) => {
console.log("map called with", iterable, mapFn);
const result = [];
for (const item of iterable) result.push(mapFn(item));
return result;
});
console.log("### simple argument demonstration ###");
console.log("fib(3)", "//=>", fib(3));
console.log("fib(6)", "//=>", fib(6));
console.log("fib(5)", "//=>", fib(5));
console.log("### exlanation of when cache is garbage collected ###");
(function () {
const item = { value: 7 };
// item stays in memo cache until it is garbade collected
console.log("heavyFn(item)", "//=>", heavyFn(item));
console.log("heavyFn(item)", "//=>", heavyFn(item));
// Does not use the cached item. Although the object has the same contents
// it is a different instance, so not considdered the same.
console.log("heavyFn({ value: 7 })", "//=>", heavyFn({ value: 7 }));
// { value: 7 } is garbade collected (and removed from the memo cache)
})();
// item is garbade collected (and removed from memo cache) it is no longer in scope
console.log("### multiple complex arguments demonstration ###");
console.log("map([1], n => n * 2)", "//=>", map([1], n => n * 2));
// Does not use cache. Although the array and function have the same contents
// they are new instances, so not considdered the same.
console.log("map([1], n => n * 2)", "//=>", map([1], n => n * 2));
const ns = [1, 2];
const double = n => n * 2;
console.log("map(ns, double)", "//=>", map(ns, double));
// Does use cache, same instances are passed.
console.log("map(ns, double)", "//=>", map(ns, double));
// Does use cache, same instances are passed.
ns.push(3);
console.log("mutated ns", ns);
console.log("map(ns, double)", "//=>", map(ns, double));
The structure stays essentially the same, but depending on the type of the argument it will look in either the primitive: Map{} or complex: WeakMap{} object.
const memoizedFn = memo(fn);
memoizedFn();
memoizedFn(1);
memoizedFn(1, 2);
memoizedFn({ value: 2 }, 1);
cache = {
item: fn(),
primitive: Map{
1: {
item: fn(1),
primitive: Map{
2: { item: fn(1, 2) }
}
}
},
complex: WeakMap{
{ value: 2 }: { // <- cleared if { value: 2 } is garbage collected
primitive: Map{
1: { item: fn({ value: 2 }, 1) }
}
}
}
}
This solution does not memoize any errors thrown. Arguments are considered equal based on Map key equality. If you also need to memoize any errors thrown I hope that this answer gave you the building blocks to do so.
There are a number of memoization libraries available. Doing memoization efficiently is not as straight forward as it seems. I suggest a library be used. Two of the fastest are:
https://github.com/anywhichway/iMemoized
https://github.com/planttheidea/moize
See here for a comprehensive(-ish) list of memoization libraries: https://stackoverflow.com/a/61402805/2441655

Categories

Resources