4
\$\begingroup\$

For the sake of learning JavaScript better and getting used to the Google Chrome Extension API, I'm currently writing a little Chrome extension.

To keep things simple and being able to make use of prototypal inheritance, I decided to write a little instantiation/inheritance helper before I get started.

But as I am still deep in the process of learning JavaScript, I would greatly appreciate if anyone could take a quick look at the code and clarify some points:

  1. Are there any pitfalls I could run into with some of the approaches?
  2. Is the code, as-is, or parts of it considered bad practice some weird constructions or similar?
  3. Did I miss some important aspects regarding inheritance itself?

/* Inheritance Helper*/
var base = (function baseConstructor() {
    'use strict';
    var obj = {
        create: function instantiation() {
            if (this === base) {
                throw new SyntaxError("You can't create instances of base");
            } else if (!this.hasOwnProperty("initclosure")) {
                throw new SyntaxError("Cannot create instances without an constructor");
            } else if (this.singleton && this.instances.length !== 0) {
                throw new SyntaxError("You can't create more than one Instance of a Singleton Class");
            } else {
                var instance = Object.create(this.pub);
                this.init.apply(instance, arguments);
                this.instances.push(instance);
                return instance;
            }
        },
        inherit: function inheritation(specsOpt) {
            specsOpt = specsOpt || {};
            applyDefaults(specsOpt, {
                singleton: false,
                anonymous: false
            });
            var sub = Object.create(this);
            sub.pub = Object.create(this.pub);
            sub.instances = [];
            sub.anonymous = specsOpt.anonymous;
            sub.sup = this;

            if (specsOpt.singleton) {
                sub.singleton = specsOpt.singleton;
                sub.getSingleton = getSingleton;
                protect.call(sub, {
                    singleton: {
                        writable: false,
                        configurable: false,
                        enumerable: false
                    },
                    getSingleton: {
                        writable: false,
                        configurable: false
                    }
                });

            }

            return sub;
        },
        initclosure: function Base() {},
        instances: [],
        pub: {
            instanceOf: function (obj) {
                if (!obj) return this.className;
                return obj.pub.isPrototypeOf(this);
            }
        }

    };

    /* Helper Functions. --- Use function expressions instead of declarations to get JSHint/Lint strict mode violations
     *
     * TODO: Maybe add an obj.helper Propertie with usefull functions
     */

    var applyDefaults = function (target, obj) {
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                target[prop] = target[prop] || obj[prop];
            }
        }
    };

    var getSingleton = function () { //To get past the strict violation
        return this.instances[0];
    };
    var protect = function (props, desc) { //Maybe change it a little
        for (var prop in props) {
            if (props.hasOwnProperty) {
                Object.defineProperty(this, prop, props[prop] || desc);
            }
        }
        return this;
    };

    /*  End Helpers
     * 
     *  Protecting
     */

    Object.defineProperty(obj, "init", {
        set: function (fn) {
            if (typeof fn !== "function") {
                throw new Error("Expected typeof init to be 'function'");
            } else if (Boolean(fn.name) === this.anonymous) {
                throw new Error("Expected the constructor " + (!this.anonymous ? "not " : "") + "to be Anonymous");
            }
            if (!this.hasOwnProperty("initclosure")) {
                this.initclosure = fn;
                this.pub.constructor = this.init;
                this.pub.className = fn.name;
                protect.call(this.pub, {
                    constructor: false,
                    className: false
                }, {
                    enumerable: false
                });
            }
        },
        get: function () {
            var that = this;
            var init = function init() {
                if (that.pub.isPrototypeOf(this)) {
                    that.initclosure.apply(this, arguments);
                } else {
                    throw new Error("init can't be called directly");
                }
            };

            init.toString = function () {
                return that.initclosure.toString();
            };
            return init;
        }

    });

    obj.toString = function () {
        return "[class " + (this.initclosure.name || "Class") + "]";
    };

    obj.pub.toString = function () {
        return "[instance " + (this.className || "Anonymous") + "]";
    };

    protect.call(obj, {
        create: false,
        inherit: false,
        toString: false,
        initclosure: {
            enumerable: false
        }
    }, {
        writable: false,
        configurable: false
    });
    protect.call(obj.pub, {
        instanceOf: false,
        toString: false
    }, {
        writable: false,
        configurable: false,
        enumerable: false
    });


    return obj;
})();

Here are some example console outputs as well as a JSBin.

var Test = base.inherit();
Test.create(); //Uncaught SyntaxError: Cannot create instances without an constructor



var Test2 = base.inherit();
Test2.init = function () { //Uncaught Error: Expected the constructor not to be Anonymous
  this.is = "a test";
};




var Test3 = base.inherit({singleton:true});
Test3.init = function Test() { 
  this.is = "a test";
};
var instance1 = Test3.create();
console.log(instance1); //{is: "a test"}
console.log(instance1.instanceOf()); //Test
console.log(instance1.instanceOf(Test3)); //true
alert(instance1); //[instance Test]
var instance2 = Test3.create(); //Uncaught SyntaxError: You can't create more than one Instance of a Singleton Class
var instance2 = new Test3.init(); //Uncaught Error: init can't be called directly

