JavaScript nasted default parameters - javascript

Lets say I want to process some property x of objects in collection array. But collection may contain objects without such property or even undefineds. For example
let array = [
{x: 1},
{x: 2},
{},
{x: 4},
undefined
]
The idea is protect my self from such edge cases with default parameter. Let it be 0. I was trying to solve this as
array.map(({x: x = 0}) => process(x))
But it fails on undefined. Is there any way to solve this issue with default parameters and destructuring without writing check/set code inside of map function?

You can give the default object a default value
array.map(({x : x = 0} = 0) => process(x));

You can use .filter before .map in order to clean all falsy values like null, 0, '', false
array = array
.filter((el) => el)
.map(({x: x = 0}) => process(x));
Example
In MDN there is good description
Default function parameters allow formal parameters to be initialized with default values if no value or undefined is
passed.
so null is value., so if you pass null to function default value does not used, for example
function test(x = 10) {
console.log(x);
}
test(undefined); // 10
test(); // 10
test(null); // null
test(0); // 0
test(''); // ''
Example

Related

How to inject values from an array into an object using JavaScript?

I would like to extract values from an object (done), which get put into an array (done), then those values are changed (by tweening library) and returned to me as an array of values.
I would like to insert those values back into an object of the same shape from whence they came.
But I'm stuck on something fairly basic, and certainly do-able.
I have an example state, like so (it could be a totally different shape, this is just an example):
var state = {
ignore: "me",
count: 0,
foo: 10,
bar: {
baz: 99
}
}
I have a new state, that I want to pass into my tweening method:
var newState = {
count: 100,
foo: 50,
bar: {
baz: 999
}
}
There's a tween library in the tweenState method, that produces an array of these values at each "frame" of the animation:
// NOTE: I only pass in the properties I wanna tween!!
myApp.tweenState({ count: 100, foo: 50, bar: { baz: 999 } })
// internally, the tween lib produces something like this on each frame:
tweenedValues = [ 50.1231, 34.43543, 456.4543 ]
I would like to insert these tweened values into an object of the same shape as the state object I'm using, to create this on each frame (as per example state above):
tweenedState = {
ignore: "me",
count: 50.1231,
foo: 34.43543,
bar: {
baz: 456.4543
}
}
...so far, I only have this terrible code (called on each frame):
// make sure tweened values are in an array
var valuesArr = Array.isArray(tweenedValues) ? tweenedValues : [tweenedValues]
// this object will hold tween values of current frame/progress,
// set to original state for now... we'll update it's values from
// valuesArr later
var tweenedState = self.state
// set the newState properties with new values from valuesArr
// (newState is the state passed into the tweenState() method)
Object.keys(newState).forEach((key, index) => {
if (typeof newState[key] === "object") {
Object.keys(newState[key]).forEach(key2 => {
tweenedState[key][key2] = valuesArr[index]
})
} else {
tweenedState[key] = valuesArr[index]
}
})
console.log("tweenedState: ", tweenedState)
Which has these obvious problems:
not recursive, can't go more than 2 levels deep into an object
in any case, the line tweenedState[key][key2] = valuesArr[index] doesn't set the value as expected
when I made it into a recursive function, it looped forever and crashed
So, I need a function that takes the array, and inserts the values into the correct place - namely the properties passed in to begin with (which creates the tweenedState object).
EDIT: Obviously, the state object may look totally different - the one given is just an example - It may be many levels deep, and may contain other stuff NOT passed in.. The key point is that I end up with a state object exactly like the original, but with the tweened values inserted instead.
And I repeat - the Tween library creates the array of values, not me, not my choice. And yes I know object properties are not always in the same order.
EDIT 2: this code has to be isomorphic - working in recent browsers (don't care about IE) and Node 10 or later, ideally without polyfills...
EDIT 3: NOW SOLVED:
Both NinaScholz and mashi have provided answers that worked for me (after I fixed a bug elsewhere).
Ninas answer has stricter JS version requirements, and is a little less portable than mashis answer. On that basis I have accepted mashis answer, though Ninas answer is very nice, too.
Thanks.
function setTweenedValues(state, values) {
const reducer = (newState, [key, val]) => {
newState[key] = val;
if (typeof val === "number") {
newState[key] = values.shift();
}
if (typeof val === "object") {
newState[key] = Object.entries(val).reduce(reducer, {});
}
return newState;
};
const newState = Object.entries(state).reduce(reducer, {});
return newState;
}
var state = { count: 200, foo: 10, bar: { zzz: 999 }, ignore: "me" },
tweenedValues = [50.1231, 34.43543, 456.4543],
tweenedState = setTweenedValues(state, [...tweenedValues]);
console.log(tweenedState);
You could iterate the entries and call the function again for nested entries.
function setValues(pattern, values) {
return Object.fromEntries(Object
.entries(pattern)
.map(([k, v]) => [
k,
v && typeof v === 'object'
? setValues(v, values)
: values.length ? values.shift() : v
])
);
}
var state = { count: 200, foo: 10, bar: { zzz: 999 }, ignore: "me" },
tweenedValues = [50.1231, 34.43543, 456.4543],
tweenedState = setValues(state, [...tweenedValues]);
console.log(tweenedState);
as i see it you have values in an array that are always in a guaranteed order, and you want them as prop values in an object... you could use object destructuring
const [count, foo, bar] = tweenedValues;
then make object like this
let obj = {count, foo, bar}

How to use Array.prototype.includes() to find an object?

Sample Code:
const a = {val: 1};
const b = {val: 2};
const list = [a, b];
console.info(list.includes(a)); // true
console.info(list.includes({val: 1})); // false
Questions:
Why does the second statement evaluate as false?
How can I properly use this method to search for a specific object in an array of objects?
TL;TR
list.some(value => JSON.stringify(value) === JSON.stringify({val: 1}));
Answers:
First, variable a is a reference link to object. If you checking using list.includes(a) it returns true because it found link to same object you declared previously const a = {val: 1};.
Second, list.includes({val: 1}) returns false, because you are trying to search for reference to newly created object - {val: 1}. Object may contain same values and structured the same, but they store in memory as totally different objects.
If you want to check same object by structure, use Array.prototype.some() and write comparator function for your case and logic.
This basically boils down to this:
{ val: 1 } === { val: 1 } // false
Objects in javascript are compared by reference, and as the objects are at different positions in memory, they are not the same. To check for an object that has val set to 1 ypu have to manually search through all objects:
if(list.some(el => el.val === 1))

If I repeat a rxjs.Observable.of(obj), is obj cloned? Why I can't modify it?

As I understand, javascript passes objects as reference. So if I call a function with an object as an argument, the function can modify it. I wrote this code:
var rxjs = require("rxjs")
var obj = {}
var stream = rxjs.Observable.of(obj).
do((x)=>{x.index=isNaN(x.index)?x.index=0:x.index++; return x}).
repeat(10).
subscribe(console.log);
It defines the index property on obj if it does not exist yet, and increases it if it already exists. Since I'm creating an observable of an object, the observable should be able to modify the obj, right? So I should expect to see the output:
{index: 0}
{index: 1}
{index: 2}
...
but what happens is that I see
{index: 0}
{index: 0}
{index: 0}
...
It's not cloned and you can modify it, should you so desire.
Your problem is that the postfix ++ operator returns the previous value, so the value assigned to x.index will always be zero. Use the prefix ++ instead:
do(x => { x.index = isNaN(x.index) ? x.index = 0 : ++x.index; return x; })
Also, the return value from the next function passed to do is ignored, so the return is redundant.
Alternatively, you could use the postfix ++ operator and just drop the assignment to x.index:
do(x => isNaN(x.index) ? x.index = 0 : x.index++)

JavaScript default parameters for function

I can fully understand ECMAScript 6 has created a lot of potential way of handling with functions such as arrow functions.
Since I'm not very familiar with the new stuff, when talking about default parameters for a function. How to interpret the differences between the following way of defining functions:
Function 1:
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
Function 2:
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
The difference is clear when you try passing something to your functions:
m1({}) // [0, 0]
m1({z: 1}) // [0, 0]
m1({x: 1}) // [1, 0]
m2({}) // [undefined, undefined]
m2({z: 1}) // [undefined, undefined]
m2({x: 1}) // [1, undefined]
Your first syntax (m1({x = 0, y = 0} = {})) does three things:
First, it provides a default first argument to the function, which is an empty object. If no first argument is given (m1()) then the default empty object is used (i.e. it becomes m1({}))
Second, your code extracts the x and y properties from that object.
If either is undefined, it is given a default value 0.
m2({x, y} = { x: 0, y: 0 }) does something quite different:
First it provides a default first parameter to the function, which is the object {x: 0, y: 0}. If no first argument is passed, that object is used. If any argument other than undefined is passed, that value is used instead.
Second, the code extracts the x and y properties from that object. If they are undefined, that's what you'll get.
The first option (a parameter with a default value that is destructured with more default values) is almost certainly what you want. The second option means that your code does not have sensible/useful default values for the property if arguments are passed.
m1 provides default values for x and y, whereas m2 merely destructures x and y from a provided object and only provides default values if the object itself isn’t provided:
m1({}) will return [0, 0]
m2({}) will return [undefined, undefined]
Both m1() and m2() will return [0, 0]
m1({x: 10}) will return [10, 0]
m2({x: 10}) will return [10, undefined]
So, if m2 receives an object, it will destructure the available values to the variables x and y. If any of them is missing, it’s undefined. Only if the whole object is missing, it’ll provide a default object ({ x: 0, y: 0 }) from which to get the values.
m1, however, provides default values for both properties even if they’re missing. And if the whole object is missing, it’ll still provide those default values.

Design pattern to check if a JavaScript object has changed

I get from the server a list of objects
[{name:'test01', age:10},{name:'test02', age:20},{name:'test03', age:30}]
I load them into html controls for the user to edit.
Then there is a button to bulk save the entire list back to the database.
Instead of sending the whole list I only want to send the subset of objects that were changed.
It can be any number of items in the array. I want to do something similar to frameworks like Angular that mark an object property like "pristine" when no change has been done to it. Then use that flag to only post to the server the items that are not "pristine", the ones that were modified.
Here is a function down below that will return an array/object of changed objects when supplied with an old array/object of objects and a new array of objects:
// intended to compare objects of identical shape; ideally static.
//
// any top-level key with a primitive value which exists in `previous` but not
// in `current` returns `undefined` while vice versa yields a diff.
//
// in general, the input type determines the output type. that is if `previous`
// and `current` are objects then an object is returned. if arrays then an array
// is returned, etc.
const getChanges = (previous, current) => {
if (isPrimitive(previous) && isPrimitive(current)) {
if (previous === current) {
return "";
}
return current;
}
if (isObject(previous) && isObject(current)) {
const diff = getChanges(Object.entries(previous), Object.entries(current));
return diff.reduce((merged, [key, value]) => {
return {
...merged,
[key]: value
}
}, {});
}
const changes = [];
if (JSON.stringify(previous) === JSON.stringify(current)) {
return changes;
}
for (let i = 0; i < current.length; i++) {
const item = current[i];
if (JSON.stringify(item) !== JSON.stringify(previous[i])) {
changes.push(item);
}
}
return changes;
};
For Example:
const arr1 = [1, 2, 3, 4]
const arr2 = [4, 4, 2, 4]
console.log(getChanges(arr1, arr2)) // [4,4,2]
const obj1 = {
foo: "bar",
baz: [
1, 2, 3
],
qux: {
hello: "world"
},
bingo: "name-o",
}
const obj2 = {
foo: "barx",
baz: [
1, 2, 3, 4
],
qux: {
hello: null
},
bingo: "name-o",
}
console.log(getChanges(obj1.foo, obj2.foo)) // barx
console.log(getChanges(obj1.bingo, obj2.bingo)) // ""
console.log(getChanges(obj1.baz, obj2.baz)) // [4]
console.log(getChanges(obj1, obj2)) // {foo:'barx',baz:[1,2,3,4],qux:{hello:null}}
const obj3 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 30 }]
const obj4 = [{ name: 'test01', age: 10 }, { name: 'test02', age: 20 }, { name: 'test03', age: 20 }]
console.log(getChanges(obj3, obj4)) // [{name:'test03', age:20}]
Utility functions used:
// not required for this example but aid readability of the main function
const typeOf = o => Object.prototype.toString.call(o);
const isObject = o => o !== null && !Array.isArray(o) && typeOf(o).split(" ")[1].slice(0, -1) === "Object";
const isPrimitive = o => {
switch (typeof o) {
case "object": {
return false;
}
case "function": {
return false;
}
default: {
return true;
}
}
};
You would simply have to export the full list of edited values client side, compare it with the old list, and then send the list of changes off to the server.
Hope this helps!
Here are a few ideas.
Use a framework. You spoke of Angular.
Use Proxies, though Internet Explorer has no support for it.
Instead of using classic properties, maybe use Object.defineProperty's set/get to achieve some kind of change tracking.
Use getter/setting functions to store data instead of properties: getName() and setName() for example. Though this the older way of doing what defineProperty now does.
Whenever you bind your data to your form elements, set a special property that indicates if the property has changed. Something like __hasChanged. Set to true if any property on the object changes.
The old school bruteforce way: keep your original list of data that came from the server, deep copy it into another list, bind your form controls to the new list, then when the user clicks submit, compare the objects in the original list to the objects in the new list, plucking out the changed ones as you go. Probably the easiest, but not necessarily the cleanest.
A different take on #6: Attach a special property to each object that always returns the original version of the object:
var myData = [{name: "Larry", age: 47}];
var dataWithCopyOfSelf = myData.map(function(data) {
Object.assign({}, data, { original: data });
});
// now bind your form to dataWithCopyOfSelf.
Of course, this solution assumes a few things: (1) that your objects are flat and simple since Object.assign() doesn't deep copy, (2) that your original data set will never be changed, and (3) that nothing ever touches the contents of original.
There are a multitude of solutions out there.
With ES6 we can use Proxy
to accomplish this task: intercept an Object write, and mark it as dirty.
Proxy allows to create a handler Object that can trap, manipulate, and than forward changes to the original target Object, basically allowing to reconfigure its behavior.
The trap we're going to adopt to intercept Object writes is the handler set().
At this point we can add a non-enumerable property flag like i.e: _isDirty using Object.defineProperty() to mark our Object as modified, dirty.
When using traps (in our case the handler's set()) no changes are applied nor reflected to the Objects, therefore we need to forward the argument values to the target Object using Reflect.set().
Finally, to retrieve the modified objects, filter() the Array with our proxy Objects in search of those having its own Property "_isDirty".
// From server:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Mirror data from server to observable Proxies:
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
Object.defineProperty(ob, "_isDirty", {value: true}); // Flag
return Reflect.set(...arguments); // Forward trapped args to ob
}
}));
// From now on, use proxied data. Let's change some values:
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
// Collect modified data
const dataMod = data.filter(ob => ob.hasOwnProperty("_isDirty"));
// Test what we're about to send back to server:
console.log(JSON.stringify(dataMod, null, 2));
Without using .defineProperty()
If for some reason you don't feel comfortable into tapping into the original object adding extra properties as flags, you could instead populate immediately
the dataMod (array with modified Objects) with references:
const dataOrg = [
{id:1, name:'a', age:10},
{id:2, name:'b', age:20},
{id:3, name:'c', age:30}
];
// Prepare array to hold references to the modified Objects
const dataMod = [];
const data = dataOrg.map(ob => new Proxy(ob, {
set() {
if (dataMod.indexOf(ob) < 0) dataMod.push(ob); // Push reference
return Reflect.set(...arguments);
}
}));
data[0].name = "Lorem";
data[0].age = 42;
data[2].age = 31;
console.log(JSON.stringify(dataMod, null, 2));
Can I Use - Proxy (IE)
Proxy - handler.set()
Global Objects - Reflect
Reflect.set()
Object.defineProperty()
Object.hasOwnProperty()
Without having to get fancy with prototype properties you could simply store them in another array whenever your form control element detects a change
Something along the lines of:
var modified = [];
data.forEach(function(item){
var domNode = // whatever you use to match data to form control element
domNode.addEventListener('input',function(){
if(modified.indexOf(item) === -1){
modified.push(item);
}
});
});
Then send the modified array to server when it's time to save
Why not use Ember.js observable properties ? You can use the Ember.observer function to get and set changes in your data.
Ember.Object.extend({
valueObserver: Ember.observer('value', function(sender, key, value, rev) {
// Executes whenever the "value" property changes
// See the addObserver method for more information about the callback arguments
})
});
The Ember.object actually does a lot of heavy lifting for you.
Once you define your object, add an observer like so:
object.addObserver('propertyKey', targetObject, targetAction)
My idea is to sort object keys and convert object to be string to compare:
// use this function to sort keys, and save key=>value in an array
function objectSerilize(obj) {
let keys = Object.keys(obj)
let results = []
keys.sort((a, b) => a > b ? -1 : a < b ? 1 : 0)
keys.forEach(key => {
let value = obj[key]
if (typeof value === 'object') {
value = objectSerilize(value)
}
results.push({
key,
value,
})
})
return results
}
// use this function to compare
function compareObject(a, b) {
let aStr = JSON.stringify(objectSerilize(a))
let bStr = JSON.stringify(objectSerilize(b))
return aStr === bStr
}
This is what I think up.
It would be cleanest, I’d think to have the object emit an event when a property is added or removed or modified.
A simplistic implementation could involve an array with the object keys; whenever a setter or heck the constructor returns this, it first calls a static function returning a promise; resolving: map with changed values in the array: things added, things removed, or neither. So one could get(‘changed’) or so forth; returning an array.
Similarly every setter can emit an event with arguments for initial value and new value.
Assuming classes are used, you could easily have a static method in a parent generic class that can be called through its constructor and so really you could simplify most of this by passing the object either to itself, or to the parent through super(checkMeProperty).

Categories

Resources