Chaining inheritance through Object.create

Recently, I decided to experiment with Object.create() instead of using new. How can I achieve multiple inheritance in JavaScript, for example classA -> classA's parent -> classA's parent's parent, and so on?

For instance:

var test = Object.create(null); 
test.prototype = {
    greet: function () {
        console.info('hello world');
    },

    name: 'myName'
};

var test2 = Object.create(test.prototype);
test2.prototype = {
    name: 'newName'
};

var test3 = Object.create(test2.prototype);
test3.prototype = {
    name: 'another Name'
};

Although test2 can still use the greet method, test3 cannot because we used the prototype of test2 which does not have information about test and therefore no access to the greet method.

I've come across articles that advise against using __proto__ for inheritance. What is the proper way to achieve this kind of inheritance in JavaScript?

Is there a more correct way to do this with Object.create? Something like the following but utilizing Object.create:

test2.prototype = Object.create(test);
test2.constructor = test2;

test3.prototype = Object.create(test2);
test3.constructor = test3;

var a = new test3();
a.greet();

Answer №1

When using Object.create, objects inherit directly from one another without the use of the prototype property. In the example below, properties are set after calling Object.create rather than during the call.

var test1 = Object.create(null, {
    greet : {value : function() {
        console.info('hello world');
    }},
    
    name : {value : 'myName'}
});

var test2 = Object.create(test1, {
    name : {value : 'alteredProperty'}});

var test3 = Object.create(test2);

test3.greet(); // hello world
console.log(test3.name); // alteredProperty

Here is a simpler example without property descriptors:

var test1 = Object.create(null);
test1.greet = function() {
    console.info('hello world');
};
test1.name = 'myName';

var test2 = Object.create(test1);
test2.name = 'alteredProperty';

var test3 = Object.create(test2);

test3.greet();
console.log(test3.name);

To avoid creating new functions each time an object is made, you can offload methods to a prototype-like object as shown in the next example.

// proto object
var Test1 = {
  greet : function() { console.info('hello world ' + this.name); },
  name : 'Test1'
};

// instance of Test1
var test1 = Object.create(Test1);

// proto object inheriting from Test1
var Test2 = Object.create(Test1)
Test2.name = 'Test2';

// instance of Test2
var test2 = Object.create(Test2);

// proto object inheriting from Test2
var Test3 = Object.create(Test2);
Test3.size = 'big';

// instance of Test3
var test3 = Object.create(Test3);

test3.greet(); // hello world Test2
console.info(test3.name); // Test2
console.info(test3.size); // big
test3.name = 'Mike';
test3.greet(); // hello world Mike

Utilize the Capital letter naming convention for objects that act like Constructors with prototypes to enforce proper usage and organization of methods and default values.

Bonus:

function isInstanceOf(child, parent) {
  return Object.prototype.isPrototypeOf.call(parent, child);
}

console.info(isInstanceOf(test3, Test3)); // true
console.info(isInstanceOf(test3, Test1)); // true
console.info(isInstanceOf(test2, Test3)); // false

Answer №2

Implementing Object.create in the same manner as Tibos does appears to assign all the members of the input object to the prototype of the resulting object.

// When running in firefox firebug
// on an empty page
var Definition = {
  name : 'Test1'
};
// The location of this definition is irrelevant
Definition.greet=function() { 
  console.log(this);//<-what is this in Chrome?
};
Definition.arr=[];
// Instance of Test1
var test1 = Object.create(Definition);
var test2 = Object.create(Definition);
console.log(test1.greet===test2.greet);//true
delete test2.greet
delete test2.greet
delete test2.greet
delete test2.greet//can't delete it
test2.greet();
console.log(test1.greet===test2.greet);//true
console.log(test1.arr===test2.arr);//true
test1.arr.push(1);
console.log(test2.arr);//=[1]
var things=[];
for(thing in test1){
  things.push(thing);
}
console.log("all things in test1:",things);
things=[];
for(thing in test1){
  if(test1.hasOwnProperty(thing)){
    things.push(thing);
  }
}
console.log("instance things in test1:",things);//nothing, no instance variables

