• Undefined vs. null revisited
  • Original author: Dr. Axel Rauschmayer
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Hoarfroster
  • Proofread by Moonball and felixliao

Revisit undefined and NULL

Many programming languages have a type that represents a null value, called NULL. It indicates that a variable does not currently point to any object — for example, when a variable has not been initialized.

JavaScript, on the other hand, has two types that represent null values: undefined and null. In this article, we’ll test the differences and how to pick the best types or avoid using them.

undefined vs. null

The two values are very similar and are often used interchangeably, so the differences between them are subtle.

undefined,nullComparison on the ECMAScript language standard

The ECMAScript language standard describes them as follows:

  • undefinedUsed when a variable has not yet been assigned a value.provenance
  • nullRepresents any intentional default object value.provenance

We’ll explore how we, as programmers, can best use these two values in a moment.

Two null values — an irreparable error

Having two values representing null values in JavaScript at the same time is now considered a design error (even by Brendan Eich, the father of JavaScript).

So why not remove one of these two values from JavaScript? One of the core tenets of JavaScript is to never break backward compatibility. This principle has advantages, but it also has the biggest disadvantage of not being able to remedy design errors.

undefinednullThe history of the

In Java (a language that influences many aspects of JavaScript) the initial value depends on the static type of a variable:

  • A variable of type object value is initialized tonull.
  • Each base type has its initial value, for exampleintAn integer corresponding to the0.

In JavaScript, each variable can store either the object value or the original value, meaning that if null means that it is not an object, JavaScript also needs an initial value to indicate that it is neither an object nor has the original value, which is called undefined.

undefinedThe appearance of

If a variable myVar has not been initialized, its value is undefined:

let myVar;
assert.equal(myVar, undefined);
Copy the code

If a.unknownprop attribute does not exist, accessing it generates undefined:

const obj = {};
assert.equal(obj.unknownProp, undefined);
Copy the code

If a function does not explicitly return anything, undefined is returned by default:

function myFunc() {
}

assert.equal(myFunc(), undefined);
Copy the code

If a function has a return statement but does not specify any return value, undefined is also returned by default:

function myFunc() {
    return;
}

assert.equal(myFunc(), undefined);
Copy the code

If a parameter x does not pass an argument, it is initialized to undefined:

function myFunc(x) {
    assert.equal(x, undefined);
}

myFunc();
Copy the code

Through the obj? .someprop access optional chain returns undefined when obj is undefined or null:

> undefined? .someProp undefined > null? .someProp undefinedCopy the code

nullThe appearance of

The prototype of an object is either another object or null at the end of the prototype chain. Prototype:

> Object.getPrototypeOf(Object.prototype)
null
Copy the code

If we use a regular expression (such as /a/) to match a string (such as x), we get either an object holding the match data (if the match is successful) or NULL (if the match fails).

> /a/.exec('x')
null
Copy the code

The JSON data format does not support undefined, but only null:

> JSON.stringify({a: undefined, b: null})
'{"b":null}'
Copy the code

Designed to deal withundefinednullThe operator of

undefinedAnd default parameter values

The default value of a parameter is used when:

  • This parameter is ignored.
  • This parameter is assigned toundefinedValue.

Here’s an example:

function myFunc(arg = 'abc') {
    return arg;
}

assert.equal(myFunc('hello'), 'hello');
assert.equal(myFunc(), 'abc');
assert.equal(myFunc(undefined), 'abc');
Copy the code

Undefined also fires the default parameter value when the value pointing to it is a meta value.

The following example demonstrates where this feature can be useful:

function concat(str1 = ' ', str2 = ' ') {
    return str1 + str2;
}

function twice(str) { // (A)
    return concat(str, str);
}
Copy the code

In line A, we do not specify A default value for the STR argument, and when this parameter is ignored, we forward the state to concat() to select the default value.

undefinedDestruct the default values

Defaults under deconstruction work like parameter defaults — if variables do not match in the data or match undefined, they are used:

const [a = 'a'] = [];
assert.equal(a, 'a');

const [b = 'b'] = [undefined];
assert.equal(b, 'b');

const {prop: c = 'c'} = {};
assert.equal(c, 'c');

const {prop: d = 'd'} = {prop: undefined};
assert.equal(d, 'd');
Copy the code

undefined,nullAnd optional chain

If you use value? .prop uses optional chains:

  • ifvalueundefinednullWill returnundefined. In other words, ifvalue.propIf an error is thrown, it will returnundefined.
  • Otherwise it will returnvalue.prop.
function getProp(value) {
    // Optional static property access
    returnvalue? .prop; } assert.equal( getProp({prop: 123}), 123);
assert.equal(
    getProp(undefined), undefined);
assert.equal(
    getProp(null), undefined);
Copy the code

The following two operations work similarly:

obj? Issue. [expr]// Optional dynamic property accessfunc? . (the « arg0 », « arg1 »)// Optional function or method call
Copy the code

undefined,nullAnd empty

The null merge operator?? Allows us to use the default value when a value is undefined or null:

> undefined ?? 'default value'
'default value'
> null ?? 'default value'
'default value'

> 0 ?? 'default value'
0
> 123 ?? 'default value'
123
> '' ?? 'default value'
''
> 'abc' ?? 'default value'
'abc'
Copy the code

The null merge assignment operator?? = merges the null merge operator with the assignment operator:

function setName(obj) {
    obj.name ??= '(Unnamed)';
    return obj;
}

