javascript中null和undefined的区别到底是什么?

 

8年前我开始学习js的时候,对我来说比较诡异的一个事情是undefined和null都代表空值。那么他们之间明确的不同点是什么呢?他们都能去定义空值,而且null == undefined的值也是TRUE。

 

大部分现代语言像Ruby,Python,或者Java都只有一个空值nil 或者null,  这是很明智的方法。
而js中,如果一个变量或者一个对象没有进行初始化,(编译器)就会返回一个undefined.
例如:
let company;  
company;    // => undefined  
let person = { name: 'John Smith' };  
person.age; // => undefined  

而null是代表了缺失对象引用。js是不能给null设置变量或者对象属性的。一些原生方法像String.prototype.match()会返回null来表示缺失对象,看一下下面这个例子: 

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

因为js是非常不严格的,开发者们一不小心就会使用未初始化的值。我也犯过许多类似的错误。

通常那些有风险的操作都会产生undefined的相关错误,然后立刻结束脚本的运行。相关的错误信息一般都是:

  • TypeError: 'undefined' is not a function
  • TypeError: Cannot read property '<prop-name>' of undefined
  • and alike type errors.

js程序猿都能体会下面这个笑话的讽刺意味:

function undefined() {  
  // problem solved
}

 

为了减小此类错误的风险,你就必须要理解什么时候会出现undefined。并且更重要的是要去减少它的出现,让你的代码更加健壮耐用。

下面就详细的解释undefined和它对代码安全产生的影响。

1.什么是undefined?

js有6种基本数据类型

  • Booleantrue or false
  • Number16.70xFF
  • String"Gorilla and banana"
  • SymbolSymbol("name") (starting ES2015)
  • Nullnull
  • Undefinedundefined.

和一种单独的对象类型: {name: "Dmitri"}["apple", "orange"].

就这6种基本类型来说,undefined是一个 Undefined类型的特殊值。参照ECMAScript specification:

Undefined value primitive value is used when a variable has not been assigned a value.

当一个变量没有被赋值的时候,就会被赋值undefined。

这个标准清晰的定义了变量没有初始值,或者不存在的对象属性,或者不存在的数组元素,都会被赋值undefined。例如:

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

正如上面的例子所证明的那样:

  • an uninitialized variable number(未初始化的变量)
  • non-existing object property movie.year(不存在的对象属性)
  • or a non-existing array element movies[3](不存在的数组元素)

ECMSScript手册定义了undefined值的类型:

Undefined type is a type whose sole value is the undefined value.

Undefined是值是undefined的类型。

 

这样讲来,“typeof”操作一个未定义的值会返回字符串‘undefined’

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

所以typeof 可以很好的去核实一个变量是否是位定义的值

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

2.同样产生“undefined”的情况

2.1 未初始化的值

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

已声明但还没被赋值的变量会被赋值为undefined。

简单的例子:

let myVariable;  
myVariable; // => undefined  

myVariable 声明了但是还没被赋值,它的值就会是undefined。

一个解决未初始化值的有效途径就是尽可能的给一个初始值。未初始化的值越少存在越好。理想情况下你可以在声明myVariable之后立刻就赋值,但那通常来说又不现实。

 

建议1:优先const,其次let,告别var

在我看来,ECMAScript2015最好的特性之一就是用const和let生命变量的新方式。

这是声明块级作用域(和老的函数作用域var相反)和变量存在于暂存死区的一大进步。(这句翻译很差)

 

当一个变量赋值要永久存在,我推荐使用const 去声明。它会创建一个不可改变的变量。

const最好的特性之一就是你必须要给一个变量赋初始值,变量就不有没有初始化的情况。

我们来看一下这个可以验证一个单词是否是回文的函数:

【回文】对称

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"和“half”都被赋值,当这些变量不会改变的时候,就用const去声明。

如果需要去重新赋值变量,就用let去声明。这个时候就尽可能的给个初始值,比如let index=0。