[update]

From the code above, it's clear that using Object.create generates an object with all the properties from the first parameter within its prototype and the second parameter becomes its instance properties. (Answer provided by mccainz in the comments) An additional advantage (excluding older browsers like IE8) is the ability to set enumerable, writable, and configurable flags on instance properties, as well as creating getters and setters for assignment-like behavior (i.e., instance.someprop=22 can be achieved via instance.someprop(22)).

To define specific instance properties, different patterns can be employed. While these patterns may result in code looking "ugly" or even worse than utilizing the new keyword, personal preference plays a role here and doesn't negate the benefits of having control over properties (enumerable, writable, configurable).

One approach involves using the init function:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB).init("Bob");

A more complex pattern that offers greater control looks like this:

var Person={
  talk:function(){console.log("I'm "+this.name);}
  //,other prototype stuff related to Person
};
var userCreator={
   processInstanceMembers:function(o,initObj){
     this.createName(o,initObj.name);
     Object.defineProperty(o,"_name",{writable:true});
     o.name=initObj.name;
   },
   get:function(initObj,inheritFrom){
     var ret=Object.create(inheritFrom||Person);
     this.processInstanceMembers(ret,initObj);
     return ret;
   },
   createName:function(o){//minimalise closure scope
     Object.defineProperty(o,"name",{
       get:function(){
         return this._name;
       },
       set:function(val){
         if(val.replace(/\s*/gm,"")===""){
           throw new Error("Name can't be empty, or only whitespaces");
         }
         this._name=val;
       },
       enumerable : true
     });
   }
};

var u=userCreator.get({name:"Ben"});
u.talk();
u.name="Benji";
u.talk();
u.name="  ";//error, name can't be empty

Creating a new instance of Parent solely for setting inheritance of Child is unnecessary; Object.create can be utilized for this purpose or helper functions:

var Child =function(){
  Parent.apply(this,arguments); //get Parent's INSTANCE members defined in the parent function body with this.parentInstance=...
}
Child.prototype=Object.create(Parent.prototype);
Child.prototype.constructor=Child;
Child.prototype.otherFn=function(){};

You might find this link helpful; it includes a helper function so you don't have to use Object.create if desired.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Discovering the power of Next.js Dynamic Import for handling multiple exportsI hope this

When it comes to dynamic imports, Next.js suggests using the following syntax: const DynamicComponent = dynamic(() => import('../components/hello')) However, I prefer to import all exports from a file like this: import * as SectionComponents ...

The error message "Unexpected node environment at this time" occurred unexpectedly

