Most of the modern languages like Ruby, Python or Java have a single null value ( or ), which seems a reasonable approach.
Some native methods like can return to denote a missing object. Take a look at the sample:
Often such risky actions generate related errors, ending the script lightning fast. Related common error messages are:
- and alike type errors.
To reduce the risk of such errors, you have to understand the cases when is generated. And more important suppress its appearance and spread within your application, increasing code durability.
Let's detail the exploration of and its effect on code safety.
1. What is
- Boolean: or
- Number: , ,
- Symbol: (starting ES2015)
- Undefined: .
And a separated object type: , .
From 6 primitive types is a special value with its own type Undefined. According to ECMAScript specification:
Undefined value primitive value is used when a variable has not been assigned a value.
The standard clearly defines that you will receive an undefined value when accessing uninitialized variables, non existing object properties, non existing array elements and alike. For instance:
The above example demonstrates that accessing:
- an uninitialized variable
- a non-existing object property
- or a non-existing array element
are evaluated to .
The ECMAScript specification defines the type of value:
Undefined type is a type whose sole value is the value.
In this sense, operator returns string for an value:
Of course works nicely to verify whether a variable contains an value:
2. Common scenarios that create
2.1 Uninitialized variable
A declared variable that is not yet assigned with a value (uninitialized) is by default .
Plain and simple:
is declared and not yet assigned with a value. Accessing the variable evaluates to .
An efficient approach to solve the troubles of uninitialized variables is whenever possible assign an initial value. The less the variable exists in an uninitialized state, the better. Ideally you would assign a value right away after declaration , but this is not always possible.
Tip 1: Favor , otherwise use , but say goodbye to
In my opinion, one of the best features of ECMAScript 2015 is the new way to declare variables using and . It is a big step forward that these declarations are block scoped (contrary to older function scoped ) and exist in a temporal dead zone until the declaration line.
When the variable receives a value once and forever, I recommend to use a declaration. It creates an immutable binding.
One of the nice features of is that you have to assign an initial value to the variable . The variable is not exposed to the uninitialized state and to access is simply not possible.
Let's check the function that verifies whether a word is a palindrome:
and variables are assigned with a value once. Seems reasonable to declare them as , since these variables are not going to change.
If you need to rebind the variable (i.e. assign multiple times), apply a declaration. Whenever possible assign an initial value to it right away, e.g. .
What about the old school ? In terms of ES2015, my suggestion is stop using it at all.
declaration problem is the variable hoisting in the entire function scope. You can declare a variable somewhere at the end of the function scope, but still it can accessed before declaration: and you'll get an .
is accessible and contains even before the declaration line: .
Contrary, a (including ) variable cannot be accessed before the declaration line. It happens because the variable is in a temporal dead zone before the declaration. And that's nice, because you have less chances to access an .
The above example updated with (instead of ) throws a , because the variable in the temporal dead zone is not accessible.
Encouraging the usage of for immutable bindings or otherwise ensures a practice that reduces the uninitialized variables appearance.
Tip 2: Increase cohesion
Cohesion characterizes the degree to which the elements of a module (namespace, class, method, block of code) belong together. The measurement of the cohesion is usually described as high cohesion or low cohesion.
High cohesion is preferable because it suggests to design the elements of the module to focus solely on a single task. It makes the module:
- Focused and understandable: easier to understand what the module does
- Maintainable and easier to refactor: the change in the module affects fewer modules
- Reusable: being focusing on a single task, it makes the module easier to reuse
- Testable: you would easier test a module that's focused on a single task
High cohesion accompanied with loose coupling is the characteristic of a well designed system.
A code block by itself might be considered a small module. To profit from the benefits of high cohesion, you need to keep the variables as close as possible to the code block that uses them.
For instance, if a variable solely exists to form the logic of a block scope, then declare and allow the variable to live only within that block (using or declarations). Do not expose this variable to the outer block scope, since the outer block shouldn't care about this variable.
One classic example of unnecessary extended life of variables is the usage of cycle inside a function:
, and variables are declared at the beginning of function body. However they are used only near the end. So what is the problem with such approach?
All the way between the declaration at the top and the usage in statement the variables , are uninitialized and exposed to . They have an unreasonably long lifecycle in the entire function scope.
A better approach is to move these variables as close as possible to their usage place:
and variables exist only in the block scope of statement. They don't have any meaning outside of .
variable is declared close to the source of its usage too.
Why is the modified version better than the initial one? Let's see:
- The variables are not exposed to uninitialized state, thus you have no risk of accessing
- Moving the variables as close as possible to their usage place increases the code readability
- High cohesive chunks of code are easier to refactor and extract into separated functions when necessary
2.2 Accessing non-existing property
Let's demonstrate that in an example:
is an object with a single property . Accessing a non-existing property using a property accessor is evaluated to .
By itself accessing a non-existing property does not throw an error. The real problem appears when trying to get data from a non-existing property value. This is the most common related trap, reflected in the well known error message .
Let's slightly modify the previous code snippet to illustrate a throw:
does not have the property , so evaluates to .
As result, accessing the first item of an value using the expression throws a .
Unfortunately you often don't have control over the objects that you work with. Such objects may have different set of properties in diverse scenarios. So you have to handle all these scenarios manually.
Let's implement a function that adds at the beginning and/or at the end of an array new elements. parameter accepts an object with properties:
- : element inserted at the beginning of
- : element inserted at the end of .
The function returns a new array instance, without altering the original array (i.e. it's a pure function).
The first version of , a bit naive, may look like this:
Because object can omit or properties, it is obligatory to verify whether these properties exist in .
A property accessor evaluates to if the property does not exist. The first temptation to check weather or properties are present is to verify them against . Let's do the verification in conditionals and ...
Not so fast. There is a serious drawback in this approach. , as well as , , , and are falsy values.
In the current implementation of , the function doesn't allow to insert falsy elements:
and are falsy. Because and actually compare against falsy, these elements are not inserted into the array. The function returns the initial array without modifications.
The tips that follow explain how to correctly check the property existence.
Tip 3: Check the property existence
- : compare against directly
- : verify the property value type
- : verify whether the object has an own property
- : verify whether the object has an own or inherited property
My recommendation is to use operator. It has a short and sweet syntax. operator presence suggests a clear intent of checking whether an object has a specific property, without accessing the actual property value.
is a nice solution too. It's slightly longer than operator and verifies only in object's own properties.
The 2 ways that involve comparing with might work... But it seems to me that and look verbose and weird, and expose to a suspicions path of dealing directly with .
Let's improve function using operator:
(and ) is whether the corresponding property exists, otherwise.
The usage of operator fixes the problem with inserting falsy elements and . Now, adding these elements at the beginning and at the end of produces the expected result .
Tip 4: Destructuring to access object properties
When accessing an object property, sometimes it's necessary to indicate a default value if the property does not exist.
You might use accompanied with ternary operator to accomplish that:
The usage of ternary operator syntax becomes daunting when the number of properties to check increases. For each property you have to create a new line of code to handle the defaults, increasing an ugly wall of similar looking ternary operators.
In order to use a more elegant approach, let's get familiar with a great ES2015 feature called object destructuring.
Object destructuring allows inline extraction of object property values directly into variables, and setting a default value if the property does not exist. A convenient syntax to avoid dealing directly with .
Indeed, the property extraction now looks short and meaningful:
To see things in action, let's define an useful function that wraps a string in quotes.
accepts the first argument as the string to be wrapped. The second argument is an object with the properties:
- : the quote char, e.g. (single quote) or (double quote). Defaults to .
- : the boolean value to skip quoting if the string is already quoted. Defaults to .
Applying the benefits of the object destructuring, let's implement :
destructuring assignment in one line extracts the properties and from object.
If some properties are not available in object, the destructuring assignment sets the default values: for and for .
Fortunately, the function still has room for improvements.
Let's move the destructuring assignment right into the parameters section. And set a default value (an empty object ) for parameter, to skip the second argument when default settings are enough.
Notice that a destructuring assignment replaces the parameter in function's signature. I like that: becomes one line shorter.
on the right side of destructuring assignment ensures that an empty object is used if the second argument is not specified at all .
Object destructuring is a powerful feature that handles efficiently the extraction of properties from objects. I like the possibility to specify a default value to be returned when the accessed property doesn't exist. As result, you avoid and the problem related to handling it.
Tip 5: Fill the object with default properties
If there is no need to create variables for every property like the destructuring assignment does, the object that misses some properties can be filled with default values.
The ES2015 copies the values of all enumerable own properties from one or more source objects into the target object. The function returns the target object.
For instance, you need to access the properties of object, which not always contains its full set of properties.
To avoid when accessing a non-existing property from , let's make some adjustments:
- Define an object that holds the default property values
- Call to build a new object . The new object receives all properties from , but the missing ones are taken from .
contains only property. object defines the default values for properties and .
takes the first argument as a target object . The target object receives the value of property from source object. And the value of property from source object, because doesn't contain .
The order in which the source objects are enumerated does matter: later source object properties overwrite earlier ones.
You are now safe to access any property of object, including that wasn't available in initially.
Instead of invocation, use the object spread syntax to copy into target object all own and enumberable properties from source objects:
The object initializer spreads properties from and source objects. The order in which the source objects are specified is important: later source object properties overwrite earlier ones.
Filling an incomplete object with default property values is an efficient strategy to make your code safe and durable. No matter the situation, the object always contains the full set of properties: and cannot be generated.
2.3 Function parameters
The function parameters implicitly default to .
Normally a function that is defined with a specific number of parameters should be invoked with the same number of arguments. In such case the parameters get the values you expect:
The invocation makes the parameters and receive the corresponding and values. The multiplication is calculated as expected: .
What does happen when you omit an argument on invocation? The parameter inside the function becomes .
Let's slightly modify the previous example by calling the function with just one argument:
is defined with two parameters and .
The invocation is performed with a single argument: as result parameter is , but parameter is .
Tip 6: Use default parameter value
Sometimes a function does not require the full set of arguments on invocation. You can simply set defaults for parameters that don't have a value.
Recalling the previous example, let's make an improvement. If parameter is , it gets assigned with a default value of :
The function is invoked with a single argument . Initially parameter is and is .
The conditional statement verifies whether is . If it happens, assignment sets a default value.
While the provided way to assign default values works, I don't recommend comparing directly against . It's verbose and looks like a hack.
A better approach is to use the ES2015 default parameters feature. It's short, expressive and no direct comparisons with .
Modifying the previous example with a default parameter for indeed looks great:
in the function signature makes sure that if is , the parameter is defaulted to .
ES2015 default parameters feature is intuitive and expressive. Always use it to set default values for optional parameters.
2.4 Function return value
function does not return any computation results. The function invocation result is .
The same situation happens when statement is present, but without an expression nearby:
statement is executed, but it doesn't return any expression. The invocation result is also .
Of course, indicating near the expression to be returned works as expected:
Now the function invocation is evaluated to , which is squared.
Tip 7: Don't trust the automatic semicolon insertion
- empty statement
- , , , , declarations
- expression statement
- statement, statement
If you use one of the above statements, be sure to indicate a semicolon at the end:
At the end of both declaration and statement an obligatory semicolon is written.
What happens when you don't want to indicate these semicolons? For instance to reduce the size of the source file.
In such situation ECMAScript provides an Automatic Semicolon Insertion (ASI) mechanism, which inserts for you the missing semicolons.
Being helped by ASI, you can remove the semicolons from the previous example:
There is one small, but annoying trap created by ASI. When a newline stands between and the returned expression , ASI automatically inserts a semicolon before the newline .
What does mean inside a function to have statement? The function returns . If you don't know in details the mechanism of ASI, the unexpectedly returned is misleading.
For instance, let's study the returned value of invocation:
The statement makes the function to return instead of the expected array.
The problem is solved by removing the newline between and array literal:
My recommendation is to study how exactly Automatic Semicolon Insertion works to avoid such situations.
Of course, never put a newline between and the returned expression.
evaluates the expression and returns no matter the result of evaluation.
One use case of operator is to suppress expression evaluation to , relying on some side-effect of the evaluation.
3. in arrays
You get when accessing an array element with an out of bounds index.
array has 3 elements, thus valid indexes are , and .
Because there are no array elements at indexes and , the accessors and are .
When a gap (aka empty slot) is accessed inside a sparse array, you also get an .
The following example generates sparse arrays and tries to access their empty slots:
is created corresponding by invoking an constructor with a numeric first argument. It has 3 empty slots.
is created with an array literal with the missing second element.
In any of these sparse arrays accessing an empty slot evaluates to .
When working with arrays, to escape catching , be sure to use valid array indexes and avoid at all creating sparse arrays.
4. Difference between and
A reasonable question appears: what is the main difference between and ? Both special values imply an empty state.
The main difference is that represents a value of a variable that wasn't yet initialized, while represents an intentional absence of an object.
Let's explore the difference in some examples.
The variable is defined, however is not assigned with an initial value:
variable is , which clearly indicates an uninitialized variable.
The same uninitialized concept happens when a non-existing object property is accessed:
In other cases you know that a variable expects to hold an object or a function to return an object. But for some reason you can't instantiate the object. In such case is a meaningful indicator of a missing object.
However might be invoked with a non-object argument: or (or generally a primitive value, or ). In such case the function cannot create a clone, so it returns - the indicator of a missing object.
operator makes the distinction between the two values:
The strict quality operator correctly differentiates from :
- uninitialized variables
- non-existing object properties or methods
- out of bounds indexes to access array elements
- the invocation result of a function that returns nothing
Mostly comparing directly against is a bad practice, because you probably rely on a permitted but discouraged practice mentioned above.
An efficient strategy is to reduce at minimum the appearance of keyword in your code. In the meantime, always remember about its potential appearance in a surprising way, and prevent that by applying beneficial habits such as:
- reduce the usage of uninitialized variables
- make the variables lifecycle short and close to the source of their usage
- whenever possible assign an initial value to variables
- favor , otherwise use
- use default values for insignificant function parameters
- verify the properties existence or fill the unsafe objects with default properties
- avoid the usage of sparse arrays
Bertolucci. Fellini. Leone. The immortal names of fine Italian cinema have elevated their craft to the very pinnacle of the motion picture arts. Yet, emerging from the shadows of the masters, fearless filmmakers of science fiction made some iconic contributions to the country's embrace of big-screen cult spectacles seasoned with insane killer robots, maniacal experiments, sleek spaceships, rogue planetoids and mod outer space madness.
Any of the phenomenal sci-fi films served up on this platter are best watched with a bottle of your favorite vino and steaming dish of penne pomodoro (with fresh Parmesan, of course), for a tempting taste of Italy's wild and wonderful offerings like Planet of the Vampires, Mission Stardust, Assignment: Outer Space and Wild, Wild Planet. What these spirited movies lack in bulging budgets and screenplay coherence, they more than make up for with imaginative direction, stylish set design and unexpected parades of far out fashion.
So sip from this selection of 17 vintage sci-fi classics from the country that delivered Michelangelo, spaghetti westerns and spumoni ice cream, then tell us which ones you have tanto affeto for!