Safely inheriting prototypes in JavaScript [duplicate]

左心房为你撑大大i 提交于 2019-12-08 06:13:27

问题


Let's say I'm shooting for some basic inheritance in my application, I could achieve this by setting the prototype of my child to the parent.

// Parent "class"
var Car = function(year) {
    this.year = year;
};

Car.prototype.drive = function() {
    console.log('Vrooom');
};

// Child "class"
var Toyota = function() {
    Car.apply(this, arguments);
};

Toyota.prototype = Car.prototype;

var myCar = new Car(2010), myToyota = new Toyota(2010);

myCar.drive(); // Vrooom
myToyota.drive(); // Vrooom

Seems to work, but is obviously bad because if I set a new method on my Toyota.prototype, it will be set on Car's prototype.

Toyota.prototype.stop = function() {
    console.log('stooop');
};

myCar.stop(); // stooop  <--- bad
myToyota.stop(); // stooop  <--- good

To solve this, instead of Toyota.prototype = Car.prototype; I can add an intermediary:

var ctor = function() { };
ctor.prototype = Car.prototype;

Toyota.prototype = new ctor();

Toyota.prototype.stop = function() {
    console.log('stooop');
};

myCar.stop(); // throws undefined error  <--- good
myToyota.stop(); // stooop  <--- good

But I don't understand why this works. ctor creates a new instance with its prototype set to Car's prototype, and then Toyota sets its prototype to that new instance.

But why create an empty object with a prototype that gets referenced by Toyota's prototype?
Why doesn't setting a new method on Toyota set it on Car like it does in the first example? What if I want several layers of inheritance, it seems like I need to glue them together with a new ctor every time?


回答1:


But why create an empty object with a prototype that gets referenced by Toyota's prototype?

Because if you do this: Toyota.prototype = new Car();

Now Toyota's prototype is an instance of Car, which means that in addition to having Car's prototype in the chain, it also has any properties set in the Car constructor itself. Basically you wouldn't want Toyota.prototype to have a property called year like so: Toyota.prototype.year. Because of this it's a lot better to have an empty constructor like so:

var ctor = function() { };
ctor.prototype = Car.prototype;

Toyota.prototype = new ctor();

Now Toyota.prototype has the new ctor() instance as it's prototype, which in turn has Car.prototype in its own chain. This means that instances of Toyota now can execute methods that exist in Car.prototype.


Why doesn't setting a new method on Toyota set it on Car like it does in the first example?

With this: Toyota.prototype = Car.prototype; you're setting Toyota' prototype to be the same exact object as what Car.prototype contains. Since it is the same object, changing it in one place also changes it everywhere else. This means that objects are passed be reference and not by value, which is another way of saying that when you assign an object to 3 different variables, it is the same object regardless of which variable you use. Strings for example are passed by value. Here is an example with strings:

var str1 = 'alpha';
var str2 = str1;
var str3 = str1;

str2 = 'beta';

// Changing str2 doesn't change the rest.
console.log(str1); //=> "alpha"
console.log(str3); //=> "alpha"
console.log(str2); //=> "beta"

Now compare to objects and their properties;

var obj1 = {name: 'joe doe'};
var obj2 = obj1;
var obj3 = obj1;

console.log(obj1.name); //=> "joe doe"
console.log(obj2.name); //=> "joe doe"
console.log(obj3.name); //=> "joe doe"

obj2.name = 'JOHN SMITH';

console.log(obj1.name); //=> "JOHN SMITH"
console.log(obj2.name); //=> "JOHN SMITH"
console.log(obj3.name); //=> "JOHN SMITH"


What if I want several layers of inheritance...

Here is another way of creating layers of inheritance:

var Car = function(year) {
    this.year = year;
};

Car.prototype.drive = function() {
    console.log('Vrooom');
};

var Toyota = function() {
    Car.apply(this, arguments);
};

// `Object.create` does basically the same thing as what you were doing with `ctor()`
// Check out the documentation for `Object.create` as it takes a 2nd argument too.
Toyota.prototype = Object.create(Car.prototype);


// Create instances
var 
  myCar = new Car(2010), 
  myToyota = new Toyota(2010);


// Add method to Toyota's prototype
Toyota.prototype.stop = function() {
    console.log('stooop');
};

Let's try it out now:

myToyota.stop(); //=> 'stooop'
myCar.stop(); //=> 'TypeError: undefined is not a function'

myCar.drive(); //=> Vrooom
myToyota.drive(); //=> Vrooom



回答2:


Your problem is the following line:

Toyota.prototype = Car.prototype;

and then later modifying this object:

Toyota.prototype.stop = function() {
    console.log('stooop');
};

because in the first line, you set Toyota.prototype to the exact same object as Car.prototype. This is not a copy, it is a reference to the same object! So as soon as you modify stop on Toyota.prototype, you actually modify both Toyota.prototype and Car.prototype, because it is one and the same.

What you'd really want to do is replacing the first line with:

Toyota.prototype = Object.create(Car);

so that you now have the prototype of Toyota point to the Car function, as it should, instead to Car's own prototype. Congratulations, you've used the prototype chain! (Note: Using Object.create to do class inheritance is inherently more stable and reliable, because it does not run any code that you might have contained in the Car function, but only sets up the prototype link.)

For further discussion on what is happening here exactly, and why you might be better off not using "class inheritance" in JS at all, you might want to read Chapters 4-6 of You Don't Know JS (objects & prototypes).

On your last question: "What if I want several layers of inheritance, it seems like I need to glue them together with a new ctor every time?" – yes, you'd need to do this, and this is the standard pattern for (fake) classes and inheritance in JavaScript.



来源:https://stackoverflow.com/questions/26595866/safely-inheriting-prototypes-in-javascript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!