5年前学习null和undefined,现在有新的认知,看看这位人才怎么说
zhezhongyun 2025-05-11 03:03 7 浏览
许多编程语言都有一个称为null的非值。它指示一个变量当前不指向一个对象,例如,当它还没有初始化的时候。
相比之下,JavaScript有两个这样的非值:undefined和null。在这篇博文中,我们将研究它们有什么不同,以及如何最好地使用或避免它们。
1. undefined vs. null
这两种值非常相似,经常互换使用。因此,它们的区别是微妙的。
1.1 ECMAScript语言规范:undefined vs. null
ECMAScript语言规范对其描述如下。
- undefined是 "当一个变量没有被赋值时使用"
- null "表示有意不存在任何对象值"
1.2 两个非值--一个无法消除的错误
在JavaScript中拥有两个非值现在被认为是一个设计错误(甚至被JavaScript的创造者Brendan Eich认为)。
那为什么不从 JavaScript 中删除其中的一个值呢?JavaScript 的一个核心原则是绝不破坏向后兼容。这个原则有很多好处。它最大的缺点是,设计上的错误无法被删除。
1.3 undefined和null的历史
在Java中(它启发了JavaScript的许多方面),初始化值取决于变量的静态类型。
- 具有对象类型的变量被初始化为null。
- 每个原始类型都有自己的初始化值。例如,int变量被初始化为0。
在JavaScript中,每个变量都可以容纳对象值和基本类型值。因此,如果null意味着 "不是一个对象",那么JavaScript也需要一个初始化值,意味着 "既不是一个对象也不是一个原始值"。这个初始化值就是 undefined。
undefined 的出现
如果一个变量myVar还没有被初始化,它的值是undefined。
let myVar;
assert.equal(myVar, undefined);
如果一个属性.unknownProp丢失,访问该属性会产生undefined的值。
const obj = {};
assert.equal(obj.unknownProp, undefined);
如果一个函数有一个没有参数的返回语句,该函数隐含地返回undefined。
function myFunc() {
return;
}
assert.equal(myFunc(), undefined);
如果一个参数x被省略了,语言将该参数初始化为undefined。
function myFunc() {
return;
}
assert.equal(myFunc(), undefined);
myFunc();
通过obj?.someProp的可选链路,如果obj未定义或为空,则返回未定义。
> undefined?.someProp
undefined
> null?.someProp
undefined
3.null 出现情况
一个对象的原型要么是一个对象,要么在原型链的末端是null。Object.prototype没有原型。
> Object.getPrototypeOf(Object.prototype)
null
如果我们将一个正则表达式(如/a/)与一个字符串(如'x')相匹配,我们要么得到一个带有匹配数据的对象(如果匹配成功),要么得到空(如果匹配失败)。
> /a/.exec('x')
null
JSON数据格式不支持undefined,只支持null。
> JSON.stringify({a: undefined, b: null})
'{"b":null}'
4.特别处理 undefined 或 null 的运算符
4.1 undefined 和参数默认值
在以下情况下,将使用参数默认值。
- 缺少一个参数。
- 一个参数的值是undefined。
例如:
function myFunc(arg='abc') {
return arg;
}
assert.equal(myFunc('hello'), 'hello');
assert.equal(myFunc(), 'abc');
assert.equal(myFunc(undefined), 'abc');
undefined也触发了参数的默认值,这说明它是一个元值。
下面的例子说明了这一点的用处。
function concat(str1='', str2='') {
return str1 + str2;
}
function twice(str) { // (A)
return concat(str, str);
}
在A行,我们没有为str指定一个参数默认值。当这个参数缺失时,我们把这个状态转发给concat(),让它选择一个默认值。
4.2 undefined 的和解构的默认值
解构中的缺省值与参数缺省值的工作原理类似--如果一个变量在数据中没有匹配,或者它与undefined的变量匹配,就会使用它们。
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');
4.3 undefined 和 null 以及可选链
当通过 value?.prop 进行可选的链接时
- 如果value是undefined或null,则返回 undefined。也就是说,只要value.prop会抛出一个异常,就会发生这种情况。
- 否则,返回value.prop。
function getProp(value) {
// optional static property access
return value?.prop;
}
assert.equal(
getProp({prop: 123}), 123);
assert.equal(
getProp(undefined), undefined);
assert.equal(
getProp(null), undefined);
下面两个操作的效果类似。
obj?.[<>] // optional dynamic property access
func?.(<>, <>) // optional function or method call
4.4 undefined 和 null 和 nullish 聚结
nullish 合并算子??如果一个值undefined或为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'
null合并赋值操作符??=:
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'}
);
5. 处理 undefined 和 null
下面分享一下我们自己的代码中处理undefined 和null的最常见方法。
5.1 undefined 或 null 都不能作为实际值使用
举例,我们可能希望一个属性file.title永远存在,并且永远是一个字符串。有两种常见的方法来实现这一点。
这里,只检查undefined和null,而不检查一个值是否是字符串。你必须自己决定是否要把它作为一个额外的安全措施来实施。
5.1 undefined 和 null 都是禁止的
如下所示
function createFile(title) {
if (title === undefined || title === null) {
throw new Error('`title` must not be nullish');
}
// ···
}
为什么选择这种方法?
我们想把undefined和null当作一样的东西,因为JavaScript代码经常这样做--比如说。
// 检测一个属性是否存在
if (!obj.requiredProp) {
obj.requiredProp = 123;
}
const myValue = myParameter ?? 'some default';
如果我们的代码中出现了问题,出现了undefined或null,我们希望它尽可能快地失败。
5.1.2 undefined 和 null 都会触发默认值
如下所示
function createFile(title) {
title ??= '(Untitled)';
// ···
}
我们不能在这里使用参数缺省值,因为它只由undefined 触发。相反,我们依靠nullish 合并赋值运算符??=。
为什么选择这种方法?
- 我们想把undefined和null同等对待(见上一节)。
- 我们希望我们的代码能够稳健地、无声地处理 undefined 和null。
5.2 undefined 或 null 都是“关闭”值
例如,我们可能希望一个属性file.title是一个字符串或 "关闭"(文件没有标题)。有几种方法可以实现这一点。
5.2.1 null是 "关闭的 "值
function createFile(title) {
if (title === undefined) {
throw new Error('`title` must not be undefined');
}
return {title};
}
另外,undefined可以触发一个默认值。
function createFile(title = '(Untitled)') {
return {title};
}
为什么选择这种方法?
- 我们需要一个表示“关闭”的非值。
- 我们不希望我们的非值触发参数默认值和解构默认值。
- 我们想把非值字符串化为JSON(这是我们不能用undefined做的)。
5.2.2 undefined 是"关闭的 "值。
function createFile(title) {
if (title === null) {
throw new Error('`title` must not be null');
}
return {title};
}
为什么选择这种方法?
- 我们需要一个表示“关闭”的非值。
- 我们确实想让我们的非值触发参数默认值和解构默认值。
5.2.3 为什么不同时使用undefined和null作为 "关闭 "的值?
当接收一个值时,把undefined和null都当作 "不是一个值 "是有意义的。然而,当我们创建值时,我们希望不含糊,以便处理这些值时保持简单。
这指向了一种不同的方法。如果我们需要一个 "关闭"的值,但又不想使用undefined或null作为这样的值,怎么办?请往下看。
5.3 处理“关闭”的其他方式
5.3.1 特殊值
我们可以创建一个特殊的值,每当属性.title被关闭时,我们就使用这个值。
const UNTITLED = Symbol('UNTITLED');
const file = {
title: UNTITLED,
};
5.3.2 空对象模式
空对象模式来自于面向对象的编程。
- 一个共同的超类的所有子类都有相同的接口。
- 每个子类都实现了一个实例运行的不同模式。
- 其中一个模式是 "null"。
在下面的例子中,UntitledFile实现了 "null" 模式。
// 抽象的超类
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)',
]);
我们也可以只对标题使用空对象模式(而不是对整个文件对象)。
5.3.3 Maybe 类型
Maybe 类型是一种函数编程技术。
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)',
]);
我们可以通过Arrays对 "just "和 "nothing "进行编码。我们的方法的好处是,它得到了TypeScript的良好支持(通过判别性联合)。
6.我的方法
我不喜欢用undefined作为 "关闭 "的值,有三个原因。
- undefined经常意外地出现在JavaScript中。
- ndefined会触发参数和结构化的默认值(出于同样的原因,有些人喜欢undefined)。
因此,如果我需要一个特殊的值,我会使用以下两种方法中的一种。
- 我使用null作为一个 "关闭 "的值。(作为一个旁观者,这种方法在TypeScript中得到了比较好的支持)。)
- 我通过上述的技术之一来避免undefined和null。这样做的好处是比较干净,坏处是涉及更多的工作。
~ 完,我是小智,刷碗去了,现在唯有刷碗才能体验到生活中的一点乐趣,逃 ~
作者:Michael Thiessen 译者:前端小智 来源:dev
原文:https://2ality.com/2021/01/undefined-null-revisited.html
相关推荐
- Axure教程
-
来人人都是产品经理【起点学院】,BAT实战派产品总监手把手系统带你学产品、学运营。前几天看到有人介绍了axure8的图片放大原型设计步骤(http://www.woshipm.com/rp/24687...
- 保姆级教程:手把手教你用 Readdy.ai 轻松产品设计与前端开发
-
本文将为你介绍一款强大的AI工具——Readdy.ai,它不仅能自动生成精美的UI设计稿,还能直接生成可用的前端代码,极大地提升了开发效率。前几天雷神为大家介绍了一款AI设计工具莫高设计,但是...
- 前端学习保姆级教程,轻松入门 Web 开发
-
在当今数字化时代,我们每天浏览的网页、使用的各类应用,其美观的界面、流畅的交互体验背后,都离不开前端开发技术的支撑。前端开发,简单来说,就是负责将网站或应用的界面呈现给用户,它直接影响着用户对产品的第...
- 分享几个css实用技巧
-
本篇将介绍几个css小技巧,目录如下:自定义引用标签的符号重置所有标签样式禁止文本选择制作小三角形自定义<q>引用标签的符号默认q标签引用符号是浏览器根据不同语言环境自动设置的,当然我们也...
- 五步做一个高保真可交互原型-Principle教程
-
这篇教程要介绍的案例是:App界面上调整时间的可交互原型。我们先看一下效果:这个案例用的是Principle这款软件做的。Principle近段时间非常流行的一款做原型的软件。很多公司也用Princi...
- asp网站源码安装教程
-
asp网站源码安装指南1、打开控制面板并单击2、点选管理工具选项3、请单击打开Internet信息服务(IIS)管理器应用查看4、点击开始按钮5、请将项目文件移动到C:inetpubwwwr...
- web前端HTML教程——开发环境搭建下载和安装编辑器
-
html开发环境搭建有一个好的编辑器我们可以方便地的开发项目,编写代码,配置和管理我们的项目。所以我们开始编写html代码之前需要搭建开发环境。基于html项目的开发和代码编写现在网上有很多编辑器,也...
- 如何用控制台打印日历?
-
朋友们大家好,今天我给大家带来一个控制台小程序——打印当月的日历,效果如下图:笔者运行这个程序的时候是2022年7月30日,所以在日期的30后面加了一个*符号。这个程序很简单,但有些知识点我也会为大家...
- dotnet 值拷贝、浅拷贝、深拷贝详解
-
简介在.NET中,值拷贝(ValueCopy)主要指的是将一个值类型的实例或对象的值复制到另一个变量中,使两个变量之间互不影响。我们可以从几个维度来详细理解:值拷贝的本质.NET中的类型分...
- 41 基于Sentinel的限流
-
Sentinel支持对SpringCloudGateway、Zuul等主流的APIGateway进行限流。从1.6.0版本开始,Sentinel提供了SpringCloud...
- Java反射机制:神秘之门的钥匙
-
Java反射机制:神秘之门的钥匙什么是Java反射?当你第一次听说Java反射的时候,你可能会感到困惑。简单来说,Java反射就是让你在程序运行时动态地操作类和对象的能力。这就像一把钥匙,能够打开Ja...
- Java反射机制:魔法般的代码操控
-
Java反射机制:魔法般的代码操控在这个充满无限可能的编程世界里,Java反射机制就像一位神秘的魔法师,它允许程序在运行时检查类、方法、字段等信息,甚至还能动态调用这些成员。这种能力让Java开发者能...
- 自己动手从0开始实现一个分布式 RPC 框架
-
前言为什么要自己写一个RPC框架,我觉得从个人成长上说,如果一个程序员能清楚的了解RPC框架所具备的要素,掌握RPC框架中涉及的服务注册发现、负载均衡、序列化协议、RPC通信协议、Socket通信、异...
- C# 实战指南:使用 iText7 高效处理 PDF 文件
-
1.引言在现代开发中,处理PDF文件是一个常见的需求,例如生成报告、填充表单、设置权限或签名等。iText7是一个功能强大的PDF操作库,支持多种PDF操作,包括表单填充、加密、数字签...
- 手写代码生成工具实现类似Mybatis-Plus的效果-----02
-
#全新智己L6全国试驾开启#packagecom.alatus.builder;importcom.alatus.Entity.FieldInfo;importcom.alatus.Ent...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- HTML 参考手册 (28)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML常用标签 (29)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- HTML button formtarget 属性 (30)