4

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?

guest271314
  • 1
  • 15
  • 104
  • 177
  • I dont think. When you say default argument value, you are referring to 1 variable. So when you pass `{b:7}`, JS engine will see value is received and will not use default value. You are trying to set default value of an argument's property. – Rajesh Apr 18 '17 at 08:07
  • @Rajesh Developed a solution using two default parameters, see Answer. – guest271314 Apr 18 '17 at 09:18
  • 1
    Notice that there is absolutely nothing wrong with declaring variables inside functions. Please do not abuse further parameters like in your answer. – Bergi Apr 26 '17 at 14:34
  • 1
    Why not simply do `const fn = opts => console.log(Object.assign({a: 1, b: 2, c: 3}, opts);` - no default parameters needed at all? I'm not sure what your [actual problem](http://meta.stackexchange.com/q/66377) is. – Bergi Apr 26 '17 at 14:37
  • @Bergi The Question and bounty are clear at OP. Define a single default parameter which is persistent and remains set until changed. If no parameter is passed at function call, the default object is returned. If a parameter is passed that matches a property of the default parameter, that property's value is set; if a property and value are passed which is not set at default parameter, that property and value are set at default parameter. Was able to achieve requirement using two default parameters. The bounty is to determine if the requirement can be achieved using a single default parameter – guest271314 Apr 26 '17 at 14:45
  • Technically, `fn = (opts, _ = (opts = Object.assign({a: 1, b: 2, c: 3}, opts))) => console.log(opts);` would fulfill your requirements but fail when a second parameter is specified. – le_m Apr 30 '17 at 02:02
  • @le_m `opts` and `_` would be two parameters, no? Yes, that is one issue with approach at own Answer. – guest271314 Apr 30 '17 at 05:00

4 Answers4

