Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deleting a subfield of a field that doesn't exist

I just observed the following weird behavior:

Deleting a variable that was not defined

> 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

Deleting a subfield of a field that doesn't exist

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

I have two questions:

  • Why delete a works while a is not defined?
  • Why does deleting a.bar.something throw the error Cannot convert null to object instead of Cannot read property 'something' of undefined (because a.bar is undefined)?

According to documentation The delete operator removes a property from an object., so the answer for the first question would be that a is supposed to be a property of this object?

When using delete a; in c++ app, this error appears (and it's supposed to do) error: ‘a’ was not declared in this scope.

like image 425
Ionică Bizău Avatar asked Nov 14 '14 10:11

Ionică Bizău


1 Answers

The answer is split into two. The first doesn't describe much but answer the question, while the latter goes into the nitty gritty details of the specification.

tl;dr

  1. The first line works because in non-strict mode, trying to delete a variable just works.
  2. The rest of the examples in the first section don't work because a isn't defined
  3. delete a.foo works because there's no reason it shouldn't
  4. delete a.bar.something throws because it's first trying to turn a.bar into an object before trying to access a.bar.something.

And now for something completely different

First, let's make clear that the two sections of code are conceptually different since the first talks about a variable which was not declared.

We'll be looking at how delete is specified quite a bit.

Let's start with the easier to understand parts:

> 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

All these lines are trying to do something with a, a variable which was not declared. Therefore, you get a ReferenceError. So far so good.

> delete a
true

This goes into the 3rd clause of the delete statement: a is an "unresolved reference", which is a fancy way of saying "it wasn't declared". Spec says to simply return true in that case.

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

This one's intuitive as you expect (probably), but let's dive into it anyway. delete obj.property goes into the 4th clause:

Return the result of calling the [[Delete]] internal method on ToObject(GetBase(ref)) providing GetReferencedName(ref) and IsStrictReference(ref) as the arguments.

Welp that's a whole lot of uninteresting stuff. Let's just ignore everything after the [[Delete]] part, and just look at how [[Delete]] is specified. If I were to write it in js, it's be like this:

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

In the example, a does not have a property named foo, so nothing special happens.

Now it gets interesting:

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

This happens because of some of the uninteresting things we ignored earlier:

Return the result of calling the [[Delete]] internal method on ToObject(GetBase(ref)) [...]

I highlighted the portion relevant to this specific snippet. Before we try to delete anything, spec tells us to call ToObject(GetBase(ref)), where ref = a.bar.something. Let's do that, then!

  • GetBase(a.bar.something) === a.bar
  • ToObject(a.bar) === ToObject(undefined) which throws a TypeError

Which explains the final behaviour.

Final note: The error message you've shown is misleading, since it says it tried to convert null into an object, which it didn't, as it tried to convert undefined into one. Latest chrome and firefox throw a more accurate one, but I think v8 only recently fixed the error message, so perhaps upgrading to node v11 will show a correct version.

like image 166
Zirak Avatar answered Oct 18 '22 00:10

Zirak