Что, если мы перестанем использовать null?
В настоящее время API-интерфейсы DOM и браузера, похоже, используют null
в некоторых случаях и undefined
в некоторых других. Хотя у них были свои исторические причины, результатом этого несоответствия является то, что нам приходится полагаться на приведение типов чаще, чем следовало бы, что создает целый ряд проблем.
Однако я хотел бы обсудить, что мы получим, если перестанем использовать null
и будем придерживаться undefined
в пользовательском коде.
1. Семантика - меньше о чем думать
Неужели такая большая разница в семантике? Для меня и null
, и undefined
означают примерно одно и то же - отсутствие значения. Рассмотрим, например, этот код:
const map = new Map(); map.get('abc'); // undefined localStorage.get('abc'); // null
В одном случае мы получаем undefined
, в другом случае мы получаем null
, но какая разница на самом деле для потребительского кода?
2. Global 'undefined' is safe now
Ранее в ECMA с 1 по 5.1 в спецификации не было глобального свойства undefined
, были только Неопределенный тип и значение. Итак, чтобы безопасно использовать undefined
, вам нужно было использовать переменную со значением undefined
в локальной области, иначе последствия могли быть непредсказуемыми:
window.undefined = '¯\_(ツ)_/¯'; var a; a === undefined; // false
Начиная с ECMA 6.0 / 2015 глобальный undefined
доступен только для чтения, поэтому его можно безопасно использовать.
3. Явное намерение
Если вам действительно нужно проверить, определено ли свойство объекта, существует оператор in
, который по какой-то причине используется редко, но на самом деле существует именно для этой цели, чтобы вы могли отличить отсутствующее свойство от свойства с неопределенным значением. .
const obj = {a: undefined}; 'a' in obj; // true 'b' in obj; // false
Обратите внимание, что оператор in
будет искать в цепочке прототипов, если собственное свойство не определено. Если вы хотите проверить только собственное свойство, используйте метод hasOwnProperty
.
4. Оператор typeof работает интуитивно понятно.
Не секрет, что typeof null
в JavaScript возвращает «объект» в лучшую или худшую сторону. Какие бы доводы ни стояли за этим в первые дни существования языка, с точки зрения потребителя, это не имеет особого смысла, поскольку null - это «пустое» значение, и обычно вы хотите отличить его от других типов значений. На самом деле, чтобы правильно проверить тип, мы проверяем и typeof
, и !== null
.
В случае undefined
, typeof undefined
возвращает «undefined», что дает нам хороший последовательный и интуитивно понятный тип, когда мы проверяем пустоту.
5. А как насчет 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()
.
6. Параметры функции по умолчанию
Начиная с ECMAScript 2015, в язык были добавлены параметры функций по умолчанию. Если вы не передадите параметр или передадите undefined
- будет использовано значение по умолчанию.
const f = (a = 1) => a; f(); // 1 f(undefined); // 1 f(null); // null
Здесь мы должны заметить, что язык действительно хочет, чтобы мы использовали undefined
для пустых значений, а не null
.
7. Деструктуризация со значениями по умолчанию.
Значения по умолчанию в синтаксисе деструктурирования присваивания, также представленном в 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
Значения по умолчанию теперь могут быть еще более полезными, поскольку их можно использовать не только в параметрах функции, но также в массивах, объектах и даже вложенных объектах и массивах.
8. Печатные языки и аннотации.
С появлением типизированных аннотаций для JavaScript нам также необходимо сделать так, чтобы система типов знала о типах null
и undefined
.
Например, в Flow есть специальная нотация для уже существующих значений, которая называется Maybe Types.
// @flow
const acceptsMaybeNumber = (value: ?number) => {
// ...
}
acceptsMaybeNumber(42); // Works!
acceptsMaybeNumber(); // Works!
acceptsMaybeNumber(undefined); // Works!
acceptsMaybeNumber(null); // Works!
acceptsMaybeNumber("42"); // Error!
Теперь вам нужно написать код, который проверяет оба: значение не равно undefined
, а значение не равно null
.
В то же время существует необязательный синтаксис значения, который работает, когда значение undefined
или вообще не было передано значения, поэтому в вашем коде вам нужно только проверить undefined
.
// @flow
const acceptsMaybeNumber = (value?: number) => {
if (value !== undefined) {
return value * 2;
}
}
acceptsMaybeNumber(42); // Works!
acceptsMaybeNumber(); // Works!
acceptsMaybeNumber(undefined); // Works!
acceptsMaybeNumber(null); // Error!
acceptsMaybeNumber("42"); // Error!
Это становится довольно сложным, если мы хотим разрешить свойство, которое может иметь значение null
или undefined
или вообще не определяться.
type A = {p: ?number}; const a: A = {}; // Error!
Итак, чтобы поддерживать все 3 состояния, нам нужно описать необязательное свойство с помощью типа Maybe:
type A = {p?: ?number}; const a: A = {}; // Works!
Как видите, ваша кодовая база была бы намного чище, если бы вы просто придерживались undefined
, поскольку некоторые языковые функции не работают с null
, а их смешивание повсюду приводит к дополнительным проверкам.
Такое ощущение, что язык JavaScript хочет, чтобы мы использовали undefined
полностью.