问题
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