Indie Dev

Hello Guest!. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, sell your games, upload content, as well as connect with other members through your own private inbox!

Let's share some Javascript ES5 coding mistakes you know

DoubleX

Adventurer
Xy$
1.65
This topic aims to incite you to share as many javascript ES5 coding mistakes as you know, in order to help all of us learn from those shared mistakes, or simply make fun of them :D
Let's start by sharing mine:

Not knowing Javascript ES5 variables are references
Example - Treating this:
JavaScript:
// array1 and array2 refers to 2 different empty arrays
array1 = [];
array2 = [];
As the same as this:
JavaScript:
array1 = array2 = []; // Both array1 and array2 refer to the same empty array
However, this:
JavaScript:
// Both array2 and array3 refer to what array1 currently refers to
array2 = array1;
array3 = array1;
Is the same as this:
JavaScript:
array2 = array3 = array1; // Both array2 and array3 refer to what array1 currently refers to
Application - Thinking that this method's referentially transparent:
JavaScript:
// This function actually isn't side effect free
sideEffectFreeFunction = function(array, val)
     var tempArray = array; // The passed argument array will be modified due to sort
     tempArray.sort(function(a, b) { return a.someProp - b.someProp; });
     for (var index = 0, length = tempArray.length; index < length; index++) {
         if (tempArray[index].someProp > val) { return tempArray[index]; }
     }
     return null;
} // sideEffectFreeFunction
When a truly side effect free version should be something this :
JavaScript:
// This function's really side effect free
sideEffectFreeFunction = function(array, val)
     // tempArray is a new array instead of referring to what array currently refers to
     var tempArray = array.slice(0);
     //
     tempArray.sort(function(a, b) { return a.someProp - b.someProp; });
     for (var index = 0, length = tempArray.length; index < length; index++) {
         if (tempArray[index].someProp > val) { return tempArray[index]; }
     }
     return null;
} // sideEffectFreeFunction

Not knowing Javascript ES5 uses short circuit evaluation
Example - Treating this:
JavaScript:
// expr1 will always be run but expr2 will only be run if expr1 is falsy
if (expr1 || expr2) { expr3; }
//
As the same as this:
JavaScript:
// expr2 will always be run but expr1 will only be run if expr1 is falsy
if (expr2 || expr1) { expr3; }
//
Application - Writing buggy codes like this:
JavaScript:
// exprThatShallAlwaysBeRun won't run if this._var1 is already truthy
this._var1 = this._var1 || exprThatShallAlwaysBeRun;
//
When it should be something like this:
JavaScript:
// Ensures exprThatShallAlwaysBeRun will indeed always be run
var1 = exprThatShallAlwaysBeRun;
this._var1 = this._var1 || var1;
//

Not knowing Javascript ES5 only has global and function scopes(corresponding to global and local variables)
Example - Treating this:
JavaScript:
someFunction = function(target) {
     var index = someOtherFunction(target);
     for (var index = 0; index < target; index++) {
         yetSomeOtherFunction(index, target);
     }
     // index is now target - 1 instead of someOtherFunction(target)
     target = someOtherFunctionAgain();
     yetSomeOtherFunctionAgain(index, target);
} // someFunction
As the same as this:
JavaScript:
someFunction = function(target) {
     var index = someOtherFunction(target);
     for (var i = 0; index < target; index++) {
         yetSomeOtherFunction(i, target);
     }
     target = someOtherFunctionAgain();
     yetSomeOtherFunctionAgain(index, target);
} // someFunction

Not knowing return only works for the innermost function
Example - Treating this:
JavaScript:
someFunction = function(array, val)
     var tempArray = array.slice(0);
     tempArray.sort(function(a, b) { return a.someProp - b.someProp; });
     for (var index = 0, length = tempArray.length; index < length; index++) {
         if (tempArray[index].someProp > val) { return tempArray[index]; }
     }
     return null;
} // someFunction
As the same as this:
JavaScript:
someFunction = function(array, val)
     var tempArray = array.slice(0);
     tempArray.sort(function(a, b) { return a.someProp - b.someProp; });
     tempArray.forEach(function(element) { // return in forEach is the same as continue in for
         if (element.someProp > val) { return element; }
     });
     return null;
} // someFunction

