1

I have a basic wrapper for Object.create to be used for our application. Basically the process is to use Object.create on a parent object, then .extend({}) to add new functionality to it. If we need to call a method on the parent class, we just use ParentClass.myFunc.apply(this, arguments); in the method override. The issue I'm having is when I intialize an object and call the init function of the parent object, it overrides the parent' object's properties. How can I prevent this from happening?

Here's the main Object.create wrapper:

/**
 * Simple wrapper for Object.create and Object.extend
 * functionality.
 */
var My_Object = {
    /**
     * Simple extend function for inheritance
     * @param props An object of functions to place in the new object.
     * @returns {My_Object}
     */
    extend: function(props) {
        for(var prop in props)
            this[prop] = props[prop];

        return this;
    },

    /**
     * Basic function that checks if a function is native or user-created
     * @author Yong (http://stackoverflow.com/users/962254/yong)
     *
     * @param f The function to test if it is native
     * @returns {boolean}
     *
     * @see http://stackoverflow.com/questions/6598945/detect-if-function-is-native-to-browser
     */
    isFuncNative: function(f) {
        return !!f && (typeof f).toLowerCase() == 'function'
            && (f === Function.prototype
            || /^\s*function\s*(\b[a-z$_][a-z0-9$_]*\b)*\s*\((|([a-z$_][a-z0-9$_]*)(\s*,[a-z$_][a-z0-9$_]*)*)\)\s*{\s*\[native code\]\s*}\s*$/i.test(String(f)));
    },


    /**
     * Simple polyfill wrapper for native Object.create. Using a polyfill
     * instead of native incase for some reason consumer would have overwritten
     * it with unexpected usage.
     */
    create: function(obj) {
        // If the browser supports Object.create and hasn't been overwritten,
        // use the native version instead of the polyfill
        if(My_Object.isFuncNative(Object.create)) {
            // If extend hasn't been defined, add it
            if(!obj.hasOwnProperty('extend'))
                obj.extend = My_Object.extend;

            return Object.create(obj);
        }


        // Create empty function for polyfill
        function F(){}
        F.prototype = o;

        // If extend hasn't been defined, add it
        if(!F.prototype.extend)
            F.prototype.extend = My_Object.extend;

        return new F()
    }
};

Then I define a base object:

var SomeParentObject = {
    options: {
        a: 'a',
        b: 'b'
    },

    init: function(options) {
        this.options = $.extend(this.options, options || {});

        return this;
    }
};

And some children object:

var ChildObject1 = My_Object.create(SomeParentObject).extend({
    init: function() {
        SomeParentObject.init.call(this, {b: 'c'});        

        return this;
    }
}).init();

var ChildObject2 = My_Object.create(SomeParentObject).extend({}).init();

Now the desired result is that SomeParentObject's options should never be modified. I can't figure out what exactly is the cause of it, but I'm sure it's something stupid I'm doing. If you do some basic console.logs, you can see that when ChildObject2 has some options to override, it overrides it for SomeParentObject, and therefore for all children.

console.log('<<SomeParentObject.a, SomeParentObject.b>>');
console.log(SomeParentObject.options.a + ', ' + SomeParentObject.options.b);

console.log('<<ChildObject1.a, ChildeObject1.b>>');
console.log(ChildObject1.options.a + ', ' + ChildObject1.options.b);

console.log('<<ChildObject2.a, ChildeObject2.b>>');
console.log(ChildObject2.options.a + ', ' + ChildObject2.options.b);

Which outputs:

<<SomeParentObject.a, SomeParentObject.b>>
a, c
<<ChildObject1.a, ChildeObject1.b>>
a, c
<<ChildObject2.a, ChildeObject2.b>>
a, c 

What should be outputted is this:

<<SomeParentObject.a, SomeParentObject.b>>
a, b
<<ChildObject1.a, ChildeObject1.b>>
a, c
<<ChildObject2.a, ChildeObject2.b>>
a, b 
LordZardeck
  • 7,953
  • 19
  • 62
  • 119
  • Please show an alternative output, which you would consider being correct. – akhikhl Jan 11 '14 at 22:13
  • Right, thank you for reminding me – LordZardeck Jan 11 '14 at 22:19
  • 2
    When I do not use a deep copy in jQuerys "extend" method, like so: this.options = jQuery.extend(false,this.options, options || {}), and then use native Object.create(SomeParentObject); then call init: ChildObject1.init.call(ChildObject1,{b: 'c'}); --> the integrity of the data is intact. returns: Object { a="a", b="c"} Object { a="a", b="b"} ==> so, I think perhaps the issue manifests itself in your custom extend methods. – james emanon Jan 11 '14 at 22:50
  • Wow, didn't realize that you needed to use false. That worked! – LordZardeck Jan 11 '14 at 23:35

1 Answers1

2

If you don't like the way prototyping works in JavaScript in order to achieve a simple way of inheritance and OOP, I'd suggest taking a look at this: https://github.com/haroldiedema/joii

It basically allows you to do the following (and more):

// First (bottom level)
var Person = new Class(function() {
    this.name = "Unknown Person";
});

// Employee, extend on Person & apply the Role property.
var Employee = new Class({ extends: Person }, function() {
    this.name = 'Unknown Employee';
    this.role = 'Employee';

    this.getValue = function() {
        return "Hello World";
    }
});

// 3rd level, extend on Employee. Modify existing properties.
var Manager = new Class({ extends: Employee }, function() {

    // Overwrite the value of 'role'.
    this.role = this.role + ': Manager';

    // Class constructor to apply the given 'name' value.
    this.__construct = function(name) {
        this.name = name;
    }

    // Parent inheritance & override
    this.getValue = function() {
        return this.parent.getValue().toUpperCase();
    }
});

// And to use the final result:
var myManager = new Manager("John Smith");
console.log( myManager.name ); // John Smith
console.log( myManager.role ); // Manager

console.log( myManager.getValue() ); // HELLO WORLD
Harold
  • 1,372
  • 1
  • 14
  • 25