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);
Related
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);
Here's the thing.. I want to overwite an object with if-else condition inside angular.forEach with this controller, basically "k1" has an original keyword and I want to replace it with a new keyword. what do you think is the best way to display the new input object?
$scope.SubmitKeyword = function (key, new_keyword) {
console.log(key, new_keyword)
$scope.new_keyword = new_keyword;
if ($scope.new_keyword == null || $scope.new_keyword == undefined || $scope.new_keyword == "") {
alert('Invalid input!')
return
}
angular.forEach($scope.new_campaign_keywords, function (v, k) {
console.log(v,k)
if (k == key) {
if (v['orig_keyword'] == new_keyword) {
alert('No Changes Found!')
return
} else {
console.log('changes detected')
var a = 0;
angular.forEach($scope.campaigns, function (v1,k1) {
a++
console.log(k1)
if (k1 == a) {
//display the new keyword
else {
//remain the original keyword
}
}
})
}
}
})
};
enter image description here
enter image description here
Use the hasOwnProperty method to detect the existence of a property:
$scope.SubmitKeyword = function (key, new_keyword) {
var obj = $scope.new_campaign_keywords;
console.log(key, new_keyword)
$scope.new_keyword = new_keyword;
if (!new_keyword) {
alert('Invalid input!');
return;
};
if (obj.hasOwnProperty(new_keyword)) {
alert('New keyword in use');
return;
};
//ELSE
console.log('changes detected');
obj[new_keyword] = obj[key];
delete obj[key];
};
Use the delete operator to remove the old keyword.
You have different options to do this, if you are using ES6 or Typescript you can use the spread operator to copy only certain properties from an Object
let obj= { bar: true, foo1: true, foo2:true};
if(someValidation){
let { bar, ...rest} = obj;
obj = {
...rest,
newKey: bar, //or whatever value/key you want
}
}
Now since I see ES5 code only on your code example, I would argue that you are complicating things a little bit, first of all is not a good idea to use a forEach to modify the same array you are iterating over.
Use a map function and do the changes there. You can use the delete operator to eliminate the property you don't want, but is well know that has some performance issues, so let's go with this approach:
$scope.campaigns = $scope.campaigns.map(function (v) {
a++
if (condition) {
return {
newBar: v.bar,
foo1: v.foo1,
foo2: v.foo2,
};
}
return v;
});
The other benefit is that now you are making a new array and replacing the current $scope variable value once, rather than modifying every single element, since the map return value will be done once the new list is created.
If you need to iterate over the keys dynamically you can use Object.keys to iterate over them and do the same approach, create a new object, and return it to the map, or return the same initial object.
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.
As demonstrated in this SO question, Proxy objects are a good way to watch for changes in an object.
What if you want to watch changes to subobjects? You'd need to proxy those subobjects as well.
I'm currently working with some code that does that automatically - any time you try to set a property it makes sure that it is wrapped in a proxy. We're doing this so that we can perform an action every time any value changes.
function createProxiedObject(objToProxy) {
return new Proxy(objToProxy, {
set: function (target, key, value) {
//proxy nested objects
if (value !== null && typeof value === 'object') {
value = createProxiedObject(value);
}
target[key.toString()] = value;
handleProxiedObjectChange();
});
This works pretty well, but there is at least one case in which this backfires:
function ensureObjectHasProperty(object, key, default) {
if (object[key] !== null) {
// some validation happens here
return object[key];
} else {
return default;
}
}
...
proxiedObject = somethingThatCreatesAProxiedObject(someValue);
proxiedObject[someProperty] = ensureObjectHasProperty(proxiedObject, someProperty, defaultValue)
The result is that the value under someProperty (which is already being proxied) gets reassigned to the proxied object, causing it to get wrapped in another proxy. This results in the handleProxiedObjectChange method being called more than once each time any part of the object changes.
The simple way to fix it is to never assign anything to the proxied object unless it's new, but as this problem has already happened there's a reasonable chance someone will do it again in the future. How can I fix the set function to not rewrap objects that are already being proxied? Or is there a better way to watch an object so that handleProxiedObjectChange can be called any time the object or any of its subobjects change?
As suggested by #DecentDabbler, using a WeakSet allowed me to ensure I never try to wrap a proxy in another proxy:
const proxiedObjs = new WeakSet();
...
function createProxiedObject(objToProxy) {
// Recursively ensure object is deeply proxied
for (let x in objToProxy) {
subObj = objToProxy[x];
if (subObj !== null && typeof subObj === 'object' && !proxiedObjs.has(subObj)) {
objToProxy[x] = createProxiedObject(subObj);
}
}
let proxied = new Proxy(objToProxy, {
set: function (target, key, value) {
//This check is also new - if nothing actually changes
//I'd rather not call handleProxiedObjectChange
if (_.isEqual(target[key.toString()], value)) {
return true;
}
//proxy nested objects
if (value !== null && typeof value === 'object' && !proxiedObjs.has(value)) {
value = createProxiedObject(value);
}
target[key.toString()] = value;
handleProxiedObjectChange();
});
proxiedObjs.add(proxied);
return proxied;
I have following code
var s = storage('foo');
s.bar = 100;
storage('foo', s);
storage returns an object from some storage media. I then change the value of one property of the object and send it to storage again. My question is is there any way to make it a one liner? Something like
storage('foo', storage('foo').bar = 100);
But the above code only saves 100 instead of entire foo object.
Here is the storage function, it stores on localstorage but makes it object storage instead of string storage:
function storage(item, value) {
if(value!=null) {
var result = (storage()) ? storage() : {};
if(item) result[item] = value;
return localStorage.setItem('storage', JSON.stringify(result))
} else return (item) ? storage()[item] : JSON.parse(localStorage.getItem('storage'));
}
Edit:
So I ended up with following one line change in storage function. But this does not account for multidimensional usage. So I will welcome any suggestions.
function storage(item, value, property) {
...
if(item) (property) ? result[item][property] = value : result[item] = value;
...
}
The reasonably thing to do, if you cannot change the storage function, is to create another function:
function set_storage(obj, prop, val) {
var s = storage(obj);
obj[prop] = val;
storage(obj, s);
}
// usage
set_storage('foo', 'bar', 100);
If you can change storage though, this would be an even better way. jQuery.data works similarly.
There are ways to do this in (almost) one line, with the help of the comma operator [MDN], but it ain't pretty:
var s = storage('foo');
storage('foo', (s.bar = 100, s));
Since you said storage is your function, overloading it seems to be a good way. It would go like this:
function storage(key, prop, val) {
var storage = JSON.parse(localStorage.getItem('storage'));
if(arguments.length === 0) {
return storage;
}
else if (arguments.length === 1) {
// only one argument passed, return the value with key `key`
return storage[key];
}
else if (arguments.length === 2) {
// set `key` to `prop` (`prop` contains the actual value)
storage[key] = prop;
}
else {
// set property `prop` of object in `key` to value `val`
storage[key][prop] = val;
}
localStorage.setItem('storage', JSON.stringify(storage));
}
// Usage
// get `foo`:
var foo = storage('foo');
// set `foo`:
storage('foo', {bar: 42});
// set foo.bar
storage('foo', 'bar', 100);
I hope it gives you some idea.
As storage returns an object, theres a (very) good chance you can do*;
storage('foo').bar = 100;
console.log(storage('foo').bar); // should give 100.
... directly, as objects are "pass by reference"-esque.
* A situation where this would not be the case would be where storage returns a copy of the object stored, rather than the object itself; therefore any updates you make to the returned object will not have an affect on the object in storage.
You can make a function which accepts a function that changes the object:
function changeObject(key, func) {
var obj = storage(key);
func(obj);
storage(key, obj);
}
changeObject('foo', function (s) { s.bar = 100; });