0
点赞
收藏
分享

微信扫一扫

7个提示在JavaScript中处理未定义


7 tips to handle undefined in JavaScript

​undefined​​ and ​​null​​ that represent empty values. What is the clear difference between them? They both seem to define empty values, and moreover the comparison ​​null == undefined​​ evaluates to ​​true​​.​​nil​​or ​​null​​), which seems a reasonable approach.​​undefined​

​​Try in repl.it​​


let company;  
company; // => undefined
let person = { name: 'John Smith' };
person.age; // => undefined

let company;
company; // => undefined
let person = { name: 'John Smith' };
person.age; // => undefined

​null​​ represents a missing object reference. JavaScript by itself does not set variables or object properties to ​​null​​. 

Some native methods like ​​String.prototype.match()​​ can return ​​null​

​​Try in repl.it​​


let array = null;  
array; // => null
let movie = { name: 'Starship Troopers', musicBy: null };
movie.musicBy; // => null
'abc'.match(/[0-9]/); // => null

let array = null;
array; // => null
let movie = { name: 'Starship Troopers', musicBy: null };
movie.musicBy; // => null
'abc'.match(/[0-9]/); // => null

Because JavaScript is very permissive, developers have the temptation to access uninitialized values. I'm guilty of such bad practice too.

​undefined​​​​TypeError: 'undefined' is not a function​​​​TypeError: Cannot read property '<prop-name>' of undefined​

  • and alike type errors.

The JavaScript developer can understand the irony of this joke:

function undefined() {  
// problem solved
}

function undefined() {
// problem solved
}

​undefined​​​​undefined​

undefined

JavaScript has 6 primitive types:

  • Boolean: 

​true​

  •  or 

​false​

  • Number: 

​1​

​6.7​

​0xFF​

  • String: 

​"Gorilla and banana"​

  • Symbol: 

​Symbol("name")​

  • Null: 

​null​

  • Undefined: 

​undefined​

  • .

object type: ​​{name: "Dmitri"}​​, ​​["apple", "orange"]​​.​​undefined​​ is a special value with its own type Undefined. According to ​​​ECMAScript specification​​:

Undefined 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:


​​Try in repl.it​​


let number;  
number; // => undefined
let movie = { name: 'Interstellar' };
movie.year; // => undefined
let movies = ['Interstellar', 'Alexander'];
movies[3]; // => undefined

let number;
number; // => undefined
let movie = { name: 'Interstellar' };
movie.year; // => undefined
let movies = ['Interstellar', 'Alexander'];
movies[3]; // => undefined

As the above example demonstrates, accessing:

  • an uninitialized variable 

​number​

  • a non-existing object property 

​movie.year​

  • or a non-existing array element 

​movies[3]​​​​undefined​​.​​undefined​

Undefined type is a type whose sole value is the undefined

​typeof​​ operator returns ​​'undefined'​​ string for an ​​undefined​

​​Try in repl.it​​


typeof undefined === 'undefined'; // => true  

typeof undefined === 'undefined'; // => true

​typeof​​ works nicely to verify whether a variable contains an ​​undefined​​value:

​​Try in repl.it​​


let nothing;  
typeof nothing === 'undefined'; // => true

let nothing;
typeof nothing === 'undefined'; // => true

undefined

2.1 Uninitialized variable

A declared variable that is not yet assigned with a value (uninitialized) is by default undefined.

Plain and simple:


​​Try in repl.it​​


let myVariable;  
myVariable; // => undefined

let myVariable;
myVariable; // => undefined

​myVariable​​ is declared and not yet assigned with a value. Accessing the variable evaluates to ​​undefined​​.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 ​​const myVariable = 'Initial value'​​, but this is not always possible. Tip 1: Favor ​​const​​, otherwise use ​​let​​, but say goodbye to ​​var​​​​const​​ and ​​let​​. It is a big step forward that these declarations are block scoped (contrary to older function scoped ​​var​​) and exist in a ​​​temporal dead zone​​​ until the declaration line.​​const​​declaration. It creates an ​​​immutable binding​​​.​​const​​ is that you have to assign an initial value to the variable ​​const myVariable = 'initial'​​. The variable is not exposed to the uninitialized state and to access ​​undefined​

Let's check the function that verifies whether a word is a palindrome:


​​Try in repl.it​​


function isPalindrome(word) {  
const length = word.length;
const half = Math.floor(length / 2);
for (let index = 0; index < half; index++) {
if (word[index] !== word[length - index - 1]) {
return false;
}
}
return true;
}
isPalindrome('madam'); // => true
isPalindrome('hello'); // => false