Adding properties to Object.prototype without making them not enumerable
Using an old version of DoubleX RMMV Object Properties as an example:
JavaScript:
/* cond: The function checking whether an object property will be traced
   label: The function returning the label of an traced object property */
Object.prototype.trace_obj_prop = function(cond, label) { // New
    if (!this._obj_prop_trace) {
        this._obj_prop_log = {};
        this._obj_prop_trace = {};
    }
    // Stop tracing the object property if the path would be cyclic
    if (this._obj_prop_trace[cond]) {
        return;
    }
    //
    this._obj_prop_log[cond] = "";
    this._obj_prop_trace[cond] = {};
    this.log_obj_prop(cond, label);
}; // Object.prototype.trace_obj_prop

/* cond: The function checking whether an object property will be traced
   label: The function returning the label of an traced object property */
Object.prototype.log_obj_prop = function(cond, label) { // New
    // Checks if the currently traced object property has object properties
    var has_obj_prop = false;
    for (var prop in this) {
        if (this.hasOwnProperty(prop) && this.is_obj_prop(prop)) {
            has_obj_prop = true;
            break;
        }
    }
    //
    if (has_obj_prop) {
        this._obj_prop_log[cond] = "{";
        this.traverse_obj_prop_tree(cond, label);
        this._obj_prop_log[cond] += "}";
    }
}; // Object.prototype.log_obj_prop

/*----------------------------------------------------------------------------
*    Label and use all nonempty subtrees to form the object property tree   
*----------------------------------------------------------------------------*/
/* cond: The function checking whether an object property will be traced
   label: The function returning the label of an traced object property */
Object.prototype.traverse_obj_prop_tree = function(cond, label) { // New
    var op = DoubleX_RMMV.Obj_Prop;
    for (var prop in this) {
        if (this.hasOwnProperty(prop) && this.is_obj_prop(prop)) {
            var obj = this[prop];
            // Recursively traverse the property tree via Depth First Search
            if (op[cond](obj)) {
                this._obj_prop_log[cond] += " " + prop + ": " +
                                            op[label](obj) + " ";
                this._obj_prop_trace[cond][obj] = [prop];
            }
            var temp_prop = prop;
            if (obj !== null && typeof obj === "object") {
                obj.trace_obj_prop(cond, label);
                if (Object.keys(obj._obj_prop_trace[cond]).length > 0) {
                    if (this._obj_prop_trace[cond][obj] === undefined) {
                        this._obj_prop_trace[cond][obj] = [];
                    }
                    this._obj_prop_log[cond] += " " + temp_prop + ": " +
                                                obj._obj_prop_log[cond];
                    this._obj_prop_trace[cond][obj].push(
                    obj._obj_prop_trace[cond]);
                }
            }
            //
        }
    }
}; // Object.prototype.traverse_obj_prop_tree

// prop: The current object property to be traced
Object.prototype.is_obj_prop = function(prop) { // New
    // Return false for all object properties added by this plugin
    if (prop === "_obj_prop_log" || prop === "_obj_prop_trace") {
        return false;
    }
    if (prop === "trace_obj_prop" || prop === "log_obj_prop") {
        return false;
    }
    return prop !== "traverse_obj_prop_tree" && prop !== "is_obj_prop";
    //
}; // Object.prototype.is_obj_prop
The above code itself seems to have no bugs in isolation, but all objects using for in loop will have troubles. It's because all the above new Object.prototype properties are enumerable.
For instance, adding the above code will cause RMMV to crash upon game start due to this:
JavaScript:
ImageManager.isReady = function() {
     for (var key in this._cache) {
         var bitmap = this._cache[key];
         if (bitmap.isError()) {
             throw new Error('Failed to load: ' + bitmap.url);
         }
         if (!bitmap.isReady()) {
             return false;
         }
     }
     return true;
};
Now for (var key in this._cache) will also enumerate all the new Object.prototype properties added by the old version of DoubleX RMMV Object Properties, as they're enumerable. This results in crashes when running bitmap.isError().

