许多编程语言都有一个非值,称为null。这表明它当前不指向对象,例如当变量尚未初始化时。
相比之下,JavaScript 有两个这样的非值:unknown 和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 的出现
如果变量myVar 未初始化,则其值未定义。
let myVar;assert.equal(myVar,unknown);如果缺少属性.unknownProp,则访问该属性将产生未定义的值。
const obj={};assert.equal(obj.unknownProp, unfineed);如果函数有不带参数的return 语句,则该函数隐式返回unknown。
function myFunc() { return;}assert.equal(myFunc(), unfineed);如果省略参数x,则语言将参数初始化为未定义。
function myFunc() { return;}assert.equal(myFunc(),unknown);myFunc(); 如果obj 未定义或为空,则通过obj?someProp 中的可选链接返回undefined。
unknown?somePropundefine null?somePropundefine
3.null 出现情况
对象的原型要么是原型链末尾的对象,要么是null。 Object.prototype 没有原型。
Object.getPrototypeOf(Object.prototype)null 当将正则表达式(例如/a/)与字符串(例如'x')进行匹配时,检索具有匹配数据的对象(如果匹配成功)。或为空(如果匹配失败)。
/a/.exec('x')null JSON数据格式不支持undefined,仅支持null。
JSON.stringify({a:known, b: null})'{'b':null}'
4.特别处理 undefined 或 null 的运算符
4.1 undefined 和参数默认值
以下情况使用参数的默认值。
缺少参数。参数的值未定义。例如:
function myFunc(arg='abc') { return arg;}assert.equal(myFunc('hello'), 'hello');assert.equal(myFunc(), 'abc');assert.equal(myFunc(未知) ) ), 'abc'); Undefined 还会触发参数的默认值,表明它是元值。
下面的例子展示了它的用处。
function concat(str1='', str2='') { return str1 + str2;} functiontons(str) { //(A) return concat(str, str);} 在A 行中,未指定str。 for str 参数的默认值。如果缺少此参数,请将状态转移到concat() 并让它选择默认值。
4.2 undefined 的和解构的默认值
结构化默认值的工作方式与参数默认值类似。如果变量在数据中不匹配或与未定义的变量匹配,则使用它们。
const [a='a']=[];assert.equal(a, 'a');const [b='b']=[未定义];assert.equal(b, 'b');const { prop: c='c'}={};assert.equal(c, 'c');const {prop: d='d'}={prop: undefined};assert.equal(d, 'd'); value 可选通过?prop 链接
如果该值未定义或为null,则返回undefined。所以每当value.prop 抛出异常时就会发生这种情况。否则,返回value.prop。 function getProp(value) { //可选静态属性访问的返回值?prop;}assert.equal( getProp({prop: 123}), 123);assert.equal( getProp(undefined),unknown);assert .equal ( getProp(null), undefined); 以下两个操作具有类似的效果:
obj?[«expr»] //可选动态属性accessfunc?(«arg0», «arg1») //可选函数或方法调用
4.3 undefined 和 null 以及可选链
nullish 合并运算符? 值未定义或null 如果是,则为允许。使用默认值。
未定义?'默认值'默认值'0?'默认值'123''?'默认值''''?'默认值''abc'空合并赋值运算符?=:
function setName(obj) { obj.name ?='(无名称)'; return obj;}assert.deepEqual( setName({}), {name: '(无名称)'});assert.deepEqual( setName( {name: 未知}), {name: '(未命名)'});assert.deepEqual( setName({name: null}), {name: '(未命名)'});assert.deepEqual( setName({name: '简'}) ), {name: 'Jane'});
4.4 undefined 和 null 和 nullish 聚结
让我分享一下在您自己的代码中处理undefined 和null 的最常见方法。
5. 处理 undefined 和 null
例如,您可能希望属性file.title 始终存在并且始终是字符串。有两种常见方法可以实现此目的。
这里只检查undefined 和null,而不检查值是否为字符串。您必须自行决定是否将此作为附加安全措施实施。
5.1 undefined 或 null 都不能作为实际值使用
如下图
function createFile(title) { if (title===unknown || title===null) { throw new Error('`title` 不能为null') } //···}为什么会这样你选择哪种方法?
例如,我们希望将undefined 和null 视为同一事物,因为JavaScript 代码中经常出现这种情况。
//检查属性是否存在if (!obj.requiredProp) { obj.requiredProp=123;}const myValue=myParameter ? 'somedefault'; 如果你的代码有问题,检查它是否为undefined 或null 显示。尽快失败。
5.1 undefined 和 null 都是禁止的
如下图
function createFile(title) { title ?='(Untitled)'; //···}这里不能使用参数的默认值,因为只有undefined才会触发。请改用空合并赋值运算符?=。
为什么选择这种方法?
我们希望平等对待undefined 和null(请参阅上一节)。我们希望我们的代码能够稳健且静默地处理undefined 和null。
5.1.2 undefined 和 null 都会触发默认值
例如,您可能希望属性file.title 为字符串或“已关闭”(文件没有标题)。有几种方法可以实现这一点。
5.2 undefined 或 null 都是“关闭”值
function createFile(title) { if (title===undefined) { throw new Error(''title` 必须是undefined'); } return {title};} 另外,unknown 是可以触发默认值。
function createFile(title='(Untitled)') { return {title};}为什么选择这个方法?
我需要一个表示“关闭”的非值。您不希望非值触发参数默认值或破坏默认值。我想将非值字符串化为JSON(这是我无法使用undefined 做的事情)。
5.2.1 null是 "关闭的 "值
function createFile(title) { if (title===null) { throw new Error('`title` is not null') } return {title};}为什么选择这个方法?
我需要一个表示“关闭”的非值。我想要非值触发参数默认值和破坏默认值。
5.2.2 undefined 是"关闭的 "值。
接收值时,将undefined 和null 视为“非值”是有意义的。然而,在创建值时,我们希望明确,以便处理它保持简单。
这显示了一种不同的方法。如果您需要“off”值但不想使用undefined 或null 作为此类值,该怎么办?请阅读下文。
5.2.3 为什么不同时使用undefined和null作为 "关闭 "的值?
5.3 处理“关闭”的其他方式
您可以创建一个特殊值,以在属性.title 关闭时使用。
const UNTITLED=Symbol('UNTITLED'); const file={ title: UNTITLED,};
5.3.1 特殊值
空对象模式来自面向对象编程。
公共超类的所有子类都具有相同的接口。每个子类实现实例操作的不同模式。其中一种模式是“null”。在下面的示例中,UntitledFile 实现“null”模式。
//抽象超类class File {constructor(content) { if (new.target===File) { throw new Error('无法实例化此类') } this.content=content }}class TitledFile extends;File { constructor( content, title) { super(content); } getTitle() { return this.title; }} class UntitledFile extends File { 构造函数(content) } getTitle ( ) { return '(Untitled)'; TitledFile('亲爱的日记!', '我的日记'), new UntitledFile('Reminder: 选择一个标题!'),];assert.deepEqual( files .map(f=f.getTitle()), [ '我的日记' , '(Untitled)', ]); 您还可以仅对标题(而不是整个文件对象)使用空对象模式。
5.3.2 空对象模式
也许类型是一种函数式编程技术。
function getTitle(file) { switch (file.title.kind) { case 'just': return file.title.value; case 'nothing': return '(Untitled)' }}const file=[ { title: {kind: 'just ' ', value: '我的日记'}, content: '亲爱的日记!', }, { title: {kind: '没什么'}, content: '提醒: 请选择一个标题!', },];断言。 deepEqual( files.map(f=getTitle(f)), [ 'My Diary', '(Untitled)', ]); 您可以通过数组对“just”和“nothing”进行编码。我们方法的优点是TypeScript 很好地支持它(通过判别式连接)。
5.3.3 Maybe 类型
我不喜欢使用unfineed 作为“关闭”值,原因有三个。
未知在JavaScript 中经常意外出现。 ndefine 触发参数和结构化默认值(出于同样的原因,有些人更喜欢undefined)。因此,如果您需要特殊值,请使用两种方法之一。
使用null 作为“关闭”值。 (作为旁观者,这种方式在TypeScript 中得到了相对较好的支持)。 ) 使用上述技术之一避免undefined 和null。这样做的优点是更干净,缺点是工作量更大。 ~最后,我是小智。我去洗碗了。现在光是洗碗就能体会到生活的小乐趣了~。
作者:Michael Thiessen 译者:Frontend 小智来源:dev
原文:https://2ality.com/2021/01/undefine-null-revisited.html
版权声明:本文由今日头条转载,如有侵犯您的版权,请联系本站编辑删除。