Brigo
Brigo

Reputation: 1112

Object.create() not working as expected

I've read some questions here on Stack Overflow, but I couldn't find out what's the problem. Yesterday the user Scott Marcus answered to my question with an explanatory answer, I tested the solution using Object.create() and I decided that was the way I wanted to write my objects.

Here's the working test I did:

var Obj = {
        /**
         * @type {string}
         */
        name: '',

        uppercase: function () {
            this.name = this.name.toUpperCase();
            return true;
        },

        /**
         * setName
         * Set the `name` property.
         *
         * @param param
         */
        set setName (param) {
            this.name = param;
        },

        get getName () {
            return this.name;
        }
    };

    var a = Object.create(Obj);
    var b = Object.create(Obj);
    a.setName = "Will";
    b.setName = "Byers";
    
    console.log("Name in object a is: ".concat(a.getName));
    console.log("Name in object b is: ".concat(b.getName));

I then tried the same solution on my script but Object.create() this time is not creating two separate objects and I can't understand why. If you click on CLICK button, it prints in the console the two object that should be different (object b should contain only `{ asd: "QWE" }) but they're actually the same.

Can anyone explain me what I am doing wrong?

//  Declaring my object
var Field = {
    /**
     * @type {Object}
     */
    collection: {},


    /**
     * collect
     * Push the | indexName: value | into collection object.
     *
     * @param {object} obj
     */
    collect: function (obj) {
        //console.log(obj);
        var indexNames = Object.keys(obj);
        var selectors = Object.values(obj);

        for (var i=0; i<indexNames.length; i++) {
            var el = document.querySelectorAll(selectors[i]);
            this.collection[indexNames[i]] = this.fldValue(el);
        }
    },

    /**
     * fldValue
     * Get the value of the injected object after having recognized its tagName and type.
     *
     * @param {object} HTMLObject
     * @returns {*}
     */
    fldValue: function (HTMLObject) {
        HTMLObject = HTMLObject[0];
        switch (HTMLObject.tagName) {
            case "INPUT":
                switch (HTMLObject.type) {
                    case "text":
                    case "password":
                    case "hidden":
                        return HTMLObject.value;

                    case "checkbox":
                        return HTMLObject.checked;

                    default:
                        throw "Unknown input type: ".concat(HTMLObject.type);
                }

            case "DIV":
            case "P":
            case "SPAN":
            case "H1":
            case "H2":
            case "H3":
            case "H4":
            case "H5":
            case "H6":
                return HTMLObject.textContent;

            default:
                throw "Unknown element tagName: ".concat(HTMLObject.tagName);
        }
    },

    /**
     *
     * @param {string} className
     * @returns {*}
     */
    reset: function (className) {
        var elements = document.getElementsByClassName(className);

        for (var i=0; i<elements.length; i++) {
            var el = elements[i];

            switch (el.tagName) {
                case "INPUT":
                    switch (el.type) {
                        case "text":
                        case "password":
                        case "hidden":
                            el.value = '';
                            break;

                        case "checkbox":
                            el.checked = false;
                            break;

                        default:
                            throw "Unknown input type: ".concat(el.type);
                    }
                    break;

                case "DIV":
                case "P":
                case "SPAN":
                case "H1":
                case "H2":
                case "H3":
                case "H4":
                case "H5":
                case "H6":
                    el.innerHTML = '';
                    break;

                default:
                    throw "Unknown element: ".concat(el.tagName);
            }
        }
    },

    get getCollection() {
        return this.collection;
    }
};


//  --------------------
//  Object instantiation
  var a = Object.create(Field);
  var b = Object.create(Field);
  
          document.getElementById('send')
            .addEventListener('click', function (ev) {
                a.collect({
                    name: '[name="name"]',
                    password: '.password',
                    title: '.title',
                    description: '.container',
                    note: '#paragraph',
                    chk_1: '[name="chk1"]',
                    chk_2: '[name="chk2"]'
                });

                b.collect({
                    asd: '.title'
                });
                
                // !! They should be different but they're actually the same object !!
                console.log(a.getCollection);
                console.log(b.getCollection);
            });

        document.getElementById('reset')
            .addEventListener('click', function (ev) {
                a.reset('reset');
            });
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 class="title">QWE</h1>

<input type="text" class="name reset" name="name">
<input type="password" class="password" name="password">

<input type="checkbox" class="reset" name="chk1">
<input type="checkbox" name="chk2">

<div class="container reset">HEY</div>

<p id="paragraph">OPS</p>

<button id="send">CLICK</button>
<button id="reset">RESET</button>


<script src="Field.js"></script>
<script>

</script>

Upvotes: -1

Views: 78

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1075785

It's because collection is an object, which is referenced by Field, and so a and b inherit the reference to that single object and share it. Initially, after setting up Field, you have something like this in memory (with lots of details omitted):

                                       +−−−−−−−−−−−−−−−−−−−−−+
Field−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−>|       (object)      |
                                       +−−−−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−+
                                       | collection          |−−−>| (object) |
                                       | collect             |−+  +−−−−−−−−−−+
                                       | ...                 | |
                                       +−−−−−−−−−−−−−−−−−−−−−+ |  +−−−−−−−−−−−−+
                                                               +−>| (function) |
                                                                  +−−−−−−−−−−−−+

Then after