那老的声明方式var呢?根据ES2015,我的建议是别再用它了。

Do not write var, write const and let in JavaScript

“var”声明的问题在于在整个函数作用域中的变量提升。你可以在在函数内部的最后声明变量,在声明之前它仍然是可以被访问的:你会得到一个undefined的值。

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

即使在变量被声明和赋值之前,myVariable变量也是可获取的,值是undefined。

相反的,用let 或者const声明的变量在声明前是不可访问的。因为在声明之前变量处在一个不可访问的区域---暂存死区。这点会更好,因为你会有更少的机会去访问一个undefined.

上面的例子用let替换var之后,就会跑出一个ReferenceError(引用错误), 因为在暂存死区的变量是不可访问的。

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

尽量去使用const定义不可改变的变量或者使用let可以确保你尽量少的忘记初始化变量。

 Tip 2: Increase cohesion增加凝聚力

内聚描述的是一个模块的元素的紧凑程度,内聚的衡量通常描述为高内聚和低内聚。

高内聚是更可取合理的因为它建议模块的元素完全专注于一个单独的任务。它可以是使模块拥有以下特性:

  • 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
  • 更容易测试:你可以更容易的去测试一个专注单独功能的模块
Components coupling and cohesion

高内聚通常都是低耦合,是一个好的设计系统的特征。

一个代码块就可以被当做是一个小模块,为了从高内聚中受益,你需要尽量的让变量离调用它的代码块近。

举个栗子,如果一个变量一直在一个代码块的逻辑中使用,那么久在这个代码块的内部去声明和使用,不要再代码块的外部暴露,那样外部的代码就不会知道这个变量。

一个不需要延长变量生命周期的经典例子就是在function中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';
}

index, item 和 length 这些变量在function开头被定义,但是它们只是在代码要结束的地方才被调用,搜易这种方式的问题是啥呢?

在头部的声明和for循环使用的整个过程item,index,item都是没有被初始化的,值都是undefined.它们在整个函数域中会有一个很长的生命周期而又毫无理由。

一个更好的方式是将这些变量尽可能使它们离调用的地方近:

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和item只会在for循环的作用域中存在,它们不会再for循环以外使用,length也是离它调用的地方很近。

为什么修改后的版本会比之前的要好呢?我们来看:

  • The variables are not exposed to uninitialized state, thus you have no risk of accessing undefined
  • 变量不会暴露未初始化的状态, 你就不会有获取到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 获取不存在的属性

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

当获取一个不存在的对象属性时,js会返回undefined

我们来看一个栗子

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

favoriteMovie是一个只要title属性的对象,当访问不存在的actors属性时,就会返回undefined

获取不存在的属性不会抛出错误,当尝试从不存在的属性中获取数据的时候问题才会出现。这是'TypeError: Cannot read property <prop> of undefined'这个错误信息反映出的错误问题。

我们稍微修改一下之前的代码片段来让它抛出错误:

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

favoriteMovie没有actors属性,所以favoriteMovie.actors就是undefined, 因此通过favoriteMovie.actors[0]再去访问一个undefined的第一个元素就会跑出一个TypeError.

Js宽泛的允许不存在属性的环境是造成错误的根源:属性可能设置了,也可能没有设置,绕过这个问题的解决办法就是限制它拥有的属性都要去定义。

不幸的是你通常都没有处理你用的对象,那些对象在不同的场景下有不同的属性设置,所以你不得不手动去处理这些情况。

我们去实现一个function,接受(array, config)参数,给数组的开始和结束增加新元素,config有两个属性:

  • first: element inserted at the beginning of array
  • last: element inserted at the end of array.

函数返回一个新的数组示例,不改变原有的数组

第一版的append可能比较幼稚,是这样的:

