1

There are many ways to create objects. However, I wanted to know why I am getting an error with this approach (just attempting to learn) - What is meant by 'undefined' in the error message? The problem is not related to how the object was first instantiated as I have tried var Obj={}; and obtained same result. Thx.

'use strict';
 var Obj=Object.create(null);
 var descriptor={
    value: 37, writable: true, enumerable: true, configurable: true};
 Object.defineProperty(Obj, 'p0' , descriptor);   
 // Next line causes an error: Cannot set property 'f' of undefined
 Obj.prototype.f = function() { //define a method f() on Obj PROTOTYPE
        return (`hello ${this.p0}`); 
  };
 console.log(Obj.f());
NoChance
  • 5,632
  • 4
  • 31
  • 45
  • 1
    It means that `Obj.prototype` is undefined. – Barmar Oct 27 '17 at 20:32
  • 2
    Possible duplicate of [Creating Js object with Object.create(null)?](https://stackoverflow.com/questions/15518328/creating-js-object-with-object-createnull) – Andrew Li Oct 27 '17 at 20:32
  • @barmar, thank you. Why is it not defined? Isn't the prototype implicitly created? – NoChance Oct 27 '17 at 20:34
  • @AndrewLi, thanks for the link. I tried to use Obj={}; but still got the same message. – NoChance Oct 27 '17 at 20:38
  • 3
    @NoChance By creating an object with `Object.create()` the argument you pass in IS the object's prototype. The options for this are either an Object or null. You have passed in null, which it treats as undefined in this case. – mhodges Oct 27 '17 at 20:38
  • @mhodges, that clarifies it for me. Thank you. If you care to put this in an answer I will accept it as the answer. – NoChance Oct 27 '17 at 20:41
  • @NoChance Take a look at my answer, hopefully that explains it better for you. – mhodges Oct 27 '17 at 21:14
  • 2
    @NoChance Because only `function` objects are created by default with a `.prototype` property – Bergi Oct 27 '17 at 21:57

2 Answers2

3

The OP's interesting example merits close inspection to best grasp the resulting error as well as how to improve the code.

Passing null to Object.create() produces an undefined object which causes the error to occur. The correction involves passing instead a prototype object, such as Object. The result: Obj possesses a valid and defined object with all the methods of the Object prototype.

Variable descriptor holds a valid object whose prototype incidentally is accessible as descriptor.constructor.prototype. More information about constructors here.

The statement adding a property to Obj is correct, but of limited usefulness. It adds a property directly to the Obj and not to its prototype. For that reason, adding the method f to the prototype is awkward since it can only access the property p0 of the specific object Obj owing to the property not belonging to the prototype of Obj. The final statement indicates that method f() works correctly with respect to Obj when the code below executes:

'use strict';

// creates undefined object
var Obj=Object.create(null);
console.log("Obj is " + Obj.constructor);

// instantiate Obj of type Object
Obj = Object.create( Object );
console.log("Obj is now instance of Object? " + (Obj instanceof Object));


var descriptor={
value: 37, writable: true, enumerable: true, configurable: true};

// directly add property to object Obj
Object.defineProperty(Obj, 'p0', descriptor);

// adding new method to Obj prototype
 Obj.prototype.f = function() { 
    return ("hello " + Obj.p0); 
  };
  
console.log(Obj.f());

The OP may wish to revise the code as follows:

'use strict';

var descriptor =
{
value: 37, 
writable: true,
enumerable: true,
configurable: true
};


/*  instantiate Obj of type Object
 *  like this:  
 *      var Obj = Object.create( Object );
 *  Or: 
 */
var Obj = Object.constructor;

// make p0 a property of the Obj prototype
Obj.prototype.p0 =  descriptor;

// add method to Obj prototype
 Obj.prototype.f = function() { 
    return ("hello " + this.p0.value); 
  };
  
console.log( Obj.f() );

The advantage of this second example is that all objects instantiated from the Obj prototype will have an f method and a p0 property.

Note: in re the 'use strict' statement, one should be aware that not all browsers support it; see here.

slevy1
  • 3,797
  • 2
  • 27
  • 33
  • 1
    Thank you. Obj.p0=decriptor is indeed new to me! – NoChance Oct 27 '17 at 20:49
  • 2
    Not exactly, `f` should be on Obj's prototype, not on Obj itself. `Obj.hasOwnProperty("f")` should be false – mhodges Oct 27 '17 at 20:50
  • @mhodges, you are correct again. The code will only work for one object. You may want to put your comment in an answer. – NoChance Oct 27 '17 at 20:52
  • This changes `Obj` from a plain object into a constructor function. Not sure whether the OP wanted that – Bergi Oct 27 '17 at 21:59
  • Thank you everyone for your comments; have revised the code accordingly so this should be a big improvement. – slevy1 Oct 27 '17 at 22:44
  • @mhodges the revised code does not extend Object.prototype but rather the new object Obj whose prototype is Object. Nothing disguised here. And with respect to your myObj, to quote Bergi: "This changes ... from a plain object into a constructor function. Not sure whether the OP wanted that " – slevy1 Oct 30 '17 at 18:32
  • @slevy1 Your `Obj` is a constructor function, just as `myObj` is in my code. In both cases, you can add to the prototype and create instances of that object that point to the prototype we have defined. The only difference is that you would create instances with `var x = Object.create(Obj)` and I would say `var x = new myObj()` – mhodges Oct 30 '17 at 18:49
3

So, basically, I think this boils down to a fundamental misunderstanding of what a prototype is. Individual instances of an object do not have a .prototype, rather, they have an internal link to the prototype of the constructor from which the object instance was created. This was formerly known as .__proto__ (AKA dunder-proto), but has since been officially deprecated.

More recently, to reference the prototype of the constructor for an object instance, you can access a property called .constructor. (*Note*: .constructor may be undefined depending on how the object was created). From this, you can access the .prototype.

Similarly, you can use Object.getPrototypeOf(obj) and Object.setPrototypeOf(obj) where obj is an instance of an object.

For example:

var x = Object.create(null);
console.log("x.prototype", x.prototype);

var y = Object.create({a: "foo"});
console.log("y.prototype:", y.prototype);

The .prototype is undefined in both cases, because object instances do not have a prototype property, only object constructors do.

That being said, we can access the prototype that an object instance was created from by using the .constructor.prototype property, like so:

function myObj (){
  this.a = "foo";
}
// myObj is a constructor function, which has a prototype that we can set properties on
myObj.prototype.b = "bar";
// obj is an instance of an object, which does not have a prototype property
var obj = new myObj();

console.log("obj.prototype:", obj.prototype);
console.log("Object.getPrototypeOf(obj):", Object.getPrototypeOf(obj));
console.log("obj.constructor.prototype:", obj.constructor.prototype);
mhodges
  • 10,938
  • 2
  • 28
  • 46
  • Thank you for your through explanation of the problem. One day, I may get how this language works. – NoChance Oct 27 '17 at 22:00
  • 2
    Please weed out any mentions of the deprecated `__proto__` getter/setter. No, objects don't have a `__proto__` themselves, they have an *internal* prototype chain link - usually called [[prototype]]. It should always be accessed using `Object.getPrototype`. – Bergi Oct 27 '17 at 22:01
  • 1
    @Bergi True, I think it's a matter of semantics, which is why I didn't say `__proto__` **property**. You're right that `__proto__` is simply an internal prototype chain link. Your point about it being deprecated is valid, I just used that as an example to illustrate the difference. Saying `getPrototype` when `.prototype` is undefined can be pretty confusing. However, for the sake of future readers, I will update my post with your suggestions. – mhodges Oct 27 '17 at 22:27
  • @mhodges y does have a prototype. But, it's a property of its constructor. Try this: console.log(y.constructor.prototype===Object.prototype); – slevy1 Oct 28 '17 at 08:22
  • @slevy1 I would disagree. y *points* to a prototype, but does not own one itself. For this reason, you cannot change an object instance's prototype without changing the prototype for the object constructor - it can only be referenced via its constructor. – mhodges Oct 30 '17 at 15:42
  • @mhodges Thanks, but `.constructor` is *not* a built in link. It's a normal property that is usually inherited from the [[prototype]] - but e.g. `x.constructor` will be empty. – Bergi Oct 30 '17 at 16:35
  • @Bergi Fixed (I think). Getting the wording exactly right is tricky - thanks for the continued feedback. – mhodges Oct 30 '17 at 16:43
  • Why not just drop all that and *only* recommend to use `Object.getPrototypeOf`? :-P – Bergi Oct 30 '17 at 16:45