❝
成为高手需要在没有感觉的情况下,蹚过漫长的无聊和低成就感时期, 蹚不过就一直是二流水平。
❞
简明扼要
- 在JS中,存在两个空值
undefinednull
Object.prototype不存在原型对象且值为null- 假值:通过Boolean(X)强制类型转换后的值为
falseundefinednull- Boolean:
false - Numbers:
0,NaN - Bigint:
0n - String:
''
文章概要
undefinedvsnull- 如何产生
undefined和null - Null 判断运算符(
??)的默认值 [es2020] undefined和null没有任何属性undefined和null的历史
许多编程语言都有一个空值(non-value)null:表示存在一个变量但是没有指向一个对象。
但是,在JS中,存在两个空值\
undefined\null
1. undefined vs null
一般情况下,这两个值在使用上都可以互换使用。只有在一些细微的方面存在差别。
undefined意味着:「未初始化」(例如:定义一个变量但是未初始化)或者**「不存在」**(例如:访问一个在对象中不存在的属性)null意味着:故意将某个对象置为空 (可以参考tc39对Null的解释)
我们可以从使用上对其进行分类
undefined: 是语言层面上使用的非值(定义一个变量,但未赋值,此时该变量会被JS引擎自动赋为undefined)null: 蓄意控制变量的值
2. 如何产生undefined和null
下面我们将从语言层面讨论undefined和null是如何产生。
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为数组;而未匹配成功时,matchResult为null。
我们也可以利用**「链判断运算符」**(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\
undefined\null\- Boolean:
false\- Numbers:
0,NaN\- Bigint:
0n\- String:
''❞
false || 'default' //'default'
0 || 'default' //'default'
0n || 'default' //'default'
'' || 'default' //'default'
逻辑赋值运算符(??=) [es2021]
下面的代码是等价的。
a ??= b
a ?? (a = b)
??=会发生**「截断现象」**:只有变量a的值为undefined或null才会发生赋值操作。
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. undefined 和 null 没有任何属性
undefined和null是JS中仅有的两个变量:当试图读取它们的属性,会得到一个错误。
我们定义一个函数,读取变量(x)的foo属性,并将结果返回。
function getFoo(x) {
return x.foo;
}
如果,我们将getFoo()应用于各种不同的值,我们发现只有undefined和null会报错。
getFoo(undefined)
//TypeError: Cannot read property 'foo' of undefined
getFoo(null)
//TypeError: Cannot read property 'foo' of null
getFoo(true) // undefined
getFoo({}) // undefined
如果,继续深究的话,其实,这涉及到JS类型转换,而undefined和null不存在包装函数。前面的文章中,也有对变量类型的转换做了分析。
5. undefined 和 null的历史
在Java(它启发了JavaScript的许多方面)中,初始化值取决于变量的静态类型。
- 具有对象类型的变量初始化为null。
- 每个基本类型都有自己的初始值。例如,int变量用0初始化
在JavaScript中,每个变量都可以保存对象值和原始值。
❝
每个变量只不过是一个用于保存任意值的命名占位符
❞
因此,如果null表示不是对象,那么JavaScript还需要一个初始化值,这个初始化值既不是对象,也不是原始值。该初始化值就是undefined。