Can we set persistent default parameters which remain set until explicitly changed? - javascript

The below is a function fn where expected result is for a, b, c to defined at every call of fn, whether an object parameter is passed or not. If object is passed which sets property, property should be set only for that object.
const fn = (opts = {a:1, b:2, c:3}) => console.log(opts);
when called without parameters the result is
fn() // {a: 1, b: 2, c: 3}
when called with parameter, for example {b:7}, the expected result is
fn({b:7}) // {a: 1, b: 7, c: 3}
however, the actual result is
fn({b:7}) // {b: 7}
Was able to get expected result by defining an object outside of function and using Object.assign() within function body
const settings = {a: 1, b: 2, c: 3};
const fn = opts => {opts = Object.assign({}, settings, opts); console.log(opts)}
fn({b: 7}) // {a: 1, b: 7, c: 3}
fn(); // {a: 1, b: 2, c: 3}
/*
// does not log error; does not return expected result
const fn = (opts = Object.assign({}, settings, opts)) => console.log(opts)
*/
Can the above result be achieved solely utilizing default parameters, without defining an object to reference outside of function parameters or within function body?

Maybe I misunderstood the question, but you seem to be looking for default initialisers for each separate property. For that, you have to use destructuring:
const fn = ({a = 1, b = 2, c = 3} = {}) => console.log({a, b, c});
If you want to keep arbitrary properties, not just those that you know of up front, you might be interested in the object rest/spread properties proposal that allows you to write
const fn = ({a = 1, b = 2, c = 3, ...opts} = {}) => console.log({a, b, c, ...opts});
Can an opts variable as the single object reference be achieved solely utilizing default parameters, without defining an object to reference outside of function parameters or within function body?
No. Parameter declarations are only able to initialise variables with (parts of) the arguments, and possibly (as syntactic sugar) with default values when no or undefined argument (parts) are passed. They are not able to carry out unconditional computations and create variables inialised from the results - which is what you attempt to achieve here.
You are supposed to use the function body for that.

No
The best that can be done is either your own answer or this:
const fn = (default_parameters) => {
default_parameters = Object.assign({}, {a: 1, b: 2, c: 3},default_parameters);
console.log('These are the parameters:');
console.log(default_parameters);
}
fn();
fn({b: 7});
fn({g: 9, x: 10});
The default parameter block is only executed if the value is not set, so your own answer is the best that is on offer ie use two parameters
You can convince yourself of this by creating a code block that will fail if executed and testing that passing a parameter works (to show that the code block is not executed) and testing that not passing a parameter fails (showing that the code block is only executed when no parameter is passed).
This should demonstrate clearly that any paramter passed will prevent the default parameter from being evaluated at all.
const fn = (default_parameters = (default_parameters = Object.assign({}, {a: 1, b: 2, c: 3},default_parameters))) => {
console.log('These are the parameters:');
console.log(default_parameters);
}
fn({b: 7});
fn();
fn({g: 9, x: 10});

We can set fn as a variable which returns an arrow function expression. When called set a, b, c and rest parameters reference using spread element at new object, which is returned when the function is invoked.
const fn = ((...opts) => ({a:1,b:2,c:3, ...opts.pop()}));
let opts = fn();
console.log(opts);
opts = fn({b: 7});
console.log(opts);
opts = fn({g: 9, x: 10});
console.log(opts);
Using rest element, Object.assign(), spread element, Array.prototype.map(), setting element that is not an object as value of property reflecting index of element in array.
const fn = ((...opts) => Object.assign({a:1,b:2,c:3}, ...opts.map((prop, index) =>
prop && typeof prop === "object" && !Array.isArray(prop)
? prop
: {[index]:prop}))
);
let opts = fn([2,3], ...[44, "a", {b:7}, {g:8, z: 9}, null, void 0]);
console.log(opts);

Though code at OP uses single default parameter, until we locate or develop a procedure for using only single parameter, we can utilize setting two default parameters to achieve expected result.
The first parameter defaults to a plain object, at second default parameter we pass parameter identifier from first parameter to Object.assign() following pattern at Question.
We reference second parameter identifier of function fn to get the default parameters when called without parameters; when called with first parameter having properties set to properties of object passed at first parameter and default parameters, the former overwriting the latter at the resulting object.
const fn = (__ = {}, opts = Object.assign({}, {a: 1, b: 2, c: 3}, __)) =>
console.log(opts);
fn();
fn({b: 7});
fn({g: 9, x: 10});

Related

Intercept destructured function parameters in JavaScript?