Here are a few more examples:

var Test4 = base.inherit();

Test4.init = function testingErrorStack(x) {
    this.prop = x.error;
};

var instance1 = Test4.create({
    error: "none"
});
var instance2 = Test4.create({
    error: "alsonone"
});
try {
    var errorInstance = Test4.create();
} catch (e) {
    console.dir(e.stack);
    /*"TypeError: Cannot read property 'error' of undefined
    at init.testingErrorStack */
}

console.log(instance1, instance2, errorInstance); //{"prop": "none"},{"prop": "alsonone"},undefined

You can see the constructor name in the errors stackTrace. And you can of course create multiple instances, as well as doing multiple inheritance and anonymous constructors.

var Test5 = base.inherit({
    anonymous: true
});
Test5.init = function (anotherProp, to) {
    this.anotherProp = anotherProp;
    this.from = "an";
    this.to = to;
};

var Test5_1 = Test5.inherit();
Test5_1.init = function test5SubClass(someProp) {
    Test5_1.sup.init.call(this, "has been passed", "a super class");
    this.sub = someProp;
};

var instance5 = Test5_1.create('"Class"');

console.log(instance5); //{"anotherProp": "has been passed", "from": "an", "sub": "\"Class\"", "to": "a super class"}

I thought about adding these as I got the feeling you might have misunderstood the purpose as an singleton pattern only helper.

Isn't this still just a prototypal approach? I just thought it would be nice to have the benefits of proper prototypal inheritance through Object.create along with "constructors" to properly initialize the Objects.

Here's a fiddle.

\$\endgroup\$

1 Answer 1

2
+50
\$\begingroup\$

Did I miss some important aspects regarding inheritance itself

JavaScript uses prototypal inheritance, which is already quite simple. Here's an example:

function Animal() {
    this.name = 'Animal';
}

Animal.prototype.speak = function() {
    console.log('My name is ' + this.name);
};

var animal = new Animal();
animal.speak(); // My name is Animal

function Cat() {
    this.name = 'Cat';
}

Cat.prototype = new Animal();

var cat = new Cat();
cat.speak(); // My name is Cat

Are there any Pitfalls I could run into with some of the appraches

This whole approach feels quite complicated, and I'm not sure that I understand the benefits of using this over prototypal inheritance. My advice would be to stick with existing patterns which are widely used and understood (e.g. this Singleton pattern).

Is the code, as is, or parts of it considered bad practice some weird constructions or similar

Aside from feeling a little over-engineered, the only other feedback I have is that you don't need to name functions that are assigned to a variable. You could change

var obj = {
    create: function instantiation() {
    } 
};

To

var obj = {
    create: function() {
    } 
};
\$\endgroup\$
8
  • \$\begingroup\$ The problem with this Approach is that you have to set the prototype of the constructor to an instance of another constructor which could lead to Problems, when initializing more complex constructors which require more parameters to get initialized correctly. You could of course use some intermediate constructors like described e.g in (this)[3site.eu/doc/#intermediate] article, but thats not what makes my heart goes boom. \$\endgroup\$
    – C5H8NNaO4
    Feb 13, 2013 at 14:52
  • 1
    \$\begingroup\$ And using named function expression gives you a huge benefit in debugging you applications as the stacktrace can provide you these names in case of errors, another good article to read btw =) Also does the Singleton pattern in the article you linked to use arguments.callee which is forbidden in ES5s strict mode. \$\endgroup\$
    – C5H8NNaO4
    Feb 13, 2013 at 15:06
  • \$\begingroup\$ Many thanks for you answer and efforts btw, i really appreciate that =) \$\endgroup\$
    – C5H8NNaO4
    Feb 13, 2013 at 15:26
  • \$\begingroup\$ I understand what you're saying, but it still seems to me like you're trying to bend and twist JavaScript into having classical inheritance. Once you "get" prototypal inheritance, you'll see that you can write much more simple code without the need for this sort of helper. If you do prefer classical inheritance though, you might want to check out CoffeeScript. \$\endgroup\$ Feb 13, 2013 at 18:33
  • \$\begingroup\$ =) It may be a kind of bending javascript. But to be honest, i don't really have an idea of what classical inheritance really is, i just started programming about 8 Month ago, and 90% of what i did until now is learning javascript through trial/error a little bit of reading articles and answering questions on SO I didn't knew the Answer to. The problem i had with the before ES5 prototypal inheritance, was the above mentioned, and Object.create alone couldn't construct Objects, so i started to kind of mix it. I wrote sth similar 8 month ago, and was being helped on SO. (many thx @Bergi) \$\endgroup\$
    – C5H8NNaO4
    Feb 13, 2013 at 19:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.