Extending an Object in Javascript

﹥>﹥吖頭↗ 提交于 2019-11-26 12:39:40
osahyoun

Edit:
Before using the code, please check the comments from user2491400 that reports about the side effects of simply assigning to prototype.

Original answer:

You want to 'inherit' from Person's prototype object:

var Person = function(name){
  this.name = name;
  this.type = 'human';
}

Person.prototype.info = function(){
  console.log("Name:", this.name, "Type:", this.type);
}

var Robot = function(name){
  Person.apply(this,arguments)
  this.name = name;
  this.type = 'robot';
}

Robot.prototype = Person.prototype;        // Set prototype to Person's
Robot.prototype.constructor = Robot;   // Set constructor back to Robot

person = new Person("Bob");
robot = new Robot("Boutros");

person.info();
// Name: Bob Type: human

robot.info();
// Name: Boutros Type: robot

World without the "new" keyword.

And simpler syntax with Object.create().

I'm in the camp that believes Javascript should try to live without "new". It is a classless language, it doesn't need constructors. You simply create Objects and then extend or morph them. Granted, there are pitfalls, but this is so much more powerful and simple:

// base `Person` prototype
const Person = {
   name : '',
   age  : 22,
   type : 'human',
   greet() {
       console.log('Hi, my name is ' + this.name + ' and I am a ' + this.type + '.' )
   }
}

// create an instance of `Person`:
const skywalker = Object.create(Person)
skywalker.name = 'Anakin Skywalker'
skywalker.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'

Extending the base prototype

// create a `Robot` prototype by extending the `Person` prototype:
const Robot = Object.create(Person)
Robot.type = 'robot'
Robot.variant = '' // add properties for Robot prototype

// Robots speak in binaries, so we need a different greet function:
Robot.greet = function() { //some function to convert strings to binary }

One more level deeper

// create a new instance `Robot`
const Astromech = Object.create(Robot)
Astromech.variant = 'astromech'

const r2d2 = Object.create(Astromech)
r2d2.name = 'R2D2'
r2d2.greet() // '0000111010101011100111....'

// morphing the `Robot` object doesn't affect `Person` prototypes
skywalker.greet() // 'Hi, my name is Anakin Skywalker and I am a human.'

Further reading

** Update 3 Oct 18. Adopt ES6 syntax and use of const and let. Added example to show how to make properties immutable.

** Update 22 Jan 17. Included ES6 Object.assign().

As you can see the assignment requires multiple statements. With ES6, you can use the #assign method to shorten the assignments. (For polyfill to use on older browsers, see MDN on ES6.)

//instead of this
const Robot = Object.create(Person)
Robot.name = "Robot"
Robot.madeOf = "metal"
Robot.powerConsumption_kW = 5

//you can do this
const Robot = Object.create(Person)
Object.assign(Robot, {
    name: "Robot",
    madeOf: "metal",
    powerConsumption_kWh: 5,
    fullCharge_kWh: 10,
    currentCharge_kWh: 5
})

//attach some methods unique to Robot prototype.
Robot.charge = function(kWh) {
    let self = this
    this.currentCharge_kWh = Math.min(self.fullCharge_kWh, self.currentCharge_kWh + kWh)
    var percentageCharged = this.currentCharge_kWh / this.fullCharge_kWh * 100
    console.log(this.name + (percentageCharged === 100) ? ' is fully charged.' : ' is ' + percentageCharged +'% charged.')
}

Robot.charge(5) // outputs "Robot is fully charged."

You can also use Object.create()'s second argument a.k.a propertiesObject, which I find to be a little too lengthy. The only reason to use it over #assign is if you need more control over the values i.e writability/configurability etc... Notice how Robot is strictly to be all made of metal.

const Robot = Object.create(Person, {
    madeOf: { 
        value: "metal",
        writable: false,
        configurable: false,
        enumerable: true
    },
    powerConsumption: {
        value: "5kWh",
        writable: true,
        configurable: true,
        enumerable: true   
    }
})

And all prototypes of Robot cannot be made of something else.

const polymerRobot = Object.create(Robot)

polymerRobot.madeOf = 'polymer'

console.log(polymerRobot.madeOf) // outputs 'metal'

There are gotchas to this pattern that are likely to trip "classical-trained" programmers. Nonetheless, I find this pattern so much more readable.

tomilay

If you haven't yet figured out a way, use the associative property of JavaScript objects to add an extend function to the Object.prototype as shown below.

Object.prototype.extend = function(obj) {
   for (var i in obj) {
      if (obj.hasOwnProperty(i)) {
         this[i] = obj[i];
      }
   }
};

You can then use this function as shown below.

var o = { member: "some member" };
var x = { extension: "some extension" };

o.extend(x);

Different approach: Object.create

Per @osahyoun answer, I find the following as a better and efficient way to 'inherit' from Person's prototype object:

function Person(name){
    this.name = name;
    this.type = 'human';
}

Person.prototype.info = function(){
    console.log("Name:", this.name, "Type:", this.type);
}

function Robot(name){
    Person.call(this, name)
    this.type = 'robot';
}

// Set Robot's prototype to Person's prototype by
// creating a new object that inherits from Person.prototype,
// and assigning it to Robot.prototype
Robot.prototype = Object.create(Person.prototype);

// Set constructor back to Robot
Robot.prototype.constructor = Robot;

Create new instances:

var person = new Person("Bob");
var robot = new Robot("Boutros");

person.info(); // Name: Bob Type: human
robot.info();  // Name: Boutros Type: robot

Now, by using Object.create:

Person.prototype.constructor !== Robot

Check also the MDN documentation.

And another year later, I can tell you there is another nice answer.

If you don't like the way prototyping works in order to extend on objects/classes, take alook at this: https://github.com/haroldiedema/joii

Quick example code of possibilities (and many more):

var Person = Class({

    username: 'John',
    role: 'Employee',

    __construct: function(name, role) {
        this.username = name;
        this.role = role;
    },

    getNameAndRole: function() {
        return this.username + ' - ' + this.role;
    }

});

var Manager = Class({ extends: Person }, {

  __construct: function(name)
  {
      this.super('__construct', name, 'Manager');
  }

});

var m = new Manager('John');
console.log(m.getNameAndRole()); // Prints: "John - Manager"

In ES6, there is Object.assign for copying property values. Use {} as first param if you don't want to modify the target object (the first param passed).

var resultObj = Object.assign({},Obj1,Obj2);

For more details see link,

MDN - Object.assign()

In case if you need is a Polyfill for ES5, the link offers it too. :)