function isPalindrome(word) {
const length = word.length;
const half = Math.floor(length / 2);
for (let index = 0; index < half; index++) {
if (word[index] !== word[length - index - 1]) {
return false;
}
}
return true;
}
isPalindrome('madam'); // => true
isPalindrome('hello'); // => false

​length​​ and ​​half​​ variables are assigned with a value once. Seems reasonable to declare them as ​​const​​, since these variables are not going to change.​​let​​ declaration. Whenever possible assign an initial value to it right away, e.g. ​​let index = 0​​.​​var​​? In terms of ES2015, my suggestion is ​​​stop using it at all​​.




​var​​ declaration problem is the ​​​variable hoisting​​​ in the entire function scope. You can declare a ​​var​​ variable somewhere at the end of the function scope, but still it can accessed before declaration: and you'll get an ​​undefined​​.

​​Try in repl.it​​


function bigFunction() {  
// code...
myVariable; // => undefined
// code...
var myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();

function bigFunction() {
// code...
myVariable; // => undefined
// code...
var myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();

​myVariable​​ is accessible and contains ​​undefined​​ even before the declaration line: ​​var myVariable = 'Initial value'​​.​​let​​ (including ​​const​​) 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 ​​undefined​​.​​let​​ (instead of ​​var​​) throws a ​​ReferenceError​​, because the variable in the temporal dead zone is not accessible.

​​Try in repl.it​​


function bigFunction() {  
// code...
myVariable; // => Throws 'ReferenceError: myVariable is not defined'
// code...
let myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();

function bigFunction() {
// code...
myVariable; // => Throws 'ReferenceError: myVariable is not defined'
// code...
let myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();

​const​​ for immutable bindings or ​​let​

 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.

​const​​ or ​​let​​​​for​

function someFunc(array) {  
var index, item, length = array.length;
// some code...
// some code...
for (index = 0; index < length; index++) {
item = array[index];
// some code...
}
return 'some result';
}

function someFunc(array) {
var index, item, length = array.length;
// some code...
// some code...
for (index = 0; index < length; index++) {
item = array[index];
// some code...
}
return 'some result';
}

​index​​, ​​item​​ and ​​length​​​​for​​ statement the variables ​​index​​, ​​item​​ are uninitialized and exposed to ​​undefined​​. 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:

function someFunc(array) {  
// some code...
// some code...
const length = array.length;
for (let index = 0; index < length; index++) {
const item = array[index];
// some
}
return 'some result';
}

function someFunc(array) {
// some code...
// some code...
const length = array.length;
for (let index = 0; index < length; index++) {
const item = array[index];
// some
}
return 'some result';
}

​index​​ and ​​item​​ variables exist only in the block scope of ​​for​​ statement. They don't have any meaning outside of ​​for​​. 

​length​

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 

​undefined​

  • 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

When accessing a non-existing object property, JavaScript returns undefined.

Let's demonstrate that in an example:


​​Try in repl.it​​


let favoriteMovie = {  
title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined

let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined

​favoriteMovie​​ is an object with a single property ​​title​​. Accessing a non-existing property ​​actors​​ using a property accessor ​​favoriteMovie.actors​​ is evaluated to ​​undefined​​.​​undefined​​ related trap, reflected in the well known error message ​​TypeError: Cannot read property <prop> of undefined​​.​​TypeError​

​​Try in repl.it​​


let favoriteMovie = {  
title: 'Blade Runner'
};
favoriteMovie.actors[0];
// TypeError: Cannot read property '0' of undefined

let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors[0];
// TypeError: Cannot read property '0' of undefined

​favoriteMovie​​ does not have the property ​​actors​​, so ​​favoriteMovie.actors​​ evaluates to ​​undefined​​. 

As result, accessing the first item of an ​​undefined​​ value using the expression ​​favoriteMovie.actors[0]​​ throws a ​​TypeError​​.

The permissive nature of JavaScript that allows to access non-existing properties is a source of confusion: the property may be set, or may be not. The ideal way to bypass this problem is to restrict the object to have always defined the properties that it holds.

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.

​append(array, toAppend)​​ that adds at the beginning and/or at the end of an array new elements. ​​toAppend​​​​first​

  • : element inserted at the beginning of 

​array​​​​last​

  • : element inserted at the end of 

​array​

  • .

The function returns a new array instance, without altering the original array (i.e. it's a ​​pure function​​).

​append()​​, a bit naive, may look like this:

​​Try in repl.it​​


function append(array, toAppend) {  
const arrayCopy = array.slice();
if (toAppend.first) {
arrayCopy.unshift(toAppend.first);
}
if (toAppend.last) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append(['Hello'], { last: 'World' }); // => ['Hello', 'World']
append([8, 16], { first: 4 }); // => [4, 8, 16]

function append(array, toAppend) {
const arrayCopy = array.slice();
if (toAppend.first) {
arrayCopy.unshift(toAppend.first);
}
if (toAppend.last) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append(['Hello'], { last: 'World' }); // => ['Hello', 'World']
append([8, 16], { first: 4 }); // => [4, 8, 16]

​toAppend​​ object can omit ​​first​​ or ​​last​​ properties, it is obligatory to verify whether these properties exist in ​​toAppend​​.​​undefined​​ if the property does not exist. The first temptation to check weather ​​first​​ or ​​last​​ properties are present is to verify them against ​​undefined​​. Let's do the verification in conditionals ​​if(toAppend.first){}​​ and ​​if(toAppend.last){}​​...Not so fast. There is a serious drawback in this approach. ​​undefined​​, as well as ​​false​​, ​​null​​, ​​0​​, ​​NaN​​ and ​​''​​ are ​​​falsy​​​ values.​​append()​​, the function doesn't allow to insert falsy elements:

​​Try in repl.it​​


append([10], { first: 0, last: false }); // => [10]  

append([10], { first: 0, last: false }); // => [10]

​0​​ and ​​false​​ are falsy. Because ​​if(toAppend.first){}​​ and ​​if(toAppend.last){}​​ actually compare against falsy, these elements are not inserted into the array. The function returns the initial array ​​[10]​

The tips that follow explain how to correctly check the property existence.

 Tip 3: Check the property existence

Fortunately, JavaScript offers a bunch of ways to determine if the object has a specific property:

​obj.prop !== undefined​

  • : compare against 

​undefined​​​​typeof obj.prop !== 'undefined'​

  • : verify the property value type

​obj.hasOwnProperty('prop')​

  • : verify whether the object has an own property

​'prop' in obj​

  • : verify whether the object has an own or inherited property

​in​​ operator. It has a short and sweet syntax. ​​in​​ operator presence suggests a clear intent of checking whether an object has a specific property, without



​obj.hasOwnProperty('prop')​​ is a nice solution too. It's slightly longer than ​​in​​​​undefined​​ might work... But it seems to me that ​​obj.prop !== undefined​​ and ​​typeof obj.prop !== 'undefined'​​ look verbose and weird, and expose to a suspicions path of dealing directly with ​​undefined​​.​​append(array, toAppend)​​ function using ​​in​

​​Try in repl.it​​


function append(array, toAppend) {  
const arrayCopy = array.slice();
if ('first' in toAppend) {
arrayCopy.unshift(toAppend.first);
}
if ('last' in toAppend) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append([10], { first: 0, last: false }); // => [0, 10, false]

function append(array, toAppend) {
const arrayCopy = array.slice();
if ('first' in toAppend) {
arrayCopy.unshift(toAppend.first);
}
if ('last' in toAppend) {
arrayCopy.push(toAppend.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append([10], { first: 0, last: false }); // => [0, 10, false]

​'first' in toAppend​​ (and ​​'last' in toAppend​​) is ​​true​​ whether the corresponding property exists, ​​false​​​​in​​ operator fixes the problem with inserting falsy elements ​​0​​ and ​​false​​. Now, adding these elements at the beginning and at the end of ​​[10]​​ produces the expected result ​​[0, 10, false]​​.

 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.

​in​

​​Try in repl.it​​


const object = { };  
const prop = 'prop' in object ? object.prop : 'default';
prop; // => 'default'

const object = { };
const prop = 'prop' in object ? object.prop : 'default';
prop; // => 'default'

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.

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 ​​undefined​​.

Indeed, the property extraction now looks short and meaningful:


​​Try in repl.it​​


const object = {  };  
const { prop = 'default' } = object;
prop; // => 'default'

const object = { };
const { prop = 'default' } = object;
prop; // => 'default'

To see things in action, let's define an useful function that wraps a string in quotes. 

​quote(subject, config)​​ accepts the first argument as the string to be wrapped. The second argument ​​config​​​​char​

  • : the quote char, e.g. 

​'​

  •  (single quote) or 

​"​

  •  (double quote). Defaults to 

​"​

  • .

​skipIfQuoted​

  • : the boolean value to skip quoting if the string is already quoted. Defaults to 

​true​

  • .

​quote()​​:

​​Try in repl.it​​


function quote(str, config) {  
const { char = '"', skipIfQuoted = true } = config;
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'

function quote(str, config) {
const { char = '"', skipIfQuoted = true } = config;
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'

​const { char = '"', skipIfQuoted = true } = config​​ destructuring assignment in one line extracts the properties ​​char​​ and ​​skipIfQuoted​​ from ​​config​​ object. 

If some properties are not available in ​​config​​ object, the destructuring assignment sets the default values: ​​'"'​​ for ​​char​​ and ​​false​​ for ​​skipIfQuoted​​.

Fortunately, the function still has room for improvements.

​{ }​​) for ​​config​

​​Try in repl.it​​


function quote(str, { char = '"', skipIfQuoted = true } = {}) {  
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('Sunny day'); // => '"Sunny day"'

function quote(str, { char = '"', skipIfQuoted = true } = {}) {
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('Sunny day'); // => '"Sunny day"'

​config​​ parameter in function's signature. I like that: ​​quote()​​ 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 ​​quote('Sunny day')​​.​​undefined​

 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.

​Object.assign(target, source1, source2, ...)​​​​unsafeOptions​​​​undefined​​ when accessing a non-existing property from ​​unsafeOptions​​, let's make some adjustments:

  • Define an object 

​defaults​

  • Call 

​Object.assign({ }, defaults, unsafeOptions)​

  •  to build a new object 

​options​

  • . The new object receives all properties from 

​unsafeOptions​

  • , but the missing ones are taken from 

​defaults​

  • .

​​Try in repl.it​​


const unsafeOptions = {  
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = Object.assign({}, defaults, unsafeOptions);
options.fontSize; // => 18
options.color; // => 'black'

const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = Object.assign({}, defaults, unsafeOptions);
options.fontSize; // => 18
options.color; // => 'black'

​unsafeOptions​​ contains only ​​fontSize​​ property. ​​defaults​​ object defines the default values for properties ​​fontSize​​ and ​​color​​.​​Object.assign()​​ takes the first argument as a target object ​​{}​​. The target object receives the value of ​​fontSize​​ property from ​​unsafeOptions​​ source object. And the value of ​​color​​ property from ​​defaults​​ source object, because ​​unsafeOptions​​ doesn't contain ​​color​​. 

The order in which the source objects are enumerated does matter: later source object properties overwrite earlier ones.​​options​​ object, including ​​options.color​​that wasn't available in ​​unsafeOptions​

Fortunately exists an easier and lighter way to fill the object with default properties. I recommend to use a new JavaScript feature (now at ​​stage 3​​​) that allows to ​​spread properties in object initializers​​.

​Object.assign()​

​​Try in repl.it​​


const unsafeOptions = {  
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = {
...defaults,
...unsafeOptions
};
options.fontSize; // => 18
options.color; // => 'black'

const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = {
...defaults,
...unsafeOptions
};
options.fontSize; // => 18
options.color; // => 'black'

​defaults​​ and ​​unsafeOptions​​​​undefined​

2.3 Function parameters

The function parameters implicitly default to undefined.

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:


​​Try in repl.it​​


function multiply(a, b) {  
a; // => 5
b; // => 3
return a * b;
}
multiply(5, 3); // => 15

function multiply(a, b) {
a; // => 5
b; // => 3
return a * b;
}
multiply(5, 3); // => 15

​multiply(5, 3)​​ makes the parameters ​​a​​ and ​​b​​ receive the corresponding ​​5​​ and ​​3​​ values. The multiplication is calculated as expected: ​​5 * 3 = 15​​.​​undefined​​.

Let's slightly modify the previous example by calling the function with just one argument:


​​Try in repl.it​​


function multiply(a, b) {  
a; // => 5
b; // => undefined
return a * b;
}
multiply(5); // => NaN

function multiply(a, b) {
a; // => 5
b; // => undefined
return a * b;
}
multiply(5); // => NaN

​function multiply(a, b) { }​​ is defined with two parameters ​​a​​ and ​​b​​. 

The invocation ​​multiply(5)​​ is performed with a single argument: as result ​​a​​parameter is ​​5​​, but ​​b​​ parameter is ​​undefined​​.

 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.

​b​​ parameter is ​​undefined​​, it gets assigned with a default value of ​​2​​:

​​Try in repl.it​​


function multiply(a, b) {  
if (b === undefined) {
b = 2;
}
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10

function multiply(a, b) {
if (b === undefined) {
b = 2;
}
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10

​multiply(5)​​. Initially ​​a​​ parameter is ​​2​​and ​​b​​ is ​​undefined​​. 

The conditional statement verifies whether ​​b​​ is ​​undefined​​. If it happens, ​​b = 2​​assignment sets a default value.​​undefined​​. 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 ​​undefined​​.​​b​

​​Try in repl.it​​


function multiply(a, b = 2) {  
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
multiply(5, undefined); // => 10

function multiply(a, b = 2) {
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
multiply(5, undefined); // => 10

​b = 2​​ in the function signature makes sure that if ​​b​​ is ​​undefined​​, the parameter is defaulted to ​​2​​.

ES2015 default parameters feature is intuitive and expressive. Always use it to set default values for optional parameters.

2.4 Function return value

Implicitly, without return statement, a JavaScript function returns undefined.

​return​​ statements implicitly returns an ​​undefined​​:

​​Try in repl.it​​


function square(x) {  
const res = x * x;
}
square(2); // => undefined

function square(x) {
const res = x * x;
}
square(2); // => undefined

​square()​​ function does not return any computation results. The function invocation result is ​​undefined​​.​​return​

​​Try in repl.it​​


function square(x) {  
const res = x * x;
return;
}
square(2); // => undefined

function square(x) {
const res = x * x;
return;
}
square(2); // => undefined

​return;​​ statement is executed, but it doesn't return any expression. The invocation result is also ​​undefined​​.​​return​

​​Try in repl.it​​


function square(x) {  
const res = x * x;
return res;
}
square(2); // => 4

function square(x) {
const res = x * x;
return res;
}
square(2); // => 4

​4​​, which is ​​2​

 Tip 7: Don't trust the automatic semicolon insertion

​;​​):

  • empty statement

​let​

​const​

​var​

​import​

​export​

  • expression statement

​debugger​​​​continue​

  •  statement, 

​break​​​​throw​​​​return​

If you use one of the above statements, be sure to indicate a semicolon at the end:


​​Try in repl.it​​


function getNum() {  
// Notice the semicolons at the end
let num = 1;
return num;
}
getNum(); // => 1

function getNum() {
// Notice the semicolons at the end
let num = 1;
return num;
}
getNum(); // => 1

​let​​ declaration and ​​return​

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:


​​Try in repl.it​​


function getNum() {  
// Notice that semicolons are missing
let num = 1
return num
}
getNum() // => 1

function getNum() {
// Notice that semicolons are missing
let num = 1
return num
}
getNum() // => 1

The above text is a valid JavaScript code. The missing semicolons are automatically inserted for you.

At first sight, it looks pretty promising. ASI mechanism lets you skip the unnecessary semicolons. You can make the JavaScript code smaller and easier to read.

​return​​ and the returned expression ​​return \n expression​​, ASI automatically inserts a semicolon before the newline ​​return; \n expression​​.​​return;​​ statement? The function returns ​​undefined​​. If you don't know in details the mechanism of ASI, the unexpectedly returned ​​undefined​​​​getPrimeNumbers()​

​​Try in repl.it​​


function getPrimeNumbers() {  
return
[ 2, 3, 5, 7, 11, 13, 17 ]
}
getPrimeNumbers() // => undefined

function getPrimeNumbers() {
return
[ 2, 3, 5, 7, 11, 13, 17 ]
}
getPrimeNumbers() // => undefined

​return​​ statement and the array literal expression exists a new line. JavaScript automatically inserts a semicolon after ​​return​​, interpreting the code as follows:

​​Try in repl.it​​


function getPrimeNumbers() {  
return;
[ 2, 3, 5, 7, 11, 13, 17 ];
}
getPrimeNumbers(); // => undefined

function getPrimeNumbers() {
return;
[ 2, 3, 5, 7, 11, 13, 17 ];
}
getPrimeNumbers(); // => undefined

​return;​​ makes the function ​​getPrimeNumbers()​​ to return ​​undefined​​instead of the expected array.​​return​

​​Try in repl.it​​


function getPrimeNumbers() {  
return [
2, 3, 5, 7, 11, 13, 17
];
}
getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]

function getPrimeNumbers() {
return [
2, 3, 5, 7, 11, 13, 17
];
}
getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]

My recommendation is to study ​​how exactly​​ Automatic Semicolon Insertion works to avoid such situations.

​return​

void

​void expression​​ evaluates the expression and returns ​​undefined​

​​Try in repl.it​​


void 1;                    // => undefined  
void (false); // => undefined
void {name: 'John Smith'}; // => undefined
void Math.min(1, 3); // => undefined

void 1; // => undefined
void (false); // => undefined
void {name: 'John Smith'}; // => undefined
void Math.min(1, 3); // => undefined

​​One use case​​​ of ​​void​​ operator is to suppress expression evaluation to ​​undefined​​, relying on some side-effect of the evaluation.

undefined

​undefined​

​​Try in repl.it​​


const colors = ['blue', 'white', 'red'];  
colors[5]; // => undefined
colors[-1]; // => undefined

const colors = ['blue', 'white', 'red'];
colors[5]; // => undefined
colors[-1]; // => undefined

​colors​​ array has 3 elements, thus valid indexes are ​​0​​, ​​1​​ and ​​2​​. 

Because there are no array elements at indexes ​​5​​ and ​​-1​​, the accessors ​​colors[5]​​ and​​colors[-1]​​ are ​​undefined​​.

In JavaScript you might encounter so called sparse arrays. Theses are arrays that have gaps, i.e. at some indexes no elements are defined.

​undefined​​.

The following example generates sparse arrays and tries to access their empty slots:


​​Try in repl.it​​


const sparse1 = new Array(3);  
sparse1; // => [<empty slot>, <empty slot>, <empty slot>]
sparse1[0]; // => undefined
sparse1[1]; // => undefined
const sparse2 = ['white', ,'blue']
sparse2; // => ['white', <empty slot>, 'blue']
sparse2[1]; // => undefined

const sparse1 = new Array(3);
sparse1; // => [<empty slot>, <empty slot>, <empty slot>]
sparse1[0]; // => undefined
sparse1[1]; // => undefined
const sparse2 = ['white', ,'blue']
sparse2; // => ['white', <empty slot>, 'blue']
sparse2[1]; // => undefined

​sparse1​​ is created corresponding by invoking an ​​Array​​ constructor with a numeric first argument. It has 3 empty slots. 

​sparse2​​ is created with an array literal with the missing second element. 

In any of these sparse arrays accessing an empty slot evaluates to ​​undefined​​.​​undefined​​, be sure to use valid array indexes and avoid at all creating sparse arrays.

undefined and null

​undefined​​ and ​​null​​? Both special values imply an empty state.​​undefined​​ represents a value of a variable that wasn't yet initialized, while ​​null​

Let's explore the difference in some examples.

​number​

​​Try in repl.it​​


let number;  
number; // => undefined

let number;
number; // => undefined

​number​​ variable is ​​undefined​​, which clearly indicates an uninitialized

non-existing object property


​​Try in repl.it​​


const obj = { firstName: 'Dmitri' };  
obj.lastName; // => undefined

const obj = { firstName: 'Dmitri' };
obj.lastName; // => undefined

​lastName​​ property does not exist in ​​obj​​, JavaScript correctly evaluates ​​obj.lastName​​ to ​​undefined​​.​​null​​ is a meaningful indicator of a missing object.​​clone()​

​​Try in repl.it​​


function clone(obj) {  
if (typeof obj === 'object' && obj !== null) {
return Object.assign({}, obj);
}
return null;
}
clone({name: 'John'}); // => {name: 'John'}
clone(15); // => null
clone(null); // => null

function clone(obj) {
if (typeof obj === 'object' && obj !== null) {
return Object.assign({}, obj);
}
return null;
}
clone({name: 'John'}); // => {name: 'John'}
clone(15); // => null
clone(null); // => null

​clone()​​ might be invoked with a non-object argument: ​​15​​ or ​​null​​ (or generally a primitive value, ​​null​​ or ​​undefined​​). In such case the function cannot create a clone, so it returns ​​null​​​​typeof​

​​Try in repl.it​​


typeof undefined; // => 'undefined'  
typeof null; // => 'object'

typeof undefined; // => 'undefined'
typeof null; // => 'object'

The ​​strict quality operator​​​ ​​===​​ correctly differentiates ​​undefined​​ from ​​null​​:

​​Try in repl.it​​


let nothing = undefined;  
let missingObject = null;
nothing === missingObject; // => false

let nothing = undefined;
let missingObject = null;
nothing === missingObject; // => false

5. Conclusion

​undefined​

  • 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

​undefined​​​​undefined​​ keyword in your code. In the meantime, always

  • 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 

​const​

  • , otherwise use 

​let​

  • 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



举报

相关推荐

0 条评论