Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternative methods for extending object.prototype when using jQuery

Some time ago I tried to extend Object.prototype... I was surprised when later I saw errors in the console which comes from jQuery file. I tried to figured out what is wrong and of course I found information that extending Object.prototype is a "evil", "you shouldn't do that because JS is dynamic language and your code will not work soon" and information that jQuery will now add hasOwnProperty method to their for in loops.

Because I didn't want to leave jQuery, I drop the idea about extending Object.prototype.

Till now. My project getting bigger and I am really annoyed because I have to repeat many times some parts of the code. Below is a bit of the structure which I am using in my projects:

charts.js:

CHARTS = {
    _init: function () {
        this.monthlyChart();
        /*
         * 
         * more propertys goes here
         * 
         */
        return this;
    },
    monthlyChart: function () {
        //create my chart
        return {
            update: function () {
               // update chart
            }
        };
    }()
    /*
     * 
     * more propertys goes here
     * 
     */
}._init;

dashboard.js

NAVBAR = {
    _init: function () {
        /*
         * 
         * more propertys goes here
         * 
         */
        return this;
    },
    doSomething: function(){            
        $(document).ready(function(){
            $('.myButton').on('click', function(){
                var data = [];
                // calling property from charts.js
                CHARTS.monthlyChart.update(data);
            });
        });
    }
}._init

As I mentioned project is really big now - it's over 40 js files and some of them has a few thousands line of code. It is really annoying that I have to repeat _init section every time, as well as I many functions I have to repeat $(document).ready && $(window).load.

I tried to find another solution for my problem. I tried to create class with init property (more you can find here) but I this solution forced me to add another "unnecessary" piece of the code to every file and accessing other file object property makes it to complicated too (return proper objects everywhere etc). As advised in the comment I started reading about getters and setters in JS.

After all I created something like that:

 //Auto initialization
if (typeof $document === 'undefined') {
    var $document = $(document),
            $window = $(window),
            $body = $('body');
}

Object.defineProperty(Object.prototype, '_init', {
    get: function () {
        // if object has no property named `_init`
        if (!this.hasOwnProperty('_init')) {
            for (var key in this) {
                // checking if name of property does starts from '_' and if it is function
                if (this.hasOwnProperty(key) && key[0] === '_' && typeof this[key] === 'function') {                    
                    if (key.indexOf('_ready_') > -1) {
                        //add function to document ready if property name starts from '_ready_'
                        $document.ready(this[key].bind(this));
                    } else if (key.indexOf('_load_') > -1) {
                        //add function to window load if property name starts from '_load_'
                        $window.load(this[key].bind(this));
                    } else {
                        // else execute function now
                        this[key].bind(this)();
                    }
                }
            }
            return this;
        }
    }
});

and my object:

var DASHBOARD = {
    _runMe: function(){

    },
    _ready_runMeOnReady: function(){

    },
    _load_runMeOnLoad: function(){

    },
    iAmAString: ''
}._init

It seems that this solution works with jQuery. But is it safe to use? I don't see any problem the code can cause and I don't see any further problems that it may cause. I will be really happy if somebody will tell me why I shouldn't use this solution.

Also I'm trying to understand how it works in details. Theoretically I defined property for the Object.prototype by defineProperty, without assigning value to it. Somehow it doesn't cause any errors in jQuery fore in loop, why? Does that mean that property _init is not defined at some point or at all because I am defined only getter of it?

Any help will be appreciated :)

like image 253
LJ Wadowski Avatar asked Mar 17 '23 01:03

LJ Wadowski


2 Answers

By not including the descriptor in Object.defineProperty(obj, prop, descriptor) JavaScript defaults all the Boolean descriptor attributes to false. Namely writable, enumerable, and configurable. Your new property is hidden from the for in iterators because your _init property is enumerable:false.

I am not a fan of JQuery so will not comment on why in regard to JQuery

There is no absolute rule to adding properties to JavaScript's basic type and will depend on the environment that your code is running. Adding to the basic type will add it to the global namespace. If your application is sharing the namespace with 3rd party scripts you can potentially get conflicts, causing your code or the third party code or both to fail.

If you are the only code then conflicts will not be an issues, but adding to object.prototype will incur an addition overhead on all code that uses object.

I would strongly suggest that you re examine the need for a global _init. Surely you don't use it every time you need a new object. I am a fan of the add hock approach to JavaScript data structures and try to keep away from the formal OOP paradigms

like image 152
Blindman67 Avatar answered Mar 21 '23 17:03

Blindman67


Your question in fact contains two questions.

It seams that this solution works with jQuery. But is it safe to use? I don't see any problem the code can cause and I don't see any further problems that it may cause. I will be really happy if somebody will tell me why I shouldn't use this solution.

First of all, there are three main reasons to avoid modification of built-in prototypes.

For-in loops

There is too much code using for-in loop without hasOwnProperty check. In your case that is jQuery code that does not perform check.

Solutions

  • Don't use for-in loop without .hasOwnProperty check.
    • Doesn't apply in this case because it's third-party code and you can't modify it.
  • for-in loop traverses only enumerable keys.
    • You have used that solution. Object.defineProperty creates non-enumerable properties by default (ECMAScript 5.1 specification)
      • Not supported by IE8.

Conflicts

There is risk of property name. Imagine that you use jQuery plugin that checks for existence of ._init property on objects - and it can lead to subtle and hard to debug bugs. Names prefixed with underscore are widely used in modern JavaScript libraries for indicating private properties.

Encapsulation violation (bad design)

But you have worser problem. Definining global ._init property suggests that every object have universal initialization logic. It breaks encapsulation, because your objects don't have full control over their state.

You can't rely on presence of _init method due to this. Your coworkers can't implement their own class with

Alternative designs

Global initializer

You can create global function initialize and wrap all your objects that require initialization in it.

Decouple view and logic

Your objects should not merge logic and view in one object (it violates single responsibility principle) and you are victim of spaghetti code. Moreover - object initialization should not bind it to DOM, some controller objects should be a proxy between your logic and display.

It can be good idea to inspect how popular client-side MVC frameworks have solved this problem (Angular, Ember, Backbone) have solved this problem.

like image 43
Ginden Avatar answered Mar 21 '23 17:03

Ginden