assert.deepEqual(
    setName({}),
    {name: '(Unnamed)'}); assert.deepEqual( setName({name: undefined{}),name: '(Unnamed)'}); assert.deepEqual( setName({name: null{}),name: '(Unnamed)'}); assert.deepEqual( setName({name: 'Jane'{}),name: 'Jane'});Copy the code

To deal withundefinednull

The following sections explain the most common ways to handle undefined and NULL in our code:

The actual value is neitherundefinedIs notnull

For example, we might want the attribute file.title to always exist and always be a string, so there are two common ways to do this.

Note that in this blog post, we only check for undefined and NULL, not for strings. You need to decide for yourself whether you want to add a checker as an additional security measure.

At the same time is prohibitedundefinednull

Such as:

function createFile(title) {
    if (title === undefined || title === null) {
        throw new Error('`title` must not be nullish');
    }
    / /...
}
Copy the code

Why did you choose this method?

  • We want to handle undefined and null the same way, because JavaScript code often does this, for example:

    // Check if an attribute exists
    if(! obj.requiredProp) { obj.requiredProp =123;
    }
    
    // Use the default values through the null merge operator
    const myValue = myParameter ?? 'some default';
    
    Copy the code
  • If something goes wrong in our code and we let undefined or NULL appear, we need to let it end execution as soon as possible and throw an error.

At the same timeundefinednullUse default values

Such as:

function createFile(title) { title ?? ='(Untitled)';
    / /...
}
Copy the code

We cannot use the parameter default because it will only be triggered by undefined. Here we rely on the null merge assignment operator?? =.

Why did you choose this method?

  • We want to be treated the same wayundefinednull(See above).
  • We want our code to be treated quietly but forcefullyundefinednull.

undefinednullIs an ignored value

For example, we might want the attribute file.title to be a string or an ignored value (that is, file has no title), and there are several ways to do this.

nullIs the ignored value

Such as:

function createFile(title) {
    if (title === undefined) {
        throw new Error(' 'title' should not be undefined');
    }
    return {title};
}
Copy the code

Alternatively, undefined can trigger the default:

function createFile(title = '(Untitled)') {
    return {title};
}
Copy the code

Why choose this method?

  • We need a null value to indicate that we are ignored.
  • We don’t want null values to trigger parameter defaults and break them.
  • We want to convert the null-valued string to JSON (which we can’tundefinedProcessing).

undefinedIs the ignored value

Such as:

function createFile(title) {
    if (title === null) {
        throw new Error(' 'title' should not be null');
    }
    return {title};
}
Copy the code

Why choose this method?

  • We need a null value to indicate that we are ignored.
  • We do want null values to trigger arguments or destruct defaults.

One drawback of undefined is that it is often given by accident in JavaScript — in uninitialized variables, typos in attribute names, forgetting to return content from functions, etc.

Why not at the same timeundefinednullAs a ignored value, right?

It makes sense to treat both undefined and NULL as “null” when a value is received. However, when we create values, we don’t want ambiguity to avoid unnecessary trouble.

This points to another Angle: what if we need a ignored value, but don’t want to use undefined or NULL as the ignored value? Check it out below:

Other ways to handle ignored values

Special values

We can create a special value that will be used whenever.title is ignored:

const UNTITLED = Symbol('UNTITLED');
const file = {
    title: UNTITLED,
};
Copy the code

Null object mode

The Null object pattern comes from OOP (object-oriented programming) :

  • All subclasses of a common superclass have the same interface.
  • Each subclass implements a different pattern for example use.
  • One of these patterns isnull.

In the following, UntitledFile inherits the “NULL” mode.

// Abstract superclass
class File {
    constructor(content) {
        if (new.target === File) {
            throw new Error('Can' t instantiate this class ');
        }
        this.content = content; }}class TitledFile extends File {
    constructor(content, title) {
        super(content);
        this.title = title;
    }

    getTitle() {
        return this.title; }}class UntitledFile extends File {
    constructor(content) {
        super(content);
    }

    getTitle() {
        return '(Untitled)'; }}const files = [
    new TitledFile('Dear diary! '.'My Diary'),
    new UntitledFile('Reminder: pick a title! ')]; assert.deepEqual( files.map(f= > f.getTitle()),
    [
        'My Diary'.'(Untitled)',]);Copy the code

We can also use the empty object mode only for the title, rather than the entire file object.

“Maybe” type

The “maybe” type is a functional programming technique:

function getTitle(file) {
    switch (file.title.kind) {
        case 'just':
            return file.title.value;
        case 'nothing':
            return '(Untitled)';
        default:
            throw new Error();
    }
}

const files = [
    {
        title: {kind: 'just'.value: 'My Diary'},
        content: 'Dear diary! '}, {title: {kind: 'nothing'},
        content: 'Reminder: pick a title! ',},]; assert.deepEqual( files.map(f= > getTitle(f)),
    [
        'My Diary'.'(Untitled)',]);Copy the code

We could have encoded “just” and “nothing” with arrays, but the nice thing about our approach is that TypeScript supports them well (via recognizable unions).

My method

I don’t like using undefined as a ignored value for three reasons:

  • undefinedIt’s usually an accident in JavaScript.
  • undefinedTriggers default values for parameters and destructions (some people prefer it for some reasonundefined).

Therefore, if special values are required, one of two methods can be used:

  • I will benullUsed as a ignored value. (TypeScript supports this approach relatively well, by the way.)
  • I avoided simultaneity by using one of the techniques aboveundefinednullThe advantage is that the code is cleaner, and the disadvantage is that it requires more work.

  • This article is participating in the “Nuggets 2021 Spring Recruitment Campaign”, click to see the details of the campaign

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.