JS: Confusion about inheritance

I am familiar with OOP concepts through languages ​​such as C ++, Java. Now I'm trying to learn JavaScript as a hobby, mainly because of my interest in WebGL. But I am having problems with prototype-based inheritance.

Let's say I have a base class that takes a parameter in the constructor. And I need to expand this. The following is the way I do this.

function Base(n) { this._n = n; } Base.prototype.print = function() { console.log(this._n); } function Derived(n) { Base.call(this, n); } Derived.prototype = new Base; Derived.prototype.constructor = Derived; 

Now this is what I understand: a single Base object prototypes Derived . Thus, all instances of Derived inherit properties from this Base object, for example. print method. When I call new Derived(10) , then a new object is created, the Derived function is called in the context of this newly created object, i.e. this points to a newly created object, and the Base function is called from the Derived function, and then _n is created and assigned the value 10. Therefore, if I create 5 Derived objects, they will all have their own _n property. So far so good.

But I do not like this line:

 Derived.prototype = new Base; 

The Base function expects an argument, but I am not missing anything here. There is no point in passing a parameter, since this object will act as a prototype of Derived . And for this prototype object, I don't need the value of _n . But what if the Base function depends on the argument? Let's say Base loads a resource, and the path is passed as a parameter. What to do then?

To summarize, my questions are:

  • What to do with data elements in the prototype object ( _n in this example)?
  • Derived.prototype = new Base; creates an instance of Base , and it will always remain in memory (it is assumed that Derived defined in global space). What if the Base class is very expensive and I don't need an extra object?
+6
source share
4 answers

But I do not like this line:

Derived.prototype = new Base;

Then replace it with

Derived.prototype = Object.create(Base.prototype);

See Object.create just returns a new object whose [[Prototype]] is the first parameter you give it.

Basically speaking, Derived inherits from Base , but don't call this damn constructor!

What to do with data elements in the prototype object (_n in this example)?

When your prototype chains don't call a constructor! I wrote a JS OO part 3 article about this.

It basically says that when you create the objects you create and initialize.

 // instantiate var o = Object.create(Base.prototype); // o now inherits all of Bases methods because o.[[Prototype]] === Base.prototype // o also inherits the constructor function (Base.prototype.constructor === Base) // initialize o.constructor(10); 

Now, of course, new X does both. Here you can see what is doing new.

 var new = function (constructor, ...args) { var instance = Object.create(constructor.prototype); instance.constructor(...args); return instance; } 

As you can see, you do not want new because you do not want this constructor to be called (you do not need to initialize Derived.prototype).

Derived.prototype = new Base; creates an instance of Base, and this will always remain in memory (assuming Derived is defined in global space). What if the base class is very expensive and I don't need an extra object?

This problem with Object.create not valid. Creating an instance of an object is cheap. It simply generates an object of a new thing whose internal [[Prototype]] property is a pointer to the prototype you are passing into.

The only thing that can be expensive is the constructor, and you do not call the constructor.

Disclaimer:

Object.create is ES5, and some older browsers (mostly IE8) do not support it. However, there this beautiful thing is called ES5-shim , which fixes these browsers and makes them behave like ES5.

+1
source

First, I succeeded in understanding JavaScript prototypal inheritance so well. You obviously did your homework. Most people coming from the Java or C ++ background tend to really struggle, but you got the worst of it.

The Base function expects an argument, but I am not missing anything here. What to do with data elements in the prototype object ( _n in this example)?

If you need to use Base as the base, you need to create it to accept null arguments reasonably, or you need to call it with arguments when creating the base object for Derived . These are basically your two options.

Derived.prototype = new Base; creates an instance of Base , and it will always remain in memory (it is assumed that Derived defined in global space). What if the Base class is very expensive and I don't need an extra object?

This is the same as static data in Java classes: loading a class loads this data. If you intend to use Base as a base, you would like to create it so that it does not load a bunch of things that it does not need (perhaps processing the version with a null argument otherwise, the version of the argument).

And this is the last approach (handling a construct with a null argument in a different way than a construct with arguments), which you usually see in "cool" JavaScript systems. Typically, you will see the actual design function used only to create the raw object, and another named function used to actually initialize the instances ( initialize is the name that Prototype uses and which I used when performing my replacement / revision of the prototype mechanism ). Thus, the actual constructor function takes no arguments, but then you must initialize the instance by calling the initialize function (which in turn calls the base initialize function). In most wrappers that are processed for you under the lid.

