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

What are some ways to make source code more visually appealing on an HTML/JSP page as it is being

I've recently created a webpage with some Java source code, neatly organized within blocks. However, I'm looking to enhance its appearance so it truly looks like Java code. Take a look at my page's code here for reference: Are there any onl ...

Click to save image using jQuery

I am trying to implement a feature where clicking on an image allows users to download it. I am using the download attribute for this purpose. <a href="http://mysite.ru/userfiles/certificate_2.png" class="download-certificate-link" data-title="certific ...

Displaying that the response from ajax is experiencing issues

I am currently attempting to update the td elements in a table but my current method is not yielding successful results. Here's what I have tried: <table id="job1"> <tr><td></td></tr> <tr id="Jobstatus1"> ...

Struggling to align the image elements accurately

I am aiming to create a layout similar to the image displayed below. To be more specific, my goal is to scatter smiley faces randomly within a 500px area while also having a border on the right side of the area. However, with the current code I'm us ...

The start "NaN" is not valid for the timeline in vis.js

Whenever I attempt to make a call, an error message pops up saying Error: Invalid start "NaN". I've looked everywhere online for a solution with no success. Below are the timeline options: timeline: { stack: true, start: new Date(), end: ...

Parsing JSON or eliminating double quotation marks in data acquired from a model or database within the Rails framework

foo.html.erb <script type="text/javascript"> var all_user_data = <%= @user.all_user_bar_chart.to_json.html_safe) %>; </script> abc.js $(function () { if ($("#foo").length > 0){ var user_charts = new Highcharts.Chart({ ...

Leveraging the fromNow() method in UTC with moment.js

How can I ensure that my VueJS filter converts a given date into the fromNow() format in UTC time? fromNow(date) { return moment.utc(date).fromNow(); } The timestamp provided by my Laravel backend is already generated in UTC, but the ...

Deactivating the idle timer in React Native: A Step-by-Step Guide

Currently, I am working on an app that involves a timer using RN. However, I have noticed that after approximately 1 minute and 50 seconds, the device's screen starts to dim in preparation for sleep mode. ...

unable to locate the allong.es variadic function

Encountering an Error node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ TypeError: undefined is not a function at /home/ubuntu/nodejs/test.js:4:10 at factorial (/home/ubuntu/nodejs/test.js:17: ...

Optimal Placement for FB.Event.subscribe

Here is the code I have implemented on my website for the Facebook Like and Share buttons. The functionality works seamlessly. When I click the Like button, a notification is promptly displayed on my Facebook profile page. Additionally, Facebook automatic ...

Manipulating HTML content with Javascript Array

Is there a way to retain Javascript context from an array? Any suggestions for improving this code? SAMPLE: <html> <script type="text/javascript"> var rand = Math.floor(Math.random() * 6) + 1; var i = Math.floor(Math.random() * 6) + 1; var ...

The issue I am facing is that the MDCSelect:change event does not seem to be functioning properly

Is there a way to get MDCSelect:change to work properly before appending all options? If I put MDCSelect:change after the list append, it works but the UI doesn't look right. Question: How can I make MDCSelect:change work without affecting the UI ap ...

Click event triggers dynamic function call

Is it possible to have different functions for ng-click depending on the loaded controller page? <a ng-click="removeEvent(event)" class="top_menu_link"> REMOVE</a> For instance, if I want the same button to trigger a remove action but on diff ...

Passing dynamic scope from Angular to a directive is a seamless process

I am working with a directive that retrieves an attribute value from an http call in the following manner: Controller app.controller("HomeController", function($scope){ $http.get("/api/source").then(function(res){ $scope.info = res.data }); }); ...

The Gusser Game does not refresh the page upon reaching Game Over

Hi there, I am a beginner in the world of JavaScript and currently working on developing a small guessing game app. However, I have encountered an issue where the page is not reloading after the game is over and the 'Play Again' button appears to ...

Linking promises to eliminate nesting

Hi everyone, I am currently working on chaining promises in my code. The initial HTTPS call returns an array of URLs successfully. After that, I loop through them to obtain a JSON object for each one. I am wondering if there is a way to reduce nesting in ...

Exploring the integration of angular-ui-select into an angular seed project

I set up a new project using the starter template from https://github.com/angular/angular-seed and now I'm attempting to integrate angular-ui-select for dropdown menus. I've added select.js and select.css files to my index.html as well as install ...

Retrieve the radio button value without using a key when submitting a form in JSON

Looking to extract the value upon form submission in Angular, here is the code: In my Typescript: constructor(public navCtrl: NavController, public navParams: NavParams, public modalCtrl: ModalController, public formBuilder: FormBuilder, public alertCtrl ...

Is the indigo-pink color scheme fully implemented after installing @angular/material and scss using ng add command?

After running ng add @angular/material, we are prompted to choose a CSS framework and theme. I opted for indigo-pink and scss. Will the material components automatically inherit this theme, or do we need to take additional steps? When using normal CSS (wi ...

Navigating through a complex JavaScript project and feeling a bit disoriented

I recently joined a JavaScript project that was being worked on by a single programmer for the past 6 months. However, this programmer left without providing much explanation. The project is built using Ionic, which I have discovered is primarily used for ...