乐趣区

关于javascript:JavaScript-Weekly-523Null-VS-Undefinded

🥳 欢送有趣味的小伙伴,一起做点有意义的事!本文译者:chressYu

我发动了一个 周刊翻译打算,仓库地址,拜访地址

当初还很缺气味相投的小伙伴,纯属个人兴趣,当然对于晋升英语和前端技能也会有帮忙,要求:英语不要差的离谱、github 纯熟应用、有恒心、虚心、对本人做的事负责。

想参加的小伙伴,能够 wx 私信,也能够给仓库发 issue 留言,我博客也有具体的集体联系方式:daodaolee.cn

少数编程语言都有一个“空值”的定义,为 null。它示意变量以后不指向对象——例如,当它尚未初始化时。

与其余语言相比,JavaScript 蕴含两个这样的空值:undefined 和 null。在这篇文章中,咱们将会探讨他们之间的区别,以及如何去最好的应用或者防止应用他们。

undefined vs. null

这两个值是十分类似的,并且能够调换应用的。所以他们的区别是很奥妙的。

ECMAScript 上对 undefined 和 null 的定义:

ECMAScript 对 undefined 和 null 的定义如下:

  • undefinde:当一个变量并没有被赋予一个值。
  • null:并没有示意任何对象;
    咱们将在前面看到作为程序员如何最好的解决这两个值。

两个空值 – 一个无奈删除的谬误

Javascript 有两个空值到当初都被认为是一个谬误的设计 (即使是 JavaScript 的创建者,Brendan Eich)。
那么为什么不从其中删除一个呢?一个 JavaScript 的外围准则就是永远不要突破向下的兼容。这个准则有很多益处。但它最大的毛病就是无奈去除谬误的设计。

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() {}
assert.equal(myFunc(), undefined);

如果一个函数的 return 并没有返回任何值,那么他会返回 undefined:

function myFunc() {return;}
assert.equal(myFunc(), undefined);

如果一个函数的形参 x 在调用时被省略,那么它会是 undefined:

function myFunc(x) {assert.equal(x, undefined);
}
myFunc();

如果一个变量是 undefined 或者 null 时,当应用链式调用 ojb?someProp 时会返回 undefined:

undefined?.someProp
// undefined
null?.someProp
// undefined

null 是如何产生的

一个对象的原型要么是一个对象,要么在原型链的开端是 null。Prototype 没有原型:

Object.getPrototypeOf(Object.prototype)
// null

如果咱们通过一个正则表达式 /a/,用这个正则去匹配字符串 ’x’,如果匹配失败了,则会返回一个 null:

/a/.exec('x')
// null

在调用 JSON.stringify 时,JSON 是只反对 null 的,不反对 undefined

JSON.stringify({a: undefined, b: null})
// '{"b":null}'

如何解决 undefined 和 null

undefined 和参数的默认值
参数默认值用于:

  1. 当参数短少时
  2. 入参未定义时

例如:

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 指定参数默认值。当短少这个参数时,twice 函数会应用默认值。

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');

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?.[«expr»] // optional dynamic property access
func?.(«arg0», «arg1») // optional function or method call

undefined、null 以及空值合并符

空值合并运算符 ?? 是让咱们在遇到 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'

逻辑空赋值 ??= 将空值合并符和赋值合并在一起:

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'}
);

如何解决 null 和 undefined

上面的大节解释了在咱们本人的代码中解决未定义和 null 的最常见办法。

undefined 和 null 都不做为理论值

例如,咱们可能心愿属性 file.title 始终存在并始终为字符串。有两种常见的办法能够达到这个目标。

请留神,在这篇博文中,咱们只思考 undefined 和 null,而不关注值是否是字符串。您必须本人决定是否将其实现为附加的安全措施。

undefined 和 null 都被禁止应用

例如:

function createFile(title) {if (title === undefined || title === null) {throw new Error('`title` must not be nullish');
  }
  // ···
}

为什么抉择这种办法?

  • 咱们心愿共事解决 undefined 和 null,因而咱们常常会这么写,例如:

    // Detecting if a property exists
    if (!obj.requiredProp) {obj.requiredProp = 123;}
    
    // Default values via nullish coalescing operator
    const myValue = myParameter ?? 'some default';
  • 如果咱们的代码因为 undefined 和 null 导致一些问题,咱们心愿他尽快抛出。

遇到 undefined 和 null 时,触发默认值。

例如:

function createFile(title) {title ??= '(Untitled)';
  // ···
}

咱们不能在这里应用函数的默认值,因为它只能被 undefined 触发,因而咱们在这里应用了空值合并运算符 ??=

为什么抉择这种办法?

  • 咱们心愿对 undefined 和 null 的解决是统一的。
  • 咱们心愿代码能精确地解决 undefined 和 null

undefined 或 null 示意空值

例如,咱们可能心愿属性 file.title 为字符串或空值(file 没有 title 属性)。有几种办法能够达到这个目标。

这里用 null 示意空值

例如:

function createFile(title) {if (title === undefined) {throw new Error('`title` must not be undefined');
  }
  return {title};
}

或者 undefined 触发一个默认值:

function createFile(title = '(Untitled)') {return {title};
}

为什么抉择这种形式:

  • 咱们须要一个空值来示意没有
  • 咱们不心愿 null 触发参数默认值和解构默认值。
  • 咱们心愿将空值字符串化为 JSON (所以不能应用 undefined)。

undefined 来表白一个空值

例如:

function createFile(title) {if (title === null) {throw new Error('`title` must not be null');
  }
  return {title};
}

为什么抉择这种形式:

  • 咱们须要一个空值来示意没有
  • 咱们的确心愿空值触发参数默认值和解构默认值。

undefined 的一个毛病是,它通常是由 JavaScript 无心中创立的: 由一个未初始化的变量,属性名中的一个输出谬误,遗记从函数返回一些货色,等等。

为什么不能同时应用 undefined 和 null

当接管到一个值时,将 undefined 和 null 都视为“空值”是有意义的。因为,当咱们创立一个值时,咱们心愿是明确的,以便能够不便的解决这个值。

当咱们不想应用 undefined 或者 null 来示意空值时,该如何解决呢?请往下看!

其余形式示意一个空值

非凡的一个值

咱们能够创立一个非凡的值,来示意 .title 是空的:

const UNTITLED = Symbol('UNTITLED');
const file = {title: UNTITLED,};

 空对象模式

空对象模式源自面向对象编程:

  • 公共类都继承雷同的接口。
  • 每个子类实现一个实例在其中操作的不同模式。
  • 这些模式之一是“空”。

在上面的示例中,UntitledFile 实现了“null”模式。

// Abstract superclass
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)',
  ]);

咱们也能够只应用空对象模式 File 来解决 title 属性(而不是解决整个文件对象)。

有可能的类型

写出所有有可能的类型,来解决问题:

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)',
  ]);

咱们能够通过数组对“just”和“nothing”进行编码。咱们办法的益处是,它失去了 TypeSript 的良好反对。

相干链接

原文地址:undefined vs. null revisited

翻译打算原文

退出移动版