To fix that, DoubleX RMMV Object Properties can be updated into this:
JavaScript:
Object.defineProperties(Object.prototype, {

    "trace_obj_prop": {
        /* cond: The function checking whether an object property will be traced
         * label: The function returning the label of an traced object property
         */
        value: function(cond, label) { // New
            if (!this._obj_prop_trace) {
                this._obj_prop_log = {};
                this._obj_prop_trace = {};
            }
            // Stop tracing the object property if the path would be cyclic
            if (this._obj_prop_trace[cond]) { return; }
            //
            this._obj_prop_log[cond] = "";
            this._obj_prop_trace[cond] = {};
            this.log_obj_prop(cond, label);
        }, // Object.prototype.trace_obj_prop
        writable: true,
        configurable: true
    },

    "log_obj_prop": {
        /* cond: The function checking whether an object property will be traced
         * label: The function returning the label of an traced object property
         */
        value: function(cond, label) { // New
            // Checks if currently traced object property has object properties
            var has_obj_prop = false;
            for (var prop in this) {
                if (this.hasOwnProperty(prop) && this.is_obj_prop(prop)) {
                    has_obj_prop = true;
                    break;
                }
            }
            //
            if (!has_obj_prop) { return; }
            this._obj_prop_log[cond] = "{";
            this.traverse_obj_prop_tree(cond, label);
            this._obj_prop_log[cond] += "}";
        }, // Object.prototype.log_obj_prop
        writable: true,
        configurable: true
    },

    /*------------------------------------------------------------------------
     *    Label and use all nonempty subtrees to form the object property tree
     *------------------------------------------------------------------------*/
    "traverse_obj_prop_tree": {
        /* cond: The function checking whether an object property will be traced
         * label: The function returning the label of an traced object property
         */
        value: function(cond, label) { // New
            var op = DoubleX_RMMV.Obj_Prop;
            for (var prop in this) {
                if (this.hasOwnProperty(prop) && this.is_obj_prop(prop)) {
                    var obj = this[prop];
                    // Recursively traverse property tree via Depth First Search
                    if (op[cond](obj)) {
                        this._obj_prop_log[cond] += " " + prop + ": " +
                        op[label](obj) + " ";
                        this._obj_prop_trace[cond][obj] = [prop];
                    }
                    var temp_prop = prop;
                    if (obj === null || typeof obj !== "object") { continue; }
                    obj.trace_obj_prop(cond, label);
                    if (Object.keys(obj._obj_prop_trace[cond]).length > 0) {
                        if (this._obj_prop_trace[cond][obj] === undefined) {
                            this._obj_prop_trace[cond][obj] = [];
                        }
                        this._obj_prop_log[cond] += " " + temp_prop + ": " +
                        obj._obj_prop_log[cond];
                        this._obj_prop_trace[cond][obj].push(
                        obj._obj_prop_trace[cond]);
                    }
                    //
                }
            }
        }, // Object.prototype.traverse_obj_prop_tree
        writable: true,
        configurable: true
    },

    "is_obj_prop": {
        // prop: The current object property to be traced
        value: function(prop) { // New
            // Return false for all object properties added by this plugin
            if (prop === "_obj_prop_log" || prop === "_obj_prop_trace") {
                return false;
            } else if (prop === "trace_obj_prop" || prop === "log_obj_prop") {
                return false;
            }
            return prop !== "traverse_obj_prop_tree" && prop !== "is_obj_prop";
            //
        }, // Object.prototype.is_obj_prop
        writable: true,
        configurable: true
    }

});
It works as properties defined via Object.defineProperties aren't enumerable by default, thus making all the new Object.prototype properties not enumerable.

On a side note: Even just adding something to Object.prototype itself is already playing with fire. It should be the last resort and done with extreme caution. The above mistake's just 1 of the many possible such dangers due to insufficient carefulness.

I'll continue to share more and more later. Let's share Javascript ES5 coding mistakes you know as well :)
 
Top