52

Is there a known reason why passing in null as a parameter in ES6 does not use the default parameter when one is provided?

function sayHello(name = "World") {
    console.log("Hello, " + name + "!");
}

sayHello("Jim");     // Hello, Jim!
sayHello(undefined); // Hello, World!
sayHello(null);      // Hello, null!
alacy
  • 4,462
  • 5
  • 25
  • 45
  • 4
    because null is a valid value? – toskv Sep 22 '15 at 19:14
  • @toskv But so is `undefined`.. – user2864740 Sep 22 '15 at 19:16
  • 1
    no it is not :) you can read about the difference between undefined and null here http://stackoverflow.com/questions/5076944/what-is-the-difference-between-null-and-undefined-in-javascript – toskv Sep 22 '15 at 19:17
  • semantically they are different :) – toskv Sep 22 '15 at 19:17
  • 2
    @toskv If `undefined` was *not* a value then it could *not* be assigned, yet it can be. This is the simplest test to determine if something is a value. And quite simply, that answer is wrong to use in context: `undefined` is used as a *default value* in certain cases; this is not not make undefined 'not a value'. Consider: `a = new Array(1)`. Then `a[0]` will yield the `undefined` value even though `0 in a` is false. That it, `a[0]` contains 'no value' even though it will *evaluate* to the (default) `undefined` value. – user2864740 Sep 22 '15 at 19:17
  • it can be assigned, but undefined means the variable was not defined, while null means that the variable is defined but it's value is null. – toskv Sep 22 '15 at 19:20
  • 3
    @toskv I've explained above why that is incorrect. Talking about 'if the variable was not defined' (in the case of `typeof x === 'undefined'`, where `x` alone would/could throw a ReferenceError) has no bearing on the fact that `undefined` *is* a value. In particular, 'undefined' in JavaScript does *not* strictly mean 'has not been defined/declared'. – user2864740 Sep 22 '15 at 19:21
  • @toskv: What about `var x = undefined;`? Is my variable still not defined? – Felix Kling Sep 22 '15 at 23:37
  • I think I expressed myself badly before. It is not that the variable is not defined, in that case the variable points to a value that has not yet been defined. While var x = null means x has the defined value of null. You can think of undefined as uninitialized while null is initialized with no value. It's confusing I know. @Felix Kling – toskv Sep 23 '15 at 05:44
  • You can consider this use case. X needs to be initialized with a value received on an http request. We start with var x; and x is undefined. Now, we do the request and that can end in 2 ways. The request suceeds and x gets a value. Or the request fails in which case we set x to the value null because we now know that x has no value. The difference is mainly on of meaning and javascript's truthy/falsy value system does not help a lot to clear this up. – toskv Sep 23 '15 at 05:55

4 Answers4

38

This is not that obvious

I've read some comments of why undefined is completely different than null and that's why it explains the current behavior of default parameters.

One could argue that explicitly passing undefined should not trigger the default value substitution because when I have a function:

const f = (x = 'default') => console.log(x);

I would like it to print "default" when I run it as:

f();

but I would like it to print "undefined" when I explicitly run it as:

f(undefined);

because otherwise why would I use f(undefined) in the first place? Clearly my intention here is to provide some argument instead of leaving it out.

Examples to the contrary

Now, consider this function:

const g = (...a) => console.log(JSON.stringify(a));

When I use it as:

g();

I get: []

But when I use it as:

g(undefined);

I get: [null]

which clearly demonstrates that:

  1. passing undefined is not the same as not passing an argument at all
  2. sometimes null can be a default value instead of undefined

TC39 decision

Some background on the current behavior of the default parameters can be seen in the July 24 2012 Meeting Notes by TC39:

Incidentally, it shows that explicitly passing undefined originally did not trigger the default value in the first draft and there was a discussion about whether or not it should do that. So as you can see the current behavior was not so obvious to the TC39 members as it now seems to be to people who comment here.

Other languages

