likes
comments
collection
share

如果我们停止使用 null 会怎样?(译)

作者站长头像
站长
· 阅读数 2

初见

目的:一边学技术一边学英文。😋

小编翻译过程可能不会按原文一句一字翻译,稍微会插入一些自己的理解与补充,但是原文本意不会改变,如有错误之处,欢迎指出,也望海涵。

原文链接 👈👈👈

遵纪守法,科学上网。🪜🪜🪜

相识

目前,DOM 和浏览器 API 在某些情况下有时使用 null ,有时使用 undefined 。虽然它们有其历史原因,但这种不一致的结果导致我们不得不更多地依赖类型转换,但这会带来一系列的问题。

这句话可能读着有点懵😵,我们来用实际例子说明:

例如:当我们使用 document.getElementById 方法但没有找到对应元素时,它会返回 null ;而当我们声明一个变量但没有赋值,那么这个变量的默认值就是 undefined

就是这种不一致性可能导致开发者在比较值时必须更频繁地依赖类型转换,这可能会引入错误和混淆。

不过,这次我想讨论的是,如果"我们在用户代码中停止使用 null 并坚持只使用 undefined ,我们会得到什么呢?"

统一语义需要考虑的事情更少

语义上真的有那么大的区别吗?对我来说,nullundefined 的含义几乎是相同 - 缺少值。

语义上,我们经常用 null 表示一个对象为空或者对象属性没有值并且没有引用任何对象,而用 undefined 表示一个变量被声明但没有初识化,也就是值为空的情况。

const map = new Map();
map.get('abc'); // undefined

localStorage.get('abc'); // null

在同一种缺值的情况下,我们得到却是两个不一样的结果,有时是 undefined 有时是 null ,这对开发者的代码真的有区别吗?

现在全局undefined是安全的

在 ECMA 规范 1 到 5.1 之前,规范中是没有定义 undefined 作为一个全局属性的,只有 undefined 的类型和值。因此,为了更安全地使用 undefined ,你需要在全局范围内对其进行一些保护措施,否则,一旦 undefined 被不小心或者某些其他原因篡改,这个后果是不可预测的。

window.undefined = '¯\_(ツ)_/¯'; // 篡改undefined
var a;
a === undefined; // ECMA1~5.1之前是:false;后面版本的是true

从 ECMA 规范 6.0 / 2015开始,全局 undefined 是只读的,不会被篡改,因此可以安全使用。

意图明确

如果你需要检查一个对象的属性是否存在,可以使用 in 运算符,虽然它很少被使用到,但 in 运算符的设计目的就是为此而存在的,以便你可以轻易区分对象属性是否已经定义或者是已经定义但还未定义其值。

const obj = {a: undefined};  
'a' in obj; // true  
'b' in obj; // false

请注意,如果对象未定义自己的属性,in 运算符将往原型链上查找。如果你希望只检查对象本身的属性,可以使用 hasOwnProperty

这段话看着可能难以理解作者想表达的意图,小编反复阅读原文的语句,个人理解总结如下:

当你想检查对象属性是否存在时,可以直接使用 in 运算符或者 hasOwnProperty 方法,这没毛病吧,很直观。

而当你想知道对象属性的值是否为"空"的时候,如果我们只使用 undefined 的话,那我们直接判断一下就行了,但是此时如果再引入 null 来表达空的情况,事情就变得稍微麻烦一点了。

业务数据的统计要是严格一点,我们就会陷入是否要区分两者情况的境地。如果再有引入"0"的情况就更麻烦了,这些情况小编曾经遇到过。😐

如果我们停止使用 null 会怎样?(译)

typeof

众所周知,Javascript 中的 typeof null 会返回一个对象类型(object)。无论该语言早期背后的设计目标是什么,从开发者角度来看,它都没有多大意义。因为 null 是一个"空"值,通常你希望将其与其他值类型区分开来。而实际上,我们为了进行正确的类型检查,最终都会检查 typeof!==null

undefined 情况下,typeof undefined 返回 undefined 类型,这在我们检查空值类型时,给我们提供了一个很好的、一致的且直观的类型。

其实说的就是 typeof null 没有 typeof undefined 好用,前者 typeof 后你还是很难确定它是谁,后者直接就能确定了。😗

JSON.stringify