People who are still struggling for the simple and best approach, you can use Spread Syntax for extending object.

var person1 = {
      name: "Blank",
      age: 22
    };

var person2 = {
      name: "Robo",
      age: 4,
      height: '6 feet'
    };
// spread syntax
let newObj = { ...person1, ...person2 };
console.log(newObj.height);

Note: Remember that, the property is farthest to the right will have the priority. In this example, person2 is at right side, so newObj will have name Robo in it.

250R

You might want to consider using helper library like underscore.js, which has it's own implementation of extend().

And it's also a good way to learn by looking at it's source code. The annotated source code page is quite useful.

Mozilla 'announces' object extending from ECMAScript 6.0:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends

NOTE: This is an experimental technology, part of the ECMAScript 6 (Harmony) proposal.

class Square extends Polygon {
  constructor(length) {
    // Here, it calls the parent class' constructor with lengths
    // provided for the Polygon's width and height
    super(length, length);
    // Note: In derived classes, super() must be called before you
    // can use 'this'. Leaving this out will cause a reference error.
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }

  set area(value) {
    this.area = value;     } 
}

This technology is available in Gecko (Google Chrome / Firefox) - 03/2015 nightly builds.

In the majority of project there are some implementation of object extending: underscore, jquery, lodash: extend.

There is also pure javascript implementation, that is a part of ECMAscript 6: Object.assign: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Function.prototype.extends=function(ParentClass) {
    this.prototype = new ParentClass();
    this.prototype.constructor = this;
}

Then:

function Person() {
    this.name = "anonym"
    this.skills = ["abc"];
}
Person.prototype.profile = function() {
    return this.skills.length // 1
};

function Student() {} //well extends fom Person Class
Student.extends(Person)

var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

Update 01/2017:

Please, Ignore my answer of 2015 since Javascript is now supports extends keyword since ES6 (Ecmasctipt6 )

- ES6 :

class Person {
   constructor() {
     this.name = "anonym"
     this.skills = ["abc"];
   }

   profile() {
    return this.skills.length // 1
   }

}

Person.MAX_SKILLS = 10;
class Student extends Person {


} //well extends from Person Class

//-----------------
var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

- ES7 :

class Person {
    static MAX_SKILLS = 10;
    name = "anonym"
    skills = ["abc"];

    profile() {
      return this.skills.length // 1
    }

}
class Student extends Person {


} //well extends from Person Class

//-----------------
var s1 = new Student();
s1.skills.push("")
s1.profile() // 2

Summary:

