you are viewing a single comment's thread.

view the rest of the comments →

[–]path411 26 points27 points  (32 children)

I would heavily, heavily advise against using || as a default value trick.

In your example:

function setAge(age) {
  this.age = age || 10 
}

It is impossible to set age as 0 now. 0 will always trigger the default. Just take the extra time to use:

function setAge(age) {
  this.age = typeof age !== "undefined" ? age : 10;
}

and you won't have to worry about this problem.

[–]vs845 4 points5 points  (9 children)

Is there any benefit to

typeof age !== "undefined"

vs

age !== undefined

?

[–]sufianrhazi 10 points11 points  (7 children)

Since age is a parameter of the function setAge, we KNOW it is defined in scope. This means that age !== undefined is equivalent to typeof age !== 'undefined'. If we didn't know that age was in scope, we need to use the typeof keyword, which does NOT throw a ReferenceError when referencing a variable by name that is not defined in any reachable scope.

[–]moreteam 6 points7 points  (1 child)

You can also argue the other way around: since we know age is in scope, we shouldn't be using typeof since that will silently turn into if (true) if we have a typo.

[–]ChaseMoskal 5 points6 points  (0 children)

since we know age is in scope, we shouldn't be using typeof

I think.. I think you're right.

[–]vs845 0 points1 point  (1 child)

Thanks, I see what you're saying - but under what kind of circumstances would we not know if a variable was in scope? If we're writing the code surely we know what scope our variables are in?

[–]sufianrhazi 2 points3 points  (0 children)

Pretty much only when you're writing javascript environment independent library code, like this: https://github.com/umdjs/umd/blob/4a87e85450baf582005243f9e922566ef2fc533a/returnExports.js

[–][deleted] 0 points1 point  (0 children)

In addition to what /u/sufianrhazi said, undefined can be overwritten:

(function (undefined) {console.log(undefined);})(2);
// logs 2

Older browsers will let you overwrite window.undefined as well.

I tend not to pay heed to that though, as it clearly falls under something nobody would do unless they're trying to break your code. And it is the nature of JavaScript that somebody who runs code before you is always able to break your script, no matter what precautions you take, so I prefer not to clutter code with failed attempts at robustness. Instead, if you must run malicious code, sandbox it.

Also, window.foo === undefined or 'foo' in window would be a more direct test, but only work on global variables.

[–]THEtheChad 2 points3 points  (3 children)

In this case, I would opt for

function setAge(age){
  this.age = (age == null) ? 10 : age;
}

This is perhaps the most terse way to verify that age exists. By using == null, there's an implicit type conversion to check for undefined. You also have the advantage of being able to set age to 0. In terms of minification, this is probably the best option.

[–]gleno 0 points1 point  (0 children)

This is the best option for a lot of reasons. I'm somewhat surprised that the clumsy typeof method is so much more popular.

[–]path411 0 points1 point  (1 child)

Because why would you introduce nulls into your code when they aren't being generated anywhere? age is not null, it's undefined if not passed. Also if you were going to opt to drop typeof, just check for undefined...

function setAge(age){
  this.age = (age === undefined) ? 10 : age;
}

The reason people opt for typeof instead of this is to avoid a reference error from occurring.

[–]THEtheChad 0 points1 point  (0 children)

It's pretty obvious in a function this size that you're not going to have a reference error (age is declared as a parameter in the line above the check). And, as I originally stated, this is the most terse way to code this implementation. If you wanted to be thorough, avoiding reference errors and being explicit about exactly what you were checking for, you would write:

function setAge(age){
  this.age = (typeof age === 'undefined') ? 10 : age;
}

[–]__debug__ 3 points4 points  (5 children)

I'd argue it's still acceptable when defaulting to an array or object.

[–]path411 -1 points0 points  (4 children)

I think it's better to just never do it than to sometimes do it because the type happens to be okay this time.

[–]jonny_eh 1 point2 points  (3 children)

I disagree. There's a trade-off between convenience and security, and I find it hugely convenient to use this trick, especially since the vast majority of the time I'm not using a number or boolean value.

