Удаление подполя поля, которое не существует

Я просто заметил следующее странное поведение:

Удаление переменной, которая не определена

> delete a
true
> delete a[0]
ReferenceError: a is not defined
> delete a.something
ReferenceError: a is not defined
> delete a.something[0]
ReferenceError: a is not defined

Удаление подполя поля, которое не существует

> a = {}
{}
> delete a.foo
true
> delete a.bar.something
TypeError: Cannot convert null to object
> a.bar
undefined

У меня есть два вопроса:

  • Почему delete a работает, пока a не определен?
  • Почему удаление a.bar.something выдает ошибку Cannot convert null to object вместо Cannot read property 'something' of undefined (потому что a.bar есть undefined)?

Согласно документации Оператор delete удаляет свойство из объекта. Таким образом, ответ для первого вопроса будет заключаться в том, что a должен быть свойством объекта this?

При использовании delete a; в app, this появляется ошибка (и она должна делать) error: ‘a’ was not declared in this scope.

Ответ 1

Ответ разбит на два. Первое не описывает много, но отвечает на вопрос, в то время как последний переходит в подробные детали спецификации.

TL;DR

  • Первая строка работает, потому что в нестрогом режиме просто пытается удалить переменную.
  • Остальные примеры в первом разделе не работают, потому что a не определен
  • delete a.foo работает, потому что нет причин, по которым он не должен
  • delete a.bar.something бросает, потому что сначала пытается превратить a.bar в объект, прежде чем пытаться получить доступ к a.bar.something.

И теперь для чего-то совершенно другого

Во-первых, давайте поясним, что два раздела кода концептуально отличаются, поскольку первые говорят о переменной, которая не была объявлена.

Мы рассмотрим как delete задано довольно немного.

Начнем с более понятных частей:

> delete a[0]
ReferenceError: a is not defined
> delete a.something
ReferenceError: a is not defined
> delete a.something[0]
ReferenceError: a is not defined

Все эти строки пытаются что-то сделать с a, переменной, которая не была объявлена. Поэтому вы получаете ReferenceError. Пока все хорошо.

> delete a
true

Это относится к 3-му предложению оператора delete: a - это "неразрешенная ссылка", которая представляет собой причудливый способ сказать "он не был объявлен". Spec говорит, что просто возвращает true в этом случае.

> a = {}
{}
> delete a.foo
true

Этот интуитивный, как вы ожидаете (вероятно), но пусть погружается в него в любом случае. delete obj.property переходит в четвертое предложение:

Возвращает результат вызова внутреннего метода [[Delete]] на ToObject(GetBase(ref)), предоставляя GetReferencedName(ref) и IsStrictReference(ref) в качестве аргументов.

Приветствую, что много неинтересных вещей. Позвольте просто игнорировать все после части [[Delete]] и просто посмотрите как указано [[Delete]]. Если бы я написал его в js, это будет так:

function Delete (obj, prop) {
    var desc = Object.getOwnPropertyDescriptor(obj, prop);

    if (!desc) {
        return true;
    }

    if (desc.configurable) {
        desc.magicallyRemove(prop);
        return true;
    }

    throw new TypeError('trying to delete a non-configurable property, eh!?');
}

В этом примере a не имеет свойства с именем foo, поэтому ничего особенного не происходит.

Теперь это становится интересным:

> delete a.bar.something
TypeError: Cannot convert null to object

Это происходит из-за некоторых неинтересных вещей, которые мы игнорировали ранее:

Возвращает результат вызова внутреннего метода [[Delete]] на ToObject(GetBase(ref)) [...]

Я выделил часть, относящуюся к этому конкретному фрагменту. Прежде чем мы попробуем delete что-нибудь, spec сообщит нам позвонить ToObject(GetBase(ref)), где ref = a.bar.something. Тогда сделаем это!

  • GetBase (a.bar.something) === a.bar
  • ToObject (a.bar) === ToObject(undefined), который выдает TypeError

Это объясняет окончательное поведение.

Заключительное примечание. Сообщение об ошибке, которое вы указали, вводит в заблуждение, так как оно говорит, что оно пыталось преобразовать null в объект, чего не было, поскольку он пытался преобразовать undefined в один. Последние хром и firefox бросают более точный, но я думаю, что v8 только недавно исправил сообщение об ошибке, поэтому, возможно, обновление до node v11 покажет правильную версию.

Ответ 2

Я делюсь экспериментами и чтениями, надеюсь, что это поможет!

1. Зачем удалять произведения, пока они не определены?

Если вы попытаетесь прочитать свойство, которое там отсутствует, возвращается JavaScript "undefined". Это удобно, но может маскировать ошибки, если вы не осторожно, поэтому следите за опечатками!

Источник: http://www.w3.org/wiki/Objects_in_JavaScript

"Удалить" удалит как значение, так и свойство, поэтому, пока JavaScript вернет значение для a, удаление будет работать так же, как в этом примере:

var a;
delete a;

Кроме того, как вы сказали, a является свойством объекта this. Вы можете увидеть это, протестировав этот код:

> var a = {}
undefined
> a
{}
> this.a
{}

2. Почему удаление a.bar.something вызывает ошибку Невозможно преобразовать null в объект вместо Can not read property "something" из undefined?

Прочтите эти примеры:

NodeJS:

> var a = {}
undefined
> typeof a.b
'undefined'
> typeof a.b.c
TypeError: Cannot read property 'c' of undefined
    at repl:1:12
    at ......
> delete a.b.c
TypeError: Cannot convert null to object
    at repl:1:10
    at ......

Консоль Chrome:

> var a ={}
undefined
> typeof a.b
"undefined"
> typeof a.b.c
Uncaught TypeError: Cannot read property 'c' of undefined VM335:2
> delete a.b.c
Uncaught TypeError: Cannot convert undefined or null to object

Как вы можете видеть, оба будут управлять a.b как значение undefined при тестировании typeof. Но при удалении хром говорит, что это может быть undefined или null, тогда как NodeJS считает это значением null.

Возможно ли, что ошибка NodeJS ошибочна?