3

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.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Approach does not return expected result. `fn({b:7, g:9}) // ReferenceError: g is not defined`. The requirement is to define a single variable encompassing all parameters passed. If a property not defined at default parameters is passed, that property is set at the resulting object; if the property is defined, the value is set the the passed value; the same as at http://stackoverflow.com/a/43468095/, though using a single default parameter, instead of two, which that Answer achieves – guest271314 Apr 26 '17 at 14:39
  • @guest271314 You must have done something wrong, my function does not throw a ReferenceError. Although it admittedly does not log any `g`, but that requirement was not clear from the question. – Bergi Apr 26 '17 at 14:46
  • The requirement is clear at the bounty description and the expected result is returned at own Answer. The bounty is specifically to determine if the requirement which is achieved at own Answer can be achieved using a single default parameter. `const fn = ({a = 1, b = 2, c = 3} = {}) => console.log({a, b, c, g});fn({b:7, g:9});//Uncaught ReferenceError: g is not defined` – guest271314 Apr 26 '17 at 14:48
  • The updated Answer is close. And have been trying object spread at stacksnippets with `babel` enabled; and saving each tested version to rule out what have previously tried. Though we do not not want `a`, `b`, `c` individually. Came across one of you Answers which is similar to your current Answer before posting Question. We want `opts` to be the single object reference. – guest271314 Apr 26 '17 at 14:51
  • Please state your requirements in the question itself, not an answer. Btw, my answer shows "*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.*" that was asked for in the bounty (which doesn't mention any `g`. If you want to use a `g`, you have to define `const fn = ({a = 1, b = 2, c = 3, g} = {}) => console.log({a, b, c, g});`) – Bergi Apr 26 '17 at 14:52
  • The bounty description itself is clear. The Question itself is already answered by own Answer. The bounty is specific to can own Answer be achieved using a single default parameter. We do not want `a`, `b`, `c` individually. That pattern does not appear at either OP or Answer. We want `opts` as a single object. If you believe the bounty description is not clear enough, look at the patterns used. You are sharp enough to gather what the expected result is. To be clearer, a single object is expected within function body; a single default parameter set at default parameter. – guest271314 Apr 26 '17 at 14:54
  • 1
    "No" is an Answer, if we cannot find a viable option using single default parameter. – guest271314 Apr 26 '17 at 15:02
  • However, that does not mean give up with "No" Answer. It could be possible. Have gotten close using rest parameter which always returns an array or iterable; and immediately invoked arrow function as part of default parameter . Trying to determine the limits of possibilities at default parameters – guest271314 Apr 26 '17 at 15:11
  • The portion of your Answer above the blockquote is not relevant to present bounty http://stackoverflow.com/questions/42987957/is-textcontent-completely-secure/42988014#comment73068899_42988014, http://stackoverflow.com/questions/42987957/is-textcontent-completely-secure/42988014#comment73069018_42988014. Also _"You are supposed to use the function body for that."_ is opinion-based. Consider removing those portions of post from your Answer. – guest271314 Apr 26 '17 at 15:50
  • 1
    You can keep on trying, or you can treat my answer as an expert *opinion* on the case. – Bergi Apr 26 '17 at 16:24
  • 1
    I'm happy to see someone disagree with me and learn how it is possible. Until then, I stand by my educated guess. – Bergi Apr 26 '17 at 16:37
  • The closest, if not the closest possible to achieve, have gotten so far, using the template at your Answer `const fn = ({a = 1, b = 2, c = 3, ...opt, opts = {a, b, c, ...opt}} = {}) => console.log(opts)`. Did not specify that was expecting `a`, `b`, `c` or a separate object to _not_ be defined at description of bounty. Still trying to get only `opts` defined as a single object. – guest271314 Apr 26 '17 at 19:42
  • 1
    @guest271314 That doesn't work when called with `fn({opts: "busted"})`. Also it's a syntax error given that rest properties must be the last component of a pattern – Bergi Apr 26 '17 at 23:54
  • Good catch. Did not get syntax error using `const fn = ({a = 1, b = 2, c = 3, ...opt, opts = {a, b, c, ...opt}} = {}) => console.log(a, b, opts);`, there unexpected result is essentially same issue. Trying rest parameter, iiaf between, and combinations of both `((o) => ({a:o.a, b:o.b, c:o.c} = {a: 1, b: 2, c: 3}))(/* parameters passed to fn */)`, `fn = (...props) => (({a=1,b=2,c=3, ...props} = {}) => ({a,b,c, ...props})) => console.log(props)`. Though that is edging at not conforming to within single default parameter, and does not return expected result either. – guest271314 Apr 27 '17 at 00:10
  • Had a sense that rest parameters could be used to return expected result – guest271314 May 01 '17 at 03:57
  • Apparently object rest/spread is available at chromium 57 where `--javascript-harmony` flag is set. – guest271314 May 01 '17 at 15:44
2

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});
spacepickle
  • 2,678
  • 16
  • 21
  • Answer does not set default parameters – guest271314 Apr 26 '17 at 14:18
  • `javascript` at Answer still does not set default parameters. – guest271314 Apr 26 '17 at 14:49
  • @guest271314 I get that your question specifically said default parameters but why is this effectually/functionally not the same thing? – spacepickle Apr 26 '17 at 14:54
  • Because it is not the same. – guest271314 Apr 26 '17 at 14:56
  • See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters, https://tc39.github.io/ecma262/#sec-function-definitions – guest271314 Apr 26 '17 at 15:00
  • @guest271314 I read that before but what I'm asking is what's the functional difference? – spacepickle Apr 26 '17 at 15:02
  • Not sure exact meaning of "functional", though one approach sets defaults before function body, the other within function body. The bounty is to determine the breadth of possibilities available or not available at default parameters – guest271314 Apr 26 '17 at 15:05
  • @guest271314 I guess I mean is there a code example would one approach would have a different outcome from the other approach. Are default parameter values just syntactic sugar for what my example does and therefore not different? – spacepickle Apr 26 '17 at 15:09
  • There is a difference. If they were the same we would be able to achieve the same setting default parameters as within function body, and the present bounty would not be necessary, as the "functionality" would be equal to that of function body. Though that is not the case. – guest271314 Apr 26 '17 at 15:19
  • Was able to achieve expected result using rest parameter. – guest271314 May 01 '17 at 04:01
  • @guest271314 nice. I'll have to try that out. – spacepickle May 01 '17 at 14:56
0

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);
guest271314
  • 1
  • 15
  • 104
  • 177
-1

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});
guest271314
  • 1
  • 15
  • 104
  • 177
  • I guess you do not need `__`(*not sure though*). You can directly use `.assign({}, {...}, opts)`. Also, in my understanding, this is more of a hack and I'd prefer using code similar to the one in your question. I'd create a function `parseInput` and process input there and use returned value. – Rajesh Apr 18 '17 at 09:20
  • @Rajesh _"I guess you do not need `__`(not sure though). You can directly use `.assign({}, {...}, opts)`"_ Can you create a jsfiddle or plnkr to demonstrate? – guest271314 Apr 18 '17 at 09:28
  • I was wrong. I tried and its not working. My bad. Also I would use something like [this](https://jsfiddle.net/xkhc6mc3/). Yes I understand its objective opinion, hence not answering – Rajesh Apr 18 '17 at 09:30
  • @Rajesh Yes, that is one option; posted a similar pattern at OP. The present Question inquires into using only default parameters to get expected result within function body. Note, the first parameter to `assign()` can be omitted `Object.assign({a: 1, b: 2, c: 3}, __)`. There is a side-effect of passing a plain object at second parameter, `Object {}` is returned; passing an empty string at second parameter returns `undefined`. Would prefer to use single default parameter to avoid the bug in the pattern at Answer described above. Though `Promise.resolve()` expects single parameter as well. – guest271314 Apr 18 '17 at 09:40
  • 1
    Omitting first arg will make also override config object. In this case, since its an inline object, it will not cause issues but in real code, where you use variable instead of inline-object, it will cause issues. – Rajesh Apr 18 '17 at 09:43