Making the constructor-vs-initializer mechanism work in practice requires some complicated plumbing, because it requires “super-characters” (calls to the base version of the function), and superscales are inconvenient in JavaScript. (What are supercalls - in fact this is a related article in the main, but researching an effective approach to them also involves creating / updating the entire inheritance system. I really have to update this article so it does not use class-based terminology, it is still prototype. it just provides the plumbing that I talked about.)

Since external resources can disappear / move / etc., and the stack overflow is intended mainly separately, here is the final result of the iterations presented in the article link above :

 // Take IV: Explicitly handle mixins, provide a mixin for calling super when // working with anonymous functions. // Inspired by Prototype Class class (http://prototypejs.org) // Copyright (C) 2009-2010 by TJ Crowder // Licensed under the Creative Commons Attribution License 2.0 (UK) // http://creativecommons.org/licenses/by/2.0/uk/ var Helper = (function(){ var toStringProblematic, // true if 'toString' may be missing from for..in valueOfProblematic; // true if 'valueOf' may be missing from for..in // IE doesn't enumerate toString or valueOf; detect that (once) and // remember so makeClass can deal with it. We do this with an anonymous // function we don't keep a reference to to minimize what we keep // around when we're done. (function(){ var name; toStringProblematic = valueOfProblematic = true; for (name in {toString: true, valueOf: true}) { if (name == 'toString') { toStringProblematic = false; } if (name == 'valueOf') { valueOfProblematic = false; } } })(); // This function is used to create the prototype object for our generated // constructors if the class has a parent class. See makeConstructor for details. function protoCtor() { } // Build and return a constructor; we do this with a separate function // to minimize what the new constructor (a closure) closes over. function makeConstructor(base) { // Here our basic constructor function (each class gets its own, a // new one of these is created every time makeConstructor is called). function ctor() { // Call the initialize method this.initialize.apply(this, arguments); } // If there a base class, hook it up. We go indirectly through `protoCtor` // rather than simply doing "new base()" because calling `base` will call the base // class `initialize` function, which we don't want to execute. We just want the // prototype. if (base) { protoCtor.prototype = base.prototype; ctor.prototype = new protoCtor(); protoCtor.prototype = {}; // Don't leave a dangling reference } // Set the prototype constructor property so `this.constructor` resolves // correctly ctor.prototype.constructor = ctor; // Flag up that this is a constructor (for mixin support) ctor._isConstructor = true; // Return the newly-constructed constructor return ctor; } // This function is used when a class doesn't have its own initialize // function; since it does nothing and can only appear on base classes, // all instances can share it. function defaultInitialize() { } // Get the names in a specification object, allowing for toString and // valueOf issues function getNames(members) { var names, // The names of the properties in 'members' name, // Each name nameIndex; // Index into 'names' names = []; nameIndex = 0; for (name in members) { names[nameIndex++] = name; } if (toStringProblematic && typeof members.toString != 'undefined') { names[nameIndex++] = 'toString'; } if (valueOfProblematic && typeof members.valueOf != 'undefined') { names[nameIndex++] = 'valueOf'; } return names; } // makeClass: Our public "make a class" function. // Arguments: // - base: An optional constructor for the base class. // - ...: One or more specification objects containing properties to // put on our class as members; or functions that return // specification objects. If a property is defined by more than one // specification object, the last in the list wins. // Returns: // A constructor function for instances of the class. // // Typical use will be just one specification object, but allow for more // in case the author is drawing members from multiple locations. function makeClass() { var base, // Our base class (constructor function), if any argsIndex, // Index of first unused argument in 'arguments' ctor, // The constructor function we create and return members, // Each members specification object names, // The names of the properties in 'members' nameIndex, // Index into 'names' name, // Each name in 'names' value, // The value for each name baseValue; // The base class value for the name // We use this index to keep track of the arguments we've consumed argsIndex = 0; // Do we have a base? if (typeof arguments[argsIndex] == 'function' && arguments[argsIndex]._isConstructor) { // Yes base = arguments[argsIndex++]; } // Get our constructor; this will hook up the base class prototype // if there a base class, and mark the new constructor as a constructor ctor = makeConstructor(base); // Assign the members from the specification object(s) to the prototype // Again, typically there only spec object, but allow for more while (argsIndex < arguments.length) { // Get this specification object members = arguments[argsIndex++]; if (typeof members == 'function') { members = members(); } // Get all of its names names = getNames(members); // Copy the members for (nameIndex = names.length - 1; nameIndex >= 0; --nameIndex) { name = names[nameIndex]; value = members[name]; if (base && typeof value == 'function' && !value._isMixinFunction) { baseValue = base.prototype[name]; if (typeof baseValue == 'function') { value.$super = baseValue; } } ctor.prototype[name] = value; } } // If there no initialize function, provide one if (!('initialize' in ctor.prototype)) { // Note that this can only happen in base classes; in a derived // class, the check above will find the base class version if the // subclass didn't define one. ctor.prototype.initialize = defaultInitialize; } // Return the constructor return ctor; } // makeMixin: Our public "make a mixin" function. // Arguments: // - ...: One or more specification objects containing properties to // put on our class as members; or functions that return // specification objects. If a property is defined by more than one // specification object, the last in the list wins. // Returns: // A specification object containing all of the members, flagged as // mixin members. function makeMixin() { var rv, // Our return value argsIndex, // Index of first unused argument in 'arguments' members, // Each members specification object names, // The names in each 'members' value; // Each value as we copy it // Set up our return object rv = {}; // Loop through the args (usually just one, but...) argsIndex = 0; while (argsIndex < arguments.length) { // Get this members specification object members = arguments[argsIndex++]; if (typeof members == 'function') { members = members(); } // Get its names names = getNames(members); // Copy its members, marking them as we go for (nameIndex = names.length - 1; nameIndex >= 0; --nameIndex) { name = names[nameIndex]; value = members[name]; if (typeof value == 'function') { value._isMixinFunction = true; } rv[name] = value; } } // Return the consolidated, marked specification object return rv; } // Return our public members return { makeClass: makeClass, makeMixin: makeMixin }; })(); 

