Brian Ogden
Brian Ogden

Reputation: 19232

Object.defineProperty "undefining" value Attribute in a Prototypal Object Creation Pattern

When using object literal notation to create a Javascript Object like so:

var objectLiteralCat = {
  name: {first: 'frisky', last: 'LaBeouf'},
  color: 'Red'
}

I can then alter an Attribute of the name Property of objectLiteralCat like so:

Object.defineProperty(objectLiteralCat,'name', {configurable: false});

Great, now no one else can delete the objectLiteralCat.name Property or alter its enumerable Attribute.

I am experimenting with a "flavor" of prototypal pattern object creating and I would like to use Object.defineProperty to set the name Property Attribute configurable = false like above, here is the code:

var Cat = {
  create: function(firstName, lastName, color){
    var self = Object.create(this);
    self.name.first = firstName;
    self.name.last = lastName;
    Object.defineProperty(self,'name', {configurable: false});
    Object.freeze(self.name);
    Object.freeze(self.color);
    return self;
  },
  name: {first: '', last: ''},
  color: ''
}

var cat = Cat.create('frisky','smith','white');
console.log(cat.name); //undefined

If, in the Cat.create function I comment out the line:

Object.defineProperty(self,'name', {configurable: false});

Then console.log(cat.name) outputs, as expected:

Object {first: "frisky", last: "smith"}

I can explicitly set the value Attribute of the Cat.name Property like so:

Object.defineProperty(self,'name', {value: self.name, configurable: false});

What I do not not understand is why doesn't the objectLiteralCat call to Object.defineProperty:

Object.defineProperty(objectLiteralCat,'name', {configurable: false});

set the the objectLiteralCat.name to undefined like:

Object.defineProperty(self,'name', {configurable: false});

does in my Cat.create function?

Even if I comment out the Object.define code in Cat.create and move it outside after the cat object is created like so:

var cat = Cat.create('fluffy', 'leboudf', 'white');
Object.defineProperty(cat,'name', {configurable: false});
console.log(cat.name);

The output of console.log(cat.name) is again undefined.

My objectLiteralCat object:

Object {
    name: [object Object]
    color: Red
}

looks fundamentally the same to my "flavor of prototypal pattern object creation"

Object {
    create: function(firstName, lastName, color){
        var self = Object.create(this);
        self.name.first = firstName;
        self.name.last = lastName;
        //Object.defineProperty(self,'name', {value: self.name, configurable: false});
        Object.freeze(self.name);
        Object.freeze(self.color);
        return self;
    }
    name: [object Object]
    color:
}

Looking at the console.log of the two objects though, it looks like all the Properties of the object created with Cat.create are nested in __proto__

enter image description here

I am sure that is a clue to the answer to my question but I am not advanced enough yet with Javascript to figure out the answer without help.

Upvotes: 3

Views: 91

Answers (2)

CertainPerformance
CertainPerformance

Reputation: 371069

The difference is that, in your object literal, objectLiteralCat.name refers to the name object immediately on objectLiteralCat. On the other hand, when calling Cat.create, you make two names: one is on the prototype object (the object at Cat.name), and the other is on the self object. Object.defineProperty does not look up the prototype chain to identify where/if the passed property name passed is on any of the prototype objects - rather, Object.defineProperty defines the property immediately on the object passed to it, which in this case, is self.

But, before

Object.defineProperty(self,'name', {configurable: false});

is called, references to self.name - that is, in the lines

self.name.first = firstName;
self.name.last = lastName;

will refer to the closest object in the prototype chain with a name property - which is the Cat.name object. To illustrate, see how instantiating a cat mutates the Cat.name prototype object:

var Cat = {
  create: function(firstName, lastName, color){
    var self = Object.create(this);
    self.name.first = firstName;
    self.name.last = lastName;
    Object.defineProperty(self,'name', {configurable: false});
    Object.freeze(self.name);
    Object.freeze(self.color);
    return self;
  },
  name: {first: '', last: ''},
  color: ''
}

var cat = Cat.create('frisky','smith','white');

// Log the prototype object: it's been mutated!
console.log(Cat.name);

After that, when you call

Object.defineProperty(self,'name', {configurable: false});

this sets the name property directly on self, not on the prototype. But you haven't passed any value to it, so it defaults to undefined. The name property now exists both on the prototype object (Cat.name.first and Cat.name.last), and on the instantiated cat (=== undefined).

Perhaps you want to explicitly create a new name property directly on self at the beginning, before assigning to self.name.first:

var Cat = {
  create: function(firstName, lastName, color){
    var self = Object.create(this);
    Object.defineProperty(self,'name', {configurable: false, value: {}});
    self.name.first = firstName;
    self.name.last = lastName;
    Object.freeze(self.name);
    Object.freeze(self.color);
    return self;
  },
  name: {first: '', last: ''},
  color: ''
}

var cat = Cat.create('frisky','smith','white');
console.log(cat.name);

Upvotes: 3

Jonas Wilms
Jonas Wilms

Reputation: 138477

While self is a new instance inheriting the Cat object, self.name still references the global Cat.name. Therefore if you do:

const obj = { prop: 1 }
Object.defineProperty(obj, "prop", { /*...*/ })

Its a different thing, as the property exists directly on the object itself and and is not inherited. If it gets inherited, e.g.:

const obj2 = Object.create(obj);
Object.defineProperty(obj2, "prop", {/*...*/})

then defineProperty defines a new property on the object itself. It does not consider that it was set on the prototype already. Therefore you shadow it to undefined. To solve that you might wanna assign a new object for every instance, e.g.:

self.name = { first: firstName, last: lastName};

Upvotes: 2

Related Questions