重学 Undefined

692 阅读5分钟

是什么

undefined 是一个全局的变量标识符,其值是 undefined,它是 JavaScript 的原始类型(Primitive)之一,表示原始值未定义。

既然是原始值,那么代表着其值是不可改变的,不可以像修改对象的值那样来改变 undefined 的值。但是由于 JavaScript 早年的一个设计失误,导致 undefined 变量是一个全局变量而不是关键字。这带来的问题就是在某些情况下可以篡改这个变量的值。

//  logs "foo string"
(function() {
  var undefined = 'foo';
  console.log(undefined, typeof undefined);
})();

//  logs "foo string"
(function(undefined) {
  console.log(undefined, typeof undefined);
})('foo');

如上代码可以看到,当在函数中对 undefined 变量进行重新声明时是可以改变 undefined 变量的值的。在现行的规范中表明,在全局作用域下,undefined 变量是不可改变不可从全局对象中删除的。

由此我们知道了,在全局环境中全局对象 window 上有一个变量标识符 undefined,其值是 undefined,在全局环境中不可修改 undefined 变量的值,而函数作用域中可对 undefined 变量标识符重新声明。需要注意的是,这里说的是重新声明,需要将 undefined 作为函数的形参或者使用变量声明标识符重新声明,因此对于这个现象我们使用作用域的知识很容易就解释明白了。

所以,最后用作用域的知识来解释就是:核心关键在于 undefined 是一个变量标识符而不是关键字,因此在函数中我们可以用一个“长”的和 undefined 一模一样的名字来做变量名进行声明,声明后的该变量作用范围就是该函数作用域内,这样在函数内访问 undefined 变量时访问的就是刚刚声明的新变量,而不是全局环境中之前就存在的变量,如下代码所示

var a = 1;

(function() {
  var a = 'foo';
  console.log(a, typeof a);
})();

根据上面的这段代码,你看最终输出的值是“ foo string ”而不是“1 number”,想必这么一类比你应该就清楚了。

现在再来看下面这段代码,思考一下这段代码的输出结果是什么:

var undefined = 1;
(function() {
  undefined = 'foo';
  console.log(undefined, typeof undefined);
})();

怎么产生

产生 undefined 值的方式有很多种,最常见的如 “声明一个变量但是不对变量进行赋值”、“声明一个函数,函数中没有 return”以及“创建一个对象,访问该对象中不存在的字段 key”等等

var x ;

function a(){}
var x1 = a();

var x2={a:1};
x2.b;

还有一种场景可能经常发生但是不被人在意的是,在声明和赋值前访问该变量(也就是常说的变量提升)

console.log(x3);
var x3 = 1;

怎么使用

到目前为止,我们已经知道了 undefined 表示的是“未定义”,定义这个词在中文语境中我们往往会混淆,比如刚刚例子中提到的 “var x”明明已经声明了,但是怎么会是未定义呢。对此,我的观点是,结合经验以及实际生产中看到的现象,undefined 表示的场景往往都是声明了变量但是没有给变量赋初始值(注意我这里一直在用的词是“声明”而不是“定义”),也就是说 undefined 表示的是“值不存在”,而不是变量不存在,所以这个翻译过来的译文“未定义”含义就明确了:“声明了变量,但是访问的变量里某个值不存在”。

var x;
if (typeof x === 'undefined') {
    console.log(1);
}
if (x === undefined) {
  console.log(2);
}

// 分别打印 1  2

而如果是变量不存在的话,JS 引擎会报错 Uncaught ReferenceError: xxx is not defined

if (xxx === undefined) {}

在一些最佳实践或者说代码规范中,往往会看到说“ 禁止在比较变量值是否 undefined 时使用 ‘==’,使用‘===’替换”。这是因为“==”在比较时会存在跨类型比较的情况,会将 “null”的值也拿出来比对,因此会混淆 undefined 和 null,而“===”这种严格比对的模式就不会。

最佳实践

在一些大的项目中,往往都会期望代码具有高稳定性和高可维护性,对于这种可能引起歧义或者异常的代码编写方式常常都是严格禁止的,大家都会期望用 undefined 来语义化表示变量声明了未赋值,用 null 来语义化变量赋值了但是值是“空”,因此就有了以下的最佳实践:

  1. 不再使用 undefined 变量来获取 undefined 值,而是使用 void 操作符,比如 void 0
  2. 在声明变量时,考虑根据类型和具体场景对变量进行初始化赋值,比如变量用来表示数字可以用 0 ,用来表示字符串可以用“""”,用来表示对象可以用 null
  3. 在进行比较时,严格控制使用 ===

总结

undefined 是变量不是关键字,undefined 变量用来获取 undefined 值,其类型也是 undefined ,用来表示变量声明了但是未初始化赋值。在全局环境中该变量是不可写的状态,无法对其重新赋值,但是在函数作用域中就可以重新声明然后赋值,这时候函数中的 undefined 跟全局环境中的 undefined 完全就是两个不同的东西。

在最佳实践中使用 void 操作符来代替 undefined 变量以获取 undefined 值,并且在比较时使用严格等于“===”。

参考资料:

  1. undefined-mdn
  2. undefined in ECMAScript262