Using:

 var Parent = Helper.makeClass(function(){ function hierarchy() { return "P"; } return {hierarchy: hierarchy}; }); var Child = Helper.makeClass(Parent, function(){ function hierarchy() { return hierarchy.$super.call(this) + " < C"; } return {hierarchy: hierarchy}; }); var GrandChild = Helper.makeClass(Child, function(){ function hierarchy() { return hierarchy.$super.call(this) + " < GC"; } return {hierarchy: hierarchy}; }); var gc = new GrandChild(); alert(gc.hierarchy()); // Alerts "P < C < GC" 

If you don't like the notation funcname.$super.call(...) for supercars, here is a mix that allows you to use a shorter / clearer version instead (but at a cost of execution):

 // Define our CallSuper mixin Helper.CallSuperMixin = makeMixin(function() { function callSuper(ref) { var f, // The function to call args, // Arguments to pass it, if we have any len, // Length of args to pass srcIndex, // When copying, the index into 'arguments' destIndex, // When copying args, the index into 'args' rv; // Our return value // Get the function to call: If they pass in a function, it the // subclass version so look on $super; otherwise, they've passed // in 'arguments' and it on arguments.callee.$super. f = typeof ref == 'function' ? ref.$super : ref.callee.$super; // Only proceed if we have 'f' if (f) { // If there are no args to pass on, use Function#call if (arguments.length == 1) { rv = f.call(this); } else { // We have args to pass on, build them up. // Note that doing this ourselves is more efficient on most // implementations than applying Array.prototype.slice to // 'arguments', even though it built in; the call to it // is expensive (dramatically, on some platforms). len = arguments.length - 1; args = new Array(len); srcIndex = 1; destIndex = 0; while (destIndex < len) { args[destIndex++] = arguments[srcIndex++]; } // Use Function#apply rv = f.apply(this, args); } } // Done return rv; // Will be undefined if there was no 'f' to call } return {callSuper: callSuper}; }); 

And again, I really need to update the terminology so that it is not based on classes. (And probably see how ECMAScript5 allows us to do things a little differently, because it adds some useful things, such as direct control over prototypes.)

+3
source

@@@ 2. Derived.prototype = new Base; creates an instance of Base and this will always remain in memory (assuming that Derived is defined in global space). What if the base class is very expensive and I don't want an extra object?

Yes. This example is an inheritance training style. To use in your application, try:

  function F() {} F.prototype = Base.prototype; // Linking to Base prototype Derived.prototype = new F(); // The least memory-consumption object. Derived.prototype.constructor = Base; // Constructor reference correction 

@@@1. What to do with data elements in the prototype object (_n in this example)?

Using the prototype chain described above, we do not create an instance of the database. So this question is not valid.

+3
source

JavaScript is very different from other "object-oriented" languages, first try to forget what you know about other languages ​​and find out how JavaScript works.

A good introduction can be found in JavaScript: “A world that misunderstands the programming language” by Douglas Crockford, he also describes how inheritance works in JS here .

EDIT: trying to answer your question: Question 1, I don’t understand at all ... sorry. Question 2: Classical inheritance in JavaScript is pretty ugly and I never found a need. What Crockford calls "Parasitic Inheritance" (second link), I think that will solve this problem. Here the "parent" object is called inside the scope of the constructor.

-1
source

Source: https://habr.com/ru/post/900374/


All Articles