That having been said, the decision of what should and what should not trigger the default value substitution is completely arbitrary at the end of the day. Even having a separate undefined and null can be though of as quite strange if you think about it. Some language have only undefined (like undef in Perl), some have only null (like Java), some languages use equivalents of false or an empty list or array for that (like Scheme where you can have an empty list or #f (false) but there is no equivalent of null that would be distinct from both an empty list and a false value) and some languages don't even have equivalents of null, false or undefined (like C which uses integers instead of true and false and a NULL pointer which is actually a normal pointer pointing to address 0 - making that address inaccessible even when mapped by any code that tests for null pointers).

What you can do

Now, I can understand your need to substitute default values for null. Unfortunately this is not a default behavior but you can make a simple function to help you:

const N = f => (...a) => f(...a.map(v => (v === null ? undefined : v)));

Now every time you want defaults substituted for null values you can use it like this. E.g. if you have this function from one of the examples above:

const f = (x = 'default') => console.log(x);

it will print "default" for f() and f(undefined) but not for f(null). But when you use the N function defined above to define the f function like this:

const f = N((x = 'default') => console.log(x));

now f() and f(undefined) but also f(null) prints "default".

If you want somewhat different behavior, e.g. substituting default values for empty strings - useful for environment variables that can sometimes be set to empty strings instead of not existing, you can use:

const N = f => (...a) => f(...a.map(v => (v === '' ? undefined : v)));

If you want all falsy values to be substituted you can use it like this:

const N = f => (...a) => f(...a.map(v => (v || undefined)));

If you wanted empty objects to be substituted you could use:

const N = f => (...a) => f(...a.map(v => (Object.keys(v).length ? v : undefined)));

and so on...

Conclusion

The point is that it's your code and you know what should be the API of your functions and how the default values should work. Fortunately JavaScript is powerful enough to let you easily achieve what you need (even if that is not the default behavior of default values, so to speak) with some higher order function magic.

rsp
  • 91,898
  • 19
  • 176
  • 156
33

That's just how default parameters are defined in the spec. See MDN: (emphasis mine)

Default function parameters allow formal parameters to be initialized with default values if no value or undefined is passed.

null is neither no value, nor undefined, so it doesn't trigger the default initialization.

doldt
  • 4,312
  • 3
  • 19
  • 36
  • And the reason why `undefined` is treated specially is because "empty" parameters are set to `undefined`. If `undefined` wasn't equivalent to "no value", this would be more annoying to implement: `function foo(bar) { return functionWithDefaults(bar); }; foo();`. The drawback is that it's not possible to explicitly pass `undefined`, but who does that anyway :P – Felix Kling Sep 22 '15 at 23:44
  • 4
    Does anyone know why they decided this was the right way to go? It seems to me null should trigger the use of a default value. – ThinkingInBits Aug 08 '16 at 19:39
  • It's possible to explicitly pass `undefined` by explicitly passing `undefined`, e.g. `doSomething(undefined)`, see http://codepen.io/morewry/pen/KaEVmN?editors=0010. – morewry Feb 15 '17 at 02:50
1

null is a value that won't trigger the default value to be used, the default values will be used when the argument is undefined.

taxicala
  • 20,376
  • 7
  • 31
  • 65
0

ramdajs version code

// import ramda

// infra code
const isEmptyOrNil = (value) => isEmpty(value) || isNil(value)
const nullChange = (defaultValue) => (value) => isEmptyOrNil(value)? defaultValue: value

// single null
const _somef = (value) => value
const somefWithDefault = (defaultValue) => pipe(nullChange(defaultValue), _somef)
const somef = somefWithDefault('myValue')

somef(null)

// many args
const _somef2 = (value, value2, value3) => value + value2 + value3
const nullChangeMyValue = nullChange('myValue')
const somef2 = useWith(_somef2, [nullChangeMyValue, nullChangeMyValue, nullChangeMyValue])

somef2(null, undefined, 1234)

// many args2 version2
const nullChangeWith = (defaultValue) => nullChange(defaultValue)
const arrayNullChangeWith = (defaultValue) => (array) => array.map(nullChangeWith(defaultValue))
const arg2Array = (...args) => args
const funcApplyDefaultValue = (defaultValue) => (func) => pipe(
  arg2Array, arrayNullChangeWith(defaultValue), apply(func)
)

const v2somef2 = funcApplyDefaultValue(8)(_somef2)

v2somef2(1,2,null)

gits: https://gist.github.com/otwm/3a6358e53ca794cc2e57ade4af91d3bb

김동오
  • 41
  • 1
  • 4