zl程序教程

您现在的位置是:首页 >  工具

当前栏目

undefined vs null

vs null undefined
2023-06-13 09:11:27 时间

❝成为高手需要在没有感觉的情况下,蹚过漫长的无聊和低成就感时期, 蹚不过就一直是二流水平。 ❞

简明扼要

  1. 在JS中,存在两个空值 1. undefined 2. null
  2. Object.prototype不存在原型对象且值为null
  3. 假值:通过Boolean(X)强制类型转换后的值为false 1. undefined 2. null 3. Boolean: false 4. Numbers: 0, NaN 5. Bigint: 0n 6. String: ''

文章概要

  1. undefined vs null
  2. 如何产生undefinednull
  3. Null 判断运算符(??)的默认值 [es2020]
  4. undefinednull 没有任何属性
  5. undefinednull的历史

许多编程语言都有一个空值(non-value)null:表示存在一个变量但是没有指向一个对象。

但是,在JS中,存在两个空值 1. undefined 2. null

1. undefined vs null

一般情况下,这两个值在使用上都可以互换使用。只有在一些细微的方面存在差别。

  • undefined意味着:「未初始化」(例如:定义一个变量但是未初始化)或者「不存在」(例如:访问一个在对象中不存在的属性)
  • null意味着:故意将某个对象置为空 (可以参考tc39对Null的解释)

我们可以从使用上对其进行分类

  • undefined: 是语言层面上使用的非值(定义一个变量,但未赋值,此时该变量会被JS引擎自动赋为undefined)
  • null: 蓄意控制变量的值

2. 如何产生undefinednull

下面我们将从语言层面讨论undefinednull是如何产生。

2.1 undefined的产生

定义一个变量(myVar)但未进行初始化

let myVar;
myVar// undefined

调用函数,但是未提供参数(x)

function func(x) {
  return x;
}
func() // undefined

访问对象中不存在的属性(.unknownProp)

const obj = {};
// 访问属性
obj.unknownProp // undefined

调用一个没有return语句的函数

function func() {}
//调用函数
func() // undefined

2.2 null的产生

一个对象的原型属性(__proto__)要么指向一个对象,要么是指向原型链的末端(null)。

Object.prototype不存在原型对象且值为null

Object.getPrototypeOf(Object.prototype) // null

正则匹配失败

如果将一个正则表达式(如/a/)与一个字符串(如'x')进行匹配,要么得到一个具有匹配数据的对象(如果匹配成功),要么得到null(如果匹配失败)

//  匹配成功
/a/.exec('x') // ["a",index:0,input:"a",groups:undefined]
// 匹配失败
/a/.exec('x') // null

JSON格式的数据不支持undefined,支持null

JSON.stringify({a: undefined, b: null})
//格式化后的数据(不支持的数据被过滤了)
//'{b:null}'

3.Null 判断运算符(??)的默认值

有时候,我们只有在值为非undefined和非null的时候使用它,否则使用该值的默认值。我们可以通过Null 判断运算符(??)来实现该操作。

const valueToUse = receivedValue ?? defaultValue;

下面的语句是相等的。

a ?? b
a !== undefined && a !== null ? a : b

案例分析:正则匹配

function countMatches(regex, str) {
  const matchResult = str.match(regex); // null or Array
  return (matchResult ?? []).length;
}
// 匹配成功
countMatches(/a/g, 'ababa') // 3
// 匹配失败
countMatches(/x/g, 'ababa') // 0

在满足匹配条件的时候,matchResult为数组;而未匹配成功时,matchResultnull

我们也可以利用「链判断运算符」(optional chaining operator)?.(左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined)对上述代码进行优化处理。

return matchResult?.length ?? 0;

案例分析:为属性指定默认值

function getTitle(fileDesc) {
  return fileDesc.title ?? '(Untitled)';
}

const files = [
  {path: 'index.html', title: 'Home'},
  {path: 'tmp.html'},
];

files.map(f => getTitle(f)) //['Home', '(Untitled)']

使用结构为属性指定默认值

function getTitle(fileDesc) {
  const {title = '(Untitled)'} = fileDesc;
  return title;
}

传统做法:使用或(||)运算指定默认值

undefined || 'default' // 'default'

null || 'default' // 'default'

但是,针对「假值」,或运算也是会返回默认值。

❝假值:通过Boolean(X)强制类型转换后的值为false 1. undefined 2. null 3. Boolean: false 4. Numbers: 0, NaN 5. Bigint: 0n 6. String: ''

false || 'default' //'default'

0 || 'default' //'default'

0n || 'default' //'default'

'' || 'default' //'default'

逻辑赋值运算符(??=) [es2021]

下面的代码是等价的。

a ??= b
a ?? (a = b)

??=会发生「截断现象」:只有变量a的值为undefinednull才会发生赋值操作。

const books = [
  {
    isbn: '123',
  },
  {
    title: '柒八九',
    isbn: '456',
  },
];

// 如果对象不存在.title属性,才会被赋值
for (const book of books) {
  book.title ??= '(Untitled)';
}

books 
/*[
    {
      isbn: '123',
      title: '(Untitled)', // 发生了赋值操作
    },
    {
      title: 'ECMAScript Language Specification',
      isbn: '456',
    },
  ]
*/

4. undefinednull 没有任何属性

undefinednull是JS中仅有的两个变量:当试图读取它们的属性,会得到一个错误。

我们定义一个函数,读取变量(x)的foo属性,并将结果返回。

function getFoo(x) {
  return x.foo;
}

如果,我们将getFoo()应用于各种不同的值,我们发现只有undefinednull会报错。

getFoo(undefined) 
//TypeError: Cannot read property 'foo' of undefined

getFoo(null)
//TypeError: Cannot read property 'foo' of null


getFoo(true) // undefined

getFoo({}) // undefined

如果,继续深究的话,其实,这涉及到JS类型转换,而undefinednull不存在包装函数。前面的文章中,也有对变量类型的转换做了分析。

5. undefinednull的历史

在Java(它启发了JavaScript的许多方面)中,初始化值取决于变量的静态类型。

  • 具有对象类型的变量初始化为null。
  • 每个基本类型都有自己的初始值。例如,int变量用0初始化

在JavaScript中,每个变量都可以保存对象值和原始值。

❝每个变量只不过是一个用于保存任意值的命名占位符 ❞

因此,如果null表示不是对象,那么JavaScript还需要一个初始化值,这个初始化值既不是对象,也不是原始值。该初始化值就是undefined