var a = Object.create(Field);
var b = Object.create(Field)

you have something like this:

                                       +−−−−−−−−−−−−−−−−−−−−−+                  
Field−−−−−−−−−−−−−−−−−−−−−−−−−−−−+−−+−>|       (object)      |                  
                                 |  |  +−−−−−−−−−−−−−−−−−−−−−+    +−−−−−−−−−−+  
                                 |  |  | collection          |−−−>| (object) |  
                                 |  |  | collect             |−+  +−−−−−−−−−−+  
                                 |  |  | ...                 | |                
                                 |  |  +−−−−−−−−−−−−−−−−−−−−−+ |  +−−−−−−−−−−−−+
              +−−−−−−−−−−−−−−−+  |  |                          +−>| (function) |
a−−−−−−−−−−−−>| (object)      |  |  |                             +−−−−−−−−−−−−+
              +−−−−−−−−−−−−−−−+  |  |
              | [[Prototype]] |−−+  |
              +−−−−−−−−−−−−−−−+     |
                                    |
              +−−−−−−−−−−−−−−−+     |
b−−−−−−−−−−−−>| (object)      |     |
              +−−−−−−−−−−−−−−−+     |
              | [[Prototype]] |−−−−−+
              +−−−−−−−−−−−−−−−+

Notice that a and b inherit collection, which means they refer to the same collection object.

Instead, you need a and b to each have their own collection object:

var a = Object.create(Field);
a.collection = {};
var b = Object.create(Field);
b.collection = {};

//  Declaring my object
var Field = {
    /**
     * @type {Object}
     */
    collection: {},


    /**
     * collect
     * Push the | indexName: value | into collection object.
     *
     * @param {object} obj
     */
    collect: function (obj) {
        //console.log(obj);
        var indexNames = Object.keys(obj);
        var selectors = Object.values(obj);

        for (var i=0; i<indexNames.length; i++) {
            var el = document.querySelectorAll(selectors[i]);
            this.collection[indexNames[i]] = this.fldValue(el);
        }
    },

    /**
     * fldValue
     * Get the value of the injected object after having recognized its tagName and type.
     *
     * @param {object} HTMLObject
     * @returns {*}
     */
    fldValue: function (HTMLObject) {
        HTMLObject = HTMLObject[0];
        switch (HTMLObject.tagName) {
            case "INPUT":
                switch (HTMLObject.type) {
                    case "text":
                    case "password":
                    case "hidden":
                        return HTMLObject.value;

                    case "checkbox":
                        return HTMLObject.checked;

                    default:
                        throw "Unknown input type: ".concat(HTMLObject.type);
                }

            case "DIV":
            case "P":
            case "SPAN":
            case "H1":
            case "H2":
            case "H3":
            case "H4":
            case "H5":
            case "H6":
                return HTMLObject.textContent;

            default:
                throw "Unknown element tagName: ".concat(HTMLObject.tagName);
        }
    },

    /**
     *
     * @param {string} className
     * @returns {*}
     */
    reset: function (className) {
        var elements = document.getElementsByClassName(className);

        for (var i=0; i<elements.length; i++) {
            var el = elements[i];

            switch (el.tagName) {
                case "INPUT":
                    switch (el.type) {
                        case "text":
                        case "password":
                        case "hidden":
                            el.value = '';
                            break;

                        case "checkbox":
                            el.checked = false;
                            break;

                        default:
                            throw "Unknown input type: ".concat(el.type);
                    }
                    break;

                case "DIV":
                case "P":
                case "SPAN":
                case "H1":
                case "H2":
                case "H3":
                case "H4":
                case "H5":
                case "H6":
                    el.innerHTML = '';
                    break;

                default:
                    throw "Unknown element: ".concat(el.tagName);
            }
        }
    },

    get getCollection() {
        return this.collection;
    }
};


//  --------------------
//  Object instantiation
  var a = Object.create(Field);
  a.collection = {};
  var b = Object.create(Field);
  b.collection = {};
  
          document.getElementById('send')
            .addEventListener('click', function (ev) {
                a.collect({
                    name: '[name="name"]',
                    password: '.password',
                    title: '.title',
                    description: '.container',
                    note: '#paragraph',
                    chk_1: '[name="chk1"]',
                    chk_2: '[name="chk2"]'
                });

                b.collect({
                    asd: '.title'
                });
                
                // !! They should be different but they're actually the same object !!
                console.log(a.getCollection);
                console.log(b.getCollection);
            });

        document.getElementById('reset')
            .addEventListener('click', function (ev) {
                a.reset('reset');
            });
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 class="title">QWE</h1>

<input type="text" class="name reset" name="name">
<input type="password" class="password" name="password">

<input type="checkbox" class="reset" name="chk1">
<input type="checkbox" name="chk2">

<div class="container reset">HEY</div>

<p id="paragraph">OPS</p>

<button id="send">CLICK</button>
<button id="reset">RESET</button>


<script src="Field.js"></script>
<script>

</script>

Of course, it's a bit of a pain to have to write the per-instance initialization logic every time you create a Field, which is why we have constructor functions / class syntax, or builder functions if you don't like to use new.

function newField() {
    var f = Object.create(Field);
    f.collection = {};
    return f;
}

then

a = newField();
b = newField();

Upvotes: 3

Related Questions