Javascript uses a mechanism which is called prototypal inheritance. Prototypal inheritance is used when looking up a property on an object. When we are extending properties in javascript we are inheriting these properties from an actual object. It works in the following manner:

  1. When an object property is requested, (e.g myObj.foo or myObj['foo']) the JS engine will first look for that property on the object itself
  2. When this property isn't found on the object itself it will climb the prototype chain look at the prototype object. If this property is also not found here it will keep climbing the prototype chain until the property is found. If the property is not found it will throw a reference error.

When we want to extend from a object in javascript we can simply link this object in the prototype chain. There are numerous ways to achieve this, I will describe 2 commonly used methods.

Examples:

1. Object.create()

Object.create() is a function that takes an object as an argument and creates a new object. The object which was passed as an argument will be the prototype of the newly create object. For example:

// prototype of the dog
const dogPrototype = {
  woof: function () { console.log('woof'); }
}

// create 2 dog objects, pass prototype as an argument
const fluffy = Object.create(dogPrototype);
const notFluffy = Object.create(dogPrototype);

// both newly created object inherit the woof 
// function from the dogPrototype
fluffy.woof();
notFluffy.woof();

2. Explicitly setting the prototype property

When creating objects using constructor functions, we can set add properties to its prototype object property. Objects which are created form a constructor function when using the new keyword, have their prototype set to the prototype of the constructor function. For example:

// Constructor function object
function Dog (name) {
   name = this.name;
}

// Functions are just objects
// All functions have a prototype property
// When a function is used as a constructor (with the new keyword)
// The newly created object will have the consturctor function's
// prototype as its prototype property
Dog.prototype.woof = function () {
  console.log('woof');
}

// create a new dog instance
const fluffy = new Dog('fluffyGoodBoyyyyy');
// fluffy inherits the woof method
fluffy.woof();

// can check the prototype in the following manner
console.log(Object.getPrototypeOf(fluffy));

You can simply do it by using:

Object.prototype.extend = function(object) {
  // loop through object 
  for (var i in object) {
    // check if the extended object has that property
    if (object.hasOwnProperty(i)) {
      // mow check if the child is also and object so we go through it recursively
      if (typeof this[i] == "object" && this.hasOwnProperty(i) && this[i] != null) {
        this[i].extend(object[i]);
      } else {
        this[i] = object[i];
      }
    }
  }
  return this;
};

update: I checked for this[i] != null since null is an object

Then use it like:

var options = {
      foo: 'bar',
      baz: 'dar'
    }

    var defaults = {
      foo: false,
      baz: 'car',
      nat: 0
    }

defaults.extend(options);

This well result in:

// defaults will now be
{
  foo: 'bar',
  baz: 'dar',
  nat: 0
}

PLEASE ADD REASON FOR DOWNVOTE

  • No need to use any external library to extend

  • In JavaScript, everything is an object (except for the three primitive datatypes, and even they are automatically wrapped with objects when needed). Furthermore, all objects are mutable.

Class Person in JavaScript

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype = {
    getName: function() {
        return this.name;
    },
    getAge: function() {
        return this.age;
    }
}

/* Instantiate the class. */
var alice = new Person('Alice', 93);
var bill = new Person('Bill', 30);

Modify a specific instance/object.

alice.displayGreeting = function() 
{
    alert(this.getGreeting());
}

Modify the class

Person.prototype.getGreeting = function() 
{
    return 'Hi ' + this.getName() + '!';
};

Or simply say : extend JSON and OBJECT both are same

var k = {
    name : 'jack',
    age : 30
}

k.gender = 'male'; /*object or json k got extended with new property gender*/

thanks to ross harmes , dustin diaz

This will make extend your properties create a new Object with the object parameter prototypes without altering the passed object.

function extend(object) {
    if (object === null)
        throw TypeError;
    if (typeof object !== "object" && typeof object !== "function")
        throw TypeError;
    if (Object.create)
        return Object.create(object);
    function f() {}
    ;
    f.prototype = p;
    return new f();
}

But if you want to extend your Object without modifying it parameters, you can add extendProperty to your object.

var Person{
//some code
extend: extendProperty
}

//Enforce type checking an Error report as you wish
    function extendProperty(object) {
        if ((object !== null && (typeof object === "object" || typeof object === "function"))){
            for (var prop in object) {
                if (object.hasOwnProperty(prop))
                    this[prop] = object[prop];
            }
        }else{
            throw TypeError; //Not an object
        }
    }

Prototyping is a nice way, but prototype is quite dangerous sometimes and can lead to bugs. I prefer to encapsulate this into a base object, like Ember.js does to it's Ember.Object.extend and Ember.Object.reopen. That is much more secure to use.

I created a gist with how you would setup something similar to what Ember.Object uses.

Here's the link: https://gist.github.com/WebCloud/cbfe2d848c80d4b9e9bd

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