Is there any way or pattern that would allow me to intercept destructured function arguments and do something with them before the function is executed, without knowing the argument signature beforehand? I.e. something like:
const add = ({a, b}) => a + b
const multiplyArgs = (fun) => (...args) => fun(...args.map(x => x * 10)) // Won't work
const res = multiplyArgs(add)({a: 5, b: 10, c: 20}) // 150, won't perform the multiplication on c
The only option I found so far is using a regex to get the arguments from the string representation of the function, but that's very messy.
EDIT:
The actual use case is this:
I have an object with RxJS observables/subjects, and would like to be able to call a function/method that would take in another function, pick the required observables from the object, combine them, and then pipe the function to the new combined observable.
const observablePool = {a: new Rx.BehaviorSubject(5), b: new Rx.BehaviorSubject(10)}
updatePool( ({a, b}) => ({c: a + b}) )
// In the background:
// const picked = {a: observablePool.a, b: observablePool.b}
// observablePool.c = Rx.combineLatest(picked)
// .pipe(Rx.map({a, b} => a + b))
The idea is to hide the implementation of accessing the observables object and creating new combined observables. The user should be able to chain simple functions whose results would get lifted into observables automatically.
I can do it by adding a pick() function, i.e. updatePool(pick("a", "b"), ({a, b}) => ({c: a + b}) ) but that duplicates and decouples the argument names. I was wondering if there was a more elegant solution.
For just one object argument, you could map over Object.entries.
const add = ({a, b}) => a + b;
const multiplyObj0 = fun => obj => fun(Object.fromEntries(Object.entries(obj)
.map(([k, v])=>[k, v * 10])));
const res = multiplyObj0(add)({a: 5, b: 10});
console.log(res);

Spread inside JSON reviver function

