Home > Enterprise >  Javascript Prototype changed but still accessing old methods
Javascript Prototype changed but still accessing old methods

Time:07-02

I was learning inheritance and Prototype chaining. I was stuck after seeing this behaviour of resetting the constructor function's prototype inside the function itself. On instantiating

  • Case

const Person = function (name,age) {
    this.name = name;
    this.age = age;
}
Person.prototype.calcAge = function () {
    console.log(`My age is ${2022-this.age}`);
}
const Student = function (name,age,course) {
    Person.call(this,name,age);
    this.course = course;
    Student.prototype = null; // This should set Student's Prototype to null and 
// thus should not let instance to access intro property.
}
Student.prototype.intro = function () {
    console.log(`I am ${this.name} and my course is ${this.course}`)
};


const mike = new Student("mike",2000,"cse");

mike.intro();

In the code above, the output is I am mike and my course is cse but while instantiating I did set Student.prototype = null; so how is mike instance still accessing intro method.

CodePudding user response:

At the moment you call new, the prototype object is taken from Student.prototype and used to create this. So at that moment the proto of this is the object that Student.prototype references.

It is that proto reference that is later used to find the definition of intro. It is found via Object.getPrototypeOf(mike), not via Student.prototype. The latter only serves for future constructions.

By setting Student.prototype to null, you only determine what will happen with the next use of new Student. But for the instance that was already created, it comes too late. Object.getPrototypeOf(this) (when in the constructor's execution), will still refer to the original prototype object.

Here some extra console.log to illustrate that point:

const Person = function (name,age) {
    this.name = name;
    this.age = age;
}
Person.prototype.calcAge = function () {
    console.log(`My age is ${2022-this.age}`);
}
const Student = function (name,age,course) {
    Person.call(this,name,age);
    this.course = course;
    // true:
    console.log(Student.prototype === Object.getPrototypeOf(this));

    Student.prototype = null; 
    // false:
    console.log(Student.prototype === Object.getPrototypeOf(this));
}

Student.prototype.intro = function () {
    console.log(`I am ${this.name} and my course is ${this.course}`)
};

const mike = new Student("mike",2000,"cse");
// true:
console.log(Object.getPrototypeOf(mike).hasOwnProperty("intro"));

mike.intro();

CodePudding user response:

According to MDN, the new operator does 4 things when used to call a function, of which the second is:

Points newInstance's [[Prototype]] to the constructor function's prototype property.

And it adds the following note:

Note: Properties/objects added to the constructor function's prototype property are therefore accessible to all instances created from the constructor function.

This is presumably known to you, and why you are expecting setting Student.prototype to null to stop any method calls on a Student instance from working.

But there is a big difference between:

  1. "adding (functions) to the constructor function's prototype property" (which is what the above quote talks about, and what you do in setting Student.prototype.intro to a function), and
  2. completely replacing the function's prototype property with a new value (in your case null)

When your example code runs, the above step involved in calling the new operator goes through as normal. Note that Student.prototype exists as soon as you declare the Student function - and it's (to all intents and purposes, anyway), an empty object. So the new Student instance - mike in your case - will be linked via the prototype chain to this object Student.prototype. Which contains an intro method because you added one explicitly (and you did so before instantiating the object).

When you set Student.prototype to null, later in the constructor, that doesn't change at all what mike's prototype is (in the sense of the object that JS will look up property/method accesses on if you access a non-existent property, such as intro, on mike). That's still the same object it was before - an essentially empty one, with an intro method added. That object still exists, even though it's no longer accessible as Student.prototype. And it's still prototype-linked to mike, because that link already happened when you instantiated mike.

Of course, since Student.prototype is null after that first instantiation, things will go very wrong on the second one:

const Person = function (name,age) {
    this.name = name;
    this.age = age;
}
Person.prototype.calcAge = function () {
    console.log(`My age is ${2022-this.age}`);
}
const Student = function (name,age,course) {
    Person.call(this,name,age);
    this.course = course;
    Student.prototype = null; // This should set Student's Prototype to null and 
// thus should not let instance to access intro property.
}
Student.prototype.intro = function () {
    console.log(`I am ${this.name} and my course is ${this.course}`)
};


const mike = new Student("mike",2000,"cse");
const bob = new Student("bob", 1000, "math");
mike.intro();
bob.intro();

but (as still seen above), the first invocation is absolute fine.

If you absolutely need to remove one or more methods on the first instantiation (although why you want to do this, I cannot guess), then you need to mutate the Student.prototype reference rather than overwriting it. So replace

Student.prototype = null;

with

Student.prototype.intro = null;

(or similar)

  • Related