当我们序列化数据时,JSON.stringify() 会删除所有未定义值的属性,这很好,因为我们在进行网络请求或其他地方的时候就不需要发送它们了。

JSON.stringify({b: undefined, c: null}); // {"c": null}

但是,如果你想保持未定义值的属性不被删除,这很容易做到:

JSON.stringify(  
  {b: undefined, c: null},  
  (key, value) => value === undefined ? 'undefined' : value  
); // {b: "undefined", "c": null}

其实就是转成字符串。

对于 JSON.parse() 也是如此。

函数-默认值

自 ECMAScript 2015 起(其实就是ES6),Javascript 语言中添加了函数默认值参数,如果你不传递参数或者传递 undefined ,默认值都将生效。

const f = (a = 1) => a;  
f(); // 1  
f(undefined); // 1  
f(null); // null

此时,我们应该注意到 Javascript 语言实际上希望我们使用 undefined 来表示空值,而不是 null 。

解构赋值-默认值

ES2015 中也引入了解构赋值-默认值,其工作方式与函数默认值相同。

const {a = 1} = {a: null};
a; // null
const {a = 1} = {};
a; // 1
const {a = 1} = {a: undefined};
a; // 1
const [a = 1] = [];
a; // 1
const [a = 1] = [undefined];
a; // 1
const {a: {b = 1}} = {a: {b: undefined}};
b; // 1

默认值现在变得更加有用,因为它们不仅可以在函数参数中使用,还可以在数组、对象甚至嵌套对象和数组中使用。

类型语言和注释

由于 JavaScript 类型注释的兴起,我们还需要让类型系统单独识别 nullundefined 类型。

类型系统指 TypescriptFlowJSDoc 等等。如果没有 null 类型,这些类型系统也能省点事了。😗

例如,在 Flow 中,对于最终存在的值有一个特殊的表示法,称为 Maybe Types

// @flow  
const acceptsMaybeNumber = (value: ?number) => {  
  // ...
  /* 下面一句话的解释表示我们要多加这些长长的判断
  if (value !== undefined && value !== null) {  
    return value * 2;  
  }
  */
}  
  
acceptsMaybeNumber(42);              // 正常!  
acceptsMaybeNumber();                // 正常!  
acceptsMaybeNumber(undefined);       // 正常!  
acceptsMaybeNumber(null);            // 正常!  
acceptsMaybeNumber("42");            // 错误!

你现在必须编写代码来检查值是否为 undefined 以及值是否为 null 。

Flow 中,?number 等同于 number | null | void 联合类型,也就是允许数字、nullundefined 作为值,这就是 Maybe Types

同时,还有一个可选值语法(?:)的设计是围绕 undefined 来工作的,在代码中,你只需检查是否为 undefined 即可决定如何处理。

// @flow  
const acceptsMaybeNumber = (value?: number) => {  
  if (value !== undefined) {  
    return value * 2;  
  }  
}  
  
acceptsMaybeNumber(42);           // 正常!  
acceptsMaybeNumber();             // 正常!  
acceptsMaybeNumber(undefined);    // 正常!  
acceptsMaybeNumber(null);         // 错误!  
acceptsMaybeNumber("42");         // 错误!

value?: number 表示仅支持 number | undefined 的联合类型,null 并没有被规划在其中。

如果我们想要定义一个对象属性,它仅支持 numbernullundefined 类型,或者这个对象属性可能不存在,这在 Flow 中要如何定义呢?

type A = {p: ?number};  
const a: A = {}; // 错误! 属性未定义也不行

为了支持这种情况,我们需要使用 Maybe 来描述可选属性:

type A = {p?: ?number};  
const a: A = {}; // 正常! 允许属性未定义

其实这一小段讲来讲去就是说 null 带了更多的麻烦,但规划"空值"的设计还是要偏向使用 undefined,用 Flow 与可选属性示例来论述这个结果。😐

告别

正如你所看到的,如果你坚持使用 undefined ,你的代码将会更加干净、直观,因为某些语言功能确实不适用 null ,并且混合使用这两者会导致到处进行额外的检查。

其实,感觉 Javascript 语言是希望我们一直使用 undefined 就好,而 null 是一个历史遗留问题,不希望也不应该被继续扩大滥用下去。


翻译辅助工具:谷歌翻译助手百度翻译

🙌如有翻译偏差之处,欢迎评论区指出批评,拜谢🙌

God bless and happy coding!🍊