I Would like to be able to spread out the value of an entry inside a JSON.parse reviver function while also drop the original key.
Returning a spreaded value doesn't seem to do that trick.
here is an example:
const json = {a: 'foo', b: {c: 1, d: '2'}}
const stringify = JSON.stringify(json)
const parsed = JSON.parse(stringify, function(k, v) {
if(k === 'b') {
return {...v}
} else {
return v
}
})
document.getElementById("app").innerHTML = `<pre>${JSON.stringify(parsed, null, 2)}</pre>`;
<div id="app"></div>
In the above the desired output should be
{
"a": "foo",
"c": 1,
"d": "2"
}
While I wouldn't recommend it as it's not very intuitive, you can use Object.assign(this, v) to merge with the this value in your replacer. This merges object at the b property into the object that the b property appears in, temporarily giving you:
{a: 'foo', b: {c: 1, d: '2'}, c: 1, d: '2'}
and then b is removed as we return undfined for that path, giving:
{a: 'foo', c: 1, d: '2'}
const json = {a: 'foo', b: {c: 1, d: '2'}};
const stringify = JSON.stringify(json)
const parsed = JSON.parse(stringify, function(k, v) {
if(k === 'b') {
Object.assign(this, v);
} else {
return v;
}
});
document.getElementById("app").innerHTML = `<pre>${JSON.stringify(parsed, null, 2)}</pre>`;
<div id="app"></div>
JSON.parse reviver only transform the value that is at key "b" and set it on that key. I would picture it similar to a map.
Your solution should be outside the reviver function. Here is a sample of how your code could look like:
let { b, ...parsed } = JSON.parse(stringify);
if (b) {
parsed = { ...parsed, ...b };
}
#Geo Mircean answer is correct, but your comment add new information to the question.
If you have a complex structure, I don't think there is an easier option. The first one that comes to mind is to iterate the object and see if you need to spread each field. The logic to tell if you need to spread is up to you, but it will be something like:
let parsed = {...jsonObject};
for(let key in jsonObject)
{
if (typeof jsonObject[key] === 'object') // if you want to spread
{
delete parsed[key]; // delete the actual key
Object.assign(parsed, jsonObject[key]); // add fields to object
}
}
Notice that the parsed variable will have our result. At first it get the whole object, then we iterate over the result, delete the field and spread the field value itself.
The Object.assign came from this StackOverflow question and does not work in IE9 (but I personally don't care about that).

Destructure and retrieve the fully structured variable in one expression [duplicate]

This question already has answers here:
Destructure object parameter, but also have reference to the parameter as an object? [duplicate]
(2 answers)
Closed 3 years ago.
Is it possible to both destructure a variable and have access to the structured variable in the same call?
For example, what could I replace ??? below to get the desired output (and how else might I have to edit my code)
const foo = ({ a, b }) => {
console.log(a) // 1
console.log(b) // 2
console.log(???) // { a: 1, b: 2 }
}
const x = { a: 1, b: 2 }
foo(x)
My goal is knowledge and succinct code - I want to avoid const { a, b } = params as the first line of foo() in the case where I might need to pass the entire params object on.
If the first argument is an object whose reference you want, it's not possible - once you destructure an argument, there's no longer any way to reference to the full argument, only some of its properties.
It would be possible if the object and parts you wanted was part of a larger object, though, because then you can reference the property name alone (to get the object into a variable), and then reference it again to destructure, eg:
const foo = ({ obj: { a, b }, obj }) => {
console.log(a) // 1
console.log(b) // 2
console.log(obj) // { a: 1, b: 2 }
}
const obj = { a: 1, b: 2 }
foo({ obj })
Your original code could work to an extent if the object had a property that referenced itself, but that's pretty weird:
const foo = ({ a, b, obj }) => {
console.log(a) // 1
console.log(b) // 2
console.log(obj) // { a: 1, b: 2 }
}
const x = { a: 1, b: 2 }
x.obj = x;
foo(x)

how to understand this JavaScript code output? [duplicate]

When I study electron, I found 2 ways of getting BrowserWindow object.
const {BrowserWindow} = require('electron')
and
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
What is the difference between const and const {} in JavaScript?
I can't understand why the const {} can work. Do I miss anything important about JS?
The two pieces of code are equivalent but the first one is using the ES6 destructuring assignment to be shorter.
Here is a quick example of how it works:
const obj = {
name: "Fred",
age: 42,
id: 1
}
//simple destructuring
const { name } = obj;
console.log("name", name);
//assigning multiple variables at one time
const { age, id } = obj;
console.log("age", age);
console.log("id", id);
//using different names for the properties
const { name: personName } = obj;
console.log("personName", personName);
const {BrowserWindow} = require('electron')
Above syntax uses ES6. If you have an object defined as:
const obj = {
email: "hello#gmail.com",
title: "Hello world"
}
Now if we want to assign or use email and title field of obj then we don't have to write the whole syntax like
const email = obj.email;
const title = obj.title;
This is old school now.
We can use ES6 Destructuring assignment i.e., if our object contains 20 fields in obj object then we just have to write names of those fields which we want to use like this:
const { email,title } = obj;
This is ES6 syntax-simpler one
It will automatically assign email and title from obj, just name has to be correctly stated for required field.
This is one of the new features in ES6. The curly braces notation is a part of the so called destructuring assignment. What this means is that, you no longer have to get the object itself and assign variables for each property you want on separate lines. You can do something like:
const obj = {
prop1: 1,
prop2: 2
}
// previously you would need to do something like this:
const firstProp = obj.prop1;
const secondProp = obj.prop2;
console.log(firstProp, secondProp);
// etc.
// however now you can do this on the same line:
const {prop1, prop2} = obj;
console.log(prop1, prop2);
As you have seen in the end the functionality is the same - simply getting a property from an object.
There is also more to destructuring assignment - you can check the entire syntax in MDN: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
Other answers are good enough. I would suggest some useful features of Destructuring assignment
Firstly, Let's look at the following define:
The destructuring assignment syntax is a JavaScript expression that
makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
Features:
Destructure an array, index of each item in array act as property (Due to an Array is an object in JavaScript)
> const {0: first, 1: second} = [10, 20]
console.log(first); // 10
console.log(second); // 20
Combine with Spread ... operator
> {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40}
console.log(a); // 10
console.log(b); // 20
console.log(rest ); // {c: 30, d: 40}
Default values
const {a = 10, b = 20} = {a: 1};
console.log(a); // 1
console.log(b); // 20
Assigning to new variable names
const {p: a, q: b} = {p: 10, q: 20};
console.log(a); // 10
console.log(b); // 20

Destructuring in function parameters

Lets assume we have such function:
const func = (a, b, {c: {d}}) => {console.dir(c)}
How is this function should be called and what structure of 3rd parameter?
I tried a lot of variations, but always got an error: Cannot destructure propertydof 'undefined' or 'null'.
Thanks!
const func = (a, b, {c: {d}}) => {console.dir(d)}
func(null, null, {c: {d: document.location}});
This function has to be called with object that has key c, which has object with key d as value:
func(a, b, {c: {d: document.location }})
console.dir() takes any JS object as parameter.
{ c: {d}} is a syntax called object destructuring and its purpose in this context is unpacking fields from objects passed as function parameter.
{d} is shorter syntax for object with key d and value of variable d ({d: d}).
To unpack variable d from object under key c, that object have to have that key initialized! But when you further destructurize the object passed as argument, you don't have that object as a variable in the scope.
In example you have provided, you will not be able to access object c, as it has been destructurized and only object d is available. Either you have mistake in your code or you need something like Anurat Chapanond has posted.
Here is one example.
const func = (a, b, {c: {d}}) => {console.dir(c)},
c = { d: 'hello' }
func(1, 2, { c })
I define c as an object with a property d which is a string 'hello'.
when func is called, the third parameter I pass to the function is an object with a property c.
{ c } is a shorthand for { c: c }

Categories

Resources