Tuesday, January 24, 2017

Javascript Objects - The Truth


After building a project that attempted to use an object oriented way with Javascript I found that the rules of prototyping in Javascript were very confusing.  After reading several articles that weren't really helping I found one that mentioned all the "rubbish" in articles he had read.  That led me to an experiment to get to the truth of the matter - code!
So here is the code.  Run this, observe its assumptions, and I think you will soon be on your way to really understanding Javascript objects, inheritance and prototypes.
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Object Test for Browsers</title>
<style type="text/css">
  body {
        text-align:center;
    }
</style>
<script type="text/javascript">
/*
    objectTest.html
*/
Object.prototype.deepCopyValues = function() {
    // returns a deep copy of this without function memebers - quicker
    return JSON.parse(JSON.stringify(this));
}

Object.prototype.deepCopy = function() {
    // returns a deep copy of this
    function deepCopyFunctions(org, copy) {
        if (org == null) {
            return null;
        }
        var keys = Object.keys(org);
        for (var i = 0; i < keys.length; i++) {
            var key = keys[i];
            var o = org[key];
            if (typeof(o) == 'function') {
                copy[key] = o;
            } else if (typeof(o) == 'object') {
                copy[key] = deepCopyFunctions(o, copy[key]);
            }
        }
        return copy;
    }
    var copy = this.deepCopyValues();
    return deepCopyFunctions(this, copy);
}
function run() {
    var o = {
        a : 1,
        d : 'string'
    };
    var o2 = o.deepCopy();
    var o3 = o;
    if (o !== o3) {
        debugger;
        return 'An object is equal to (===) a reference of itself';
    }
    if (o === o2) {
        debugger;
        return 'An object is not equal to (!==) a deep copy of itself';
    }
    if (o == o2) {
        debugger;
        return 'An object is not equal to (!=) a deep copy of itself';
    }
    o.a = 2;
    if (o2.a == 2) {
        debugger;
        return 'Changing a member of an object does not affect a deep copy of itself';
    }
    o3 = { a:5 };
    if (o.a == 5) {
        debugger;
        return 'Changing an object reference to a different object does not change the original object.';
    }
    if (o.__proto__ != ({}).__proto__) {
        debugger;
        return 'An object that is not created with new has an empty prototype.';
    }
    if (o2.__proto__ != ({}).__proto__) {
        debugger;
        return 'A deep copy of an object not created with new has a null prototype.';
    }
    function fnConstructorA(){
        this.a = 1;
        this.d = 'string';
    };
    try {
        fnConstructorA();
    } catch (e) {
        if (e.message != 'Cannot set property \'a\' of undefined' && // Chrome
            e.message != 'this is undefined') { // Firefox
            //debugger;
            return 'Calling a proper constructor without new makes the \'this\' variable in the constructor undefined. message=' + e.message;
        }
    }
    var A = new fnConstructorA();
    var A2 = A.deepCopy();
    var A3 = A;
    if ((A.__proto__).__proto__ !== ({}).__proto__) {
        debugger;
        return 'A new created object who\'s constructor has no prototype has the default Object prototype.';
    }
    if (A.constructor !== fnConstructorA) {
        debugger;
        return 'a new created object\'s constructor is the function that created the object.';
    }
    if (A !== A3) {
        debugger;
        return 'An new created object is equal (===) to a reference of itself';
    }
    if (A === A2) {
        debugger;
        return 'A new created object is not equal (===) to a copy of itself';
    }
    if (A != A3) {
        debugger;
        return 'An new created object is equal (==) to a reference of itself';
    }
    if (A == A2) {
        debugger;
        return 'A new created object is not equal (!=) to a copy of itself';
    }
    function fConstructorB(o) {    // like Object.create()
        this.__proto__ = o;
        this.a = 5;
        this.data = 'that string';
    }
    var B = new fConstructorB(A);
    if (B.__proto__ !== A) {
        debugger;
        return 'A proper Object.create() call yields an object with a prototype that equals the object given to the function.';
    }
    if (B.a == A.a) {
        debugger;
        return 'Values in a child object overshadow the same value in its prototype.';
    }
    if (B.d != 'string') {
        debugger;
        return 'Values in a prototype are visible from a child object if that value is not defined in the parent object.';
    }
    B.a = 14;
    if (B.a != 14) {
        debugger;
        return 'A value in a child object can be changed.';
    }
    if (A.a != 1) {
        debugger;
        return 'A value in a prototype of an object is not affected if a vaule in a child object is changed.';
    }
    B.__proto__.e = 'new string';
    if (B.e != 'new string') {
        debugger;
        return 'Changing a prototype\'s value within a child object affects the child objects same value if not defined in the child object.';
    }
    A.f = 'proto changed';
    var C = new fConstructorB(A);
    if (B.f != 'proto changed') {
        debugger;
        return 'Changing a prototype object value affects any child objects of it that don\'t have that value defined.';
    }
    if (C.f != 'proto changed') {
        debugger;
        return 'Changing a prototype object value affects any child objects of it that don\'t have that value defined.';
    }
    return 'SUCCESS!!!!';
}
function runTests() {
    var str = run();
    document.body.innerHTML = str;
}
</script>
    </head>
    <body onload="runTests();">
      Testing...
    </body>
  </html>

No comments:

Post a Comment