function append(array, config) {  
  const arrayCopy = array.slice();
  if (config.first) {
    arrayCopy.unshift(config.first);
  }
  if (config.last) {
    arrayCopy.push(config.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]  

因为config可能漏掉了first或者last属性,需要强制去校验在config这些属性是否存在。

如果属性不存在,访问的时候就会使undefined,首先要做的就是检查first或者last属性是否存在,去排除他们是undefined,通过if(config.first){} and if(config.last){}去验证。

太慢,这种方式有个很严重的问题,undefined,FALSE,null,0,NaN和'' 都是false,

当前append的实现中,不允许插入false值:

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

0和false都是否定的值,因为if(config.first){} and if(config.last){}排除了false,这些元素就不能被插入到数组中,这个函数没有处理就返回了[10]

下面解释一下如何正确的检查属性的存在

 Tip 3: Check the property existence检查属性的存在

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

庆幸的是,js提供了多种查明对象是否有某个属性的方式:

  • obj.prop !== undefined: compare against undefined directly
  • 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 操作符,简短好用。in操作符不用访问对象的属性就能清楚判断一个对象是否有某个属性。

Do not write var, write const and let in JavaScript

obj.hasOwnProperty('prop')也是一个很好的方式,它略微比in操作符长点,并且只能检验对象自身的属性。

和undefined对比的这两种方式也能使,但是我看着obj.prop !== undefined and typeof obj.prop !== 'undefined' 比较冗长而且怪异,直接的揭露对undefined的怀疑。

下面升级用in操作符升级append方法:

function append(array, config) {  
  const arrayCopy = array.slice();
  if ('first' in config) {
    arrayCopy.unshift(config.first);
  }
  if ('last' in config) {
    arrayCopy.push(config.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]  

config中first只要存在就是true,否则就是false。

in的使用修复了不能插入false的元素0,false,现在就可以产生预期的结果了[0, 10, false]

 Tip 4: Destructuring to access object properties

访问对象属性的时候,有时属性不存在的时候需要给一个初始值,你就可以用in和三元操作符去实现:

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

三元操作符的时候用会随着属性数量的增加变得恐怖,对于每个属性你都不得不写一行代码去处理默认值,就会创造出来一个丑陋的三元操作符墙。

为了用一种更优雅的方式,我们去认识一下es5一个很棒的特性--对象解构。

对象结构允许在对象的内部直接获取变量,并且设置默认值,一个非常方便处理undefined的语法。

这种属性抽取看起来也确实简短和有意义:

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 is an object with the properties:

  • 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.

Applying the benefits of the object destructuring, let's implement quote():

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.

Let's move the destructuring assignment right into the parameters section. And set a default value (an empty object { }) for config parameter, to skip the second argument when default settings are enough.

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"'  

Notice that a destructuring assignment replaces the 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').

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 undefined 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 Object.assign(target, source1, source2, ...) 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 unsafeOptions object, which not always contains its full set of properties.

To avoid undefined when accessing a non-existing property from unsafeOptions, let's make some adjustments:

  • Define an object defaults that holds the default property values
  • 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.
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.

You are now safe to access any property of options object, including options.colorthat wasn't available in unsafeOptions initially.

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.

Instead of Object.assign() invocation, use the object spread syntax to copy into target object all own and enumberable properties from source objects:

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

The object initializer spreads properties from defaults and unsafeOptions 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 undefined cannot be generated.

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:

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

The invocation 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.

What does happen when you omit an argument on invocation? The parameter inside the function becomes undefined.

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

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

function multiply(a, b) { } is fined with two parameters a and b
The invocation multiply(5) is performed with a single argument: as result aparameter 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.

Recalling the previous example, let's make an improvement. If b parameter is undefined, it gets assigned with a default value of 2:

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

The function is invoked with a single argument multiply(5). Initially a parameter is 2 and b is undefined
The conditional statement verifies whether b is undefined. If it happens, b = 2assignment sets a default value.

While the provided way to assign default values works, I don't recommend comparing directly against 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.

Modifying the previous example with a default parameter for b indeed looks great:

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.

In JavaScript a function that doesn't have any return statements implicitly returns an 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.

The same situation happens when return statement is present, but without an expression nearby:

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.

Of course, indicating near return the expression to be returned works as expected:

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

Now the function invocation is evaluated to 4, which is 2 squared.

 Tip 7: Don't trust the automatic semicolon insertion

The following list of statements in JavaScript must end with semicolons (;):

  • empty statement
  • letconstvarimportexport declarations
  • expression statement
  • debugger statement
  • continue statement, break statement
  • throw statement
  • return statement

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

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

At the end of both let declaration and return 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 insert for you the missing semicolons.

Being helped by ASI, you can remove the semicolons from the previous example:

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.

There is one small, but annoying trap created by ASI. When a newline stands between return and the returned expression return \n expression, ASI automatically inserts a semicolon before the newline return; \n expression.

What does mean inside a function to have return; statement? The function returns undefined. If you don't know in details the mechanism of ASI, the unexpectedly returned undefined is misleading.

For instance, let's study the returned value of getPrimeNumbers() invocation:

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

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

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

The statement return; makes the function getPrimeNumbers() to return undefinedinstead of the expected array.

The problem is solved by removing the newline between return and array literal:

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

My recommendation is to avoid relying on Automatic Semicolon Insertion. Put the necessary semicolons by yourself. 
Enabling the semi ESLint rule is helpful to identify the places where a semicolon is required at the end of statement.

2.5 void operator

void expression evaluates the expression and returns undefined no matter the result of evaluation.

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.

3. undefined in arrays

You get undefined when accessing an array element with an out of bounds index.

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

colors array has 3 elements, thus valid indexes are 01 and 2
Because there are no array elements at indexes 5 and -1, the accessors colors[5] andcolors[-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.

When a gap (aka empty slot) is accessed inside a sparse array, you also get an undefined.

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

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.

When working with arrays, to escape catching undefined, be sure to use valid array indexes and avoid at all creating sparse arrays.

4. Difference between undefined and null

A reasonable question appears: what is the main difference between undefined and null? Both special values imply an empty state.

The main difference is that undefined represents a value of a variable that wasn't yet initialized, while null represents an intentional absence of an object.

Let's explore the difference in some examples.

The variable number is defined, however is not assigned with an initial value:

let number;  
number; // => undefined  

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

The same uninitialized concept happens when a non-existing object property is accessed:

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

Because lastName property does not exist in obj, JavaScript correctly evaluates obj.lastName to undefined.

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 null is a meaningful indicator of a missing object.

For example, clone() is a function that clones a plain JavaScript object. The function is expected to return an object:

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  

However clone() might be invoked with a non-object argument: 15 or null (or generally a primitive value, null or undefined). In such case it's impossible to create a clone, so it sounds reasonable to return null - the indicator of a missing object.

typeof operator makes the distinction between the two values:

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

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

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

5. Conclusion(终章)

undefined的存在是js宽泛的环境导致的结果,允许以下使用:

  • 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 undefined is a bad practice, because you probably rely on a permitted but discouraged practice mentioned above.

通常直接使用undefined是不好的做法,因为你可能会依赖某个许可,但是也不鼓励使用上面提到的做法。(翻译的很差)

一个更有效率的策略是尽可能少在你的代码中出现undefined关键字,在此期间,要记得它可能出现的诡异方式,并且通过下面的好习惯去扼杀在摇篮中:

  • 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(使用const 和 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(避免使用元素太少的数组)

undefined关键字的使用是不好的做法?下面写个评论吧

注:只是做了翻译和解释,不为抄袭

【来源】https://rainsoft.io/7-tips-to-handle-undefined-in-javascript/

posted on 2017-04-26 11:35  N1coco  阅读(1375)  评论(0)    收藏  举报