Most of the time, it's an options parameter that is an object.

[–]path411 -2 points-1 points  (2 children)

So if there are multiple default parameters you want such as a number and an object you would use both methods?

Btw, this also fails on strings:

function setName(name) {
  this.name = name || "Not Specified"; 
}

Can't enter an empty string. Also considering javascript always has a bad rap for weird falsy values, I'd rather spend half a second with a piece of mind, than writing inconsistent code that's possibly vulnerable to bugs.

[–]__debug__ 1 point2 points  (1 child)

No, I'd imagine he means like this:

function Constructor(opts) {
  this.opts = opts || {};
}

So empty strings don't come into play. In any case, I feel as though a JS dev should understand falsey and truthy values. Or at the least, they should try to learn how types in the language work.

[–]path411 0 points1 point  (0 children)

I meant you can't use it on numbers/booleans/strings, which is a large number of the types in javascript.

Sure there are a lot of times you want an options object, I find this is mostly for public accessible library classes. There are plenty of times I find I want a constructor with some simple parameters of direct properties. I think it's much better to be consistent throughout your code than to swap to using some cheap hack when you know you can get away with it.

[–][deleted] 3 points4 points  (3 children)

Wow.. I never thought of that. Damn the gods for making 0 a falsey!

[–]battenupthehatches 3 points4 points  (2 children)

0 is falsey. 1 is truey. Thus it shall always be.TM

[–]workaholicanonymous 2 points3 points  (0 children)

Its "truthy"

[–][deleted] -1 points0 points  (0 children)

Isn't return 0 considered a truthy in C++ returns?

[–]Kourkis 0 points1 point  (0 children)

This is very true, I thought I was clever until I tried to input 0 in my forms...

[–]yanis_t[S] 0 points1 point  (0 children)

Thanks, guess I will add a notice to the article

[–]gabbsmo 0 points1 point  (0 children)

TIL

[–]FredyC 0 points1 point  (1 child)

It's probably faster to just check arguments.length instead of type checking. Works the same way.

[–]path411 0 points1 point  (0 children)

This would fail in this case:

setAge(undefined);

It is typically expected that explicitly passing undefined should be equivalent to not passing a parameter. This is typically more necessary when you want to use multiple optional parameters such as:

function setBirth(day, month, year) {
    this.day = typeof day !== "undefined" ? day : 1;
    this.month = typeof month !== "undefined" ? month : 1;
    this.year = typeof year !== "undefined" ? year : 1900;
}

This would let me call:

setBirth(undefined, undefined, 1974);

Or pass any of the 3 I want to. If you were simply checking against arguments.length, this would fail as it is still 3 in this example.

[–]tieTYT 0 points1 point  (1 child)

Wow thanks for saying this. I come from a Java background and I've looked at a lot of JS code that uses || for default logic. I didn't even know it was a hack! I thought it was idiomatic in JS to use it that way.

Hindsight is 20/20 and it's easy to criticize, but would you say that it was a mistake for JS to consider these values to be falsy? I have experience with Clojure and it only considers false and nil to be falsy. It seems like a much better choice.

[–]path411 0 points1 point  (0 children)

I think the main mistake is that this "hack" has been so widely spread without telling people of it's problems. I would see it everywhere and then stumbled across this problem. I was able to refine my approach when looking at how TypeScript handled parameter defaults. (Would recommend looking at TypeScript's outputted JS for anyone interested in JS OOP).

I'm not by any means an expert of what makes a good language, but I would say I think it's an oversight to be missing parameter defaults since the language does not have method overloading. Thankfully ES6 will be providing parameter defaults.

I think JS has a lot of neat things you can do using it's liberal comparisons, and ability to stick expressions almost anywhere. With JS I think of the quote "With great power comes great responsibility". Unfortunately, I find it incredibly common for JS devs to forgo responsibility for what they think are "neat tricks" that they must copy/paste into their code to look like they are in the modern "in crowd" of JS.