I am currently watching a tutorial on YouTube to enhance my knowledge of Node.js and Express. To enable the use of nodemon, I made modifications to my package.json file as shown below: package.json "scripts": { "start": "if [[ $NODE_ENV == 'p ...

Storing information when an object is indexed in a for loop, to be retrieved later when

My JavaScript code includes an AJAX call that takes user input from a form and uses it to search for a JSON object. The matching objects are then displayed in a datalist successfully. Everything is working well so far, but now I want to extract specific f ...

Struggling with your Dropdown Menu onclick functionality in Javascript? Let me lend

I am seeking assistance with implementing a Javascript onclick Dropdown Menu. The current issue I am facing is that when one menu is opened and another menu is clicked, the previous menu remains open. My desired solution is to have the previously open menu ...

Sort activities according to the preferences of the user

This is the concept behind my current design. Click here to view the design When making a GET request, I retrieve data from a MongoDB database and display it on the view. Now, I aim to implement a filter functionality based on user input through a form. ...

What mechanisms do frameworks use to update the Document Object Model (DOM) without relying on a

After delving into the intricate workings of React's virtual DOM, I have come to comprehend a few key points: The virtual DOM maintains an in-memory representation of the actual DOM at all times When changes occur within the application or compo ...

Gathering the presently unfinished observables within a superior-level rxjs observable

As an illustration, let's consider a scenario where I have a timer that emits every 5 seconds and lasts for 10 seconds. By using the scan operator, I can create an observable that includes an array of all the inner observables emitted up until now: c ...

Issues with the audio on ExpressJS video streaming can be quite vexing

After multiple attempts to run an ExpressJS web server that serves videos from my filesystem, I've encountered a frustrating issue. Whenever a video is played, there are constant popping sounds and eventually the audio cuts out completely after 3-10 m ...

Generating directory for application, only to find TypeScript files instead of JavaScript

While following a tutorial on setting up a react.js + tailwindcss app, I used the command npx create-next-app -e with-tailwindcss [app name]. However, instead of getting javascript files like index.js, I ended up with TypeScript files like index.tsx. You c ...

Issue: Error occurs when using _.sample on an array containing nested arrays

I am working with an array of arrays that looks like this: [[0,0], [0,1], [0,2], [0,3]...] My goal is to randomly select N elements from the array using Underscore's _.sample method: exampleArr = [[0,0], [0,1], [0,2], [0,3]...] _.sample(exampleArr, ...

Combining Date and Time in Javascript: A Guide

As a JavaScript beginner, I am struggling with combining date and time pickers. Despite finding similar questions, I have not yet found the solution I need. I have two inputs: one for the datePicker and another for the timePicker. <form> <div clas ...

The jquery live click event is not functioning properly on iPad devices

I am experiencing an issue with a webpage that has the following code to bind click events <script type="text/javascript"> $(".groupLbl").live("click", function(e) { var $target = $(e.currentTarget); ..... $.getJSON("/somelink.js ...

Which is the better choice for accessing and manipulating JSON files – using Ajax or the Node fs module?

When storing quiz questions using JSON data, should I use vanilla AJAX or Node.js file system to read the file? I am planning to develop a website where users can create quizzes and save them as JSON. I intend to utilize the Node.js fs module to save the ...

Tips for integrating JavaScript libraries with TypeScript

I'm looking to add the 'react-keydown' module to my project, but I'm having trouble finding typings for it. Can someone guide me on how to integrate this module into my TypeScript project? ...

The speed at which Laravel loads local CSS and JS resources is notably sluggish

Experiencing slow loading times for local resources in my Laravel project has been a major issue. The files are unusually small and the cdn is much faster in comparison. Is there a way to resolve this problem? https://i.stack.imgur.com/y5gWF.jpg ...

Troubleshooting tips for optimizing Opera and Internet Explorer performance

I'm currently on the hunt for solutions or techniques to debug my jquery script specifically under the Opera/IE browser. It appears that the ajax $.post() request is either not being sent at all, or it's being sent to the wrong address, among oth ...

Angular directive has issues with $compile functionality

This Angular directive automatically appends a new HTML item to the page every time my model changes: app.directive('helloWorld', function($compile) { return { restrict: 'AE', replace: true, scope:{ ...

The sequence of CSS and deferred JavaScript execution in web development

Consider this scenario: you have a webpage with a common structure in the <head>: <link rel="stylesheet" href="styles.css"> // large CSS bundle <script defer src="main.js"></script> // small JS bundle with defer attribute There is ...

Activate Gulp watcher to execute functions for individual files

I have developed a specific function that I need to execute only on the file that has been changed with gulp 4.0 watcher. For example, the current task setup looks like this: gulp.watch([ paths.sourceFolder + "/**/*.js", paths.sourceFolder + "/**/ ...

Is it possible to execute a function upon exiting my Node application?

Currently, I'm developing a project for my school using Node.js on a Raspberry Pi. The script I have created will be running for extended periods of time to control LEDs. Is there a method that allows me to execute a function upon exiting the program ...