0

I am getting a Maximum call stack size exceeded error whenever I try to use Object.observe to observe changes in an object that I defined properties for through Object.defineProperty.

What is the correct way to get around throwing this error while still being able to use both of these methods?

Note: Object.observe is only available in Chrome and Opera

var TestModule = (function () {
    "use strict";

    function TestClass() {
        this.testValue = 0;
        Object.defineProperty(this, "testValue", {
            get: function () {
                return this.testValue;
            },
            set: function (value) {
                this.testValue = value;
            },
            enumerable: true,
            configurable: false
        });
    }

    return {
        TestClass: TestClass
    };
}());
<!DOCTYPE html>

<head>
    <title>Stack Exceed Test</title>
    <script src="../js/TestModule.js"></script>
</head>

<body>
    <main>
        <div id="logger" role="log"></div>
    </main>
    <script>
        document.addEventListener("DOMContentLoaded", function () {
            var logger = document.getElementById("logger"),
                tc = new TestModule.TestClass();

            function log(message) {
                if (logger) {
                    logger.innerHTML = message;
                } else {
                    console.error(message);
                }
            }

            if (typeof Object.observe === "function") {
                Object.observe(tc, function (changes) {
                    console.log("Change");
                });

                try {
                    tc.testValue = 5;
                } catch (e) {
                    log(e);
                }
            } else {
                log("Object.observe is unsupported in your browser");
            }
        });
    </script>
</body>
zero298
  • 20,481
  • 7
  • 52
  • 83

2 Answers2

1

You are reading and writing to the same variable over and over again in Object.defineProperty...

You should change the name of this.testValue in the first line of TestClass. I would suggest renaming it to this._testValue which is a convention for naming variables to indict they are "private".

Note, you can also keep this.testValue and completely remove the Object.defineProperty... section, because all you're doing is reading and writing the value, which is default.

var TestModule = (function () {
    "use strict";

    function TestClass() {
        this._testValue = 0;
        Object.defineProperty(this, "testValue", {
            get: function () {
                return this._testValue;
            },
            set: function (value) {
                this._testValue = value;
            },
            enumerable: true,
            configurable: false
        });
    }

    return {
        TestClass: TestClass
    };
}());
<!DOCTYPE html>

<head>
    <title>Stack Exceed Test</title>
    <script src="../js/TestModule.js"></script>
</head>

<body>
    <main>
        <div id="logger" role="log"></div>
    </main>
    <script>
        document.addEventListener("DOMContentLoaded", function () {
            var logger = document.getElementById("logger"),
                tc = new TestModule.TestClass();

            function log(message) {
                if (logger) {
                    logger.innerHTML = message;
                } else {
                    console.error(message);
                }
            }

            if (typeof Object.observe === "function") {
                Object.observe(tc, function (changes) {
                    console.log("Change");
                });

                try {
                    tc.testValue = 5;
                } catch (e) {
                    log(e);
                }
            } else {
                log("Object.observe is unsupported in your browser");
            }
        });
    </script>
</body>
tcigrand
  • 2,220
  • 2
  • 11
  • 23
  • That fixes the exception, but I don't believe the `observe` callback is being triggered. – zero298 Sep 15 '15 at 18:06
  • It is, you can see "Change" getting printed in the dev tools console. You can change `console.log` to `alert` if you'd rather do that. – tcigrand Sep 15 '15 at 18:09
  • I understand your note about not needing the `defineProperty`, but I didn't want to include a getter with side effects in the snippet. I see that this is printing the "Change" in console. However, I'm now trying this same test in `node.js` and I'm not getting a "Change" there which is strange. – zero298 Sep 15 '15 at 18:18
  • I just tried it in node and "Change" printed to the console for me. – tcigrand Sep 15 '15 at 18:23
  • Got it, forgot a `this`. Thanks. – zero298 Sep 15 '15 at 18:31
  • Actually I found a way to get around needing to use the wrapping method using `Object.getNotifier()`. See my answer. – zero298 Sep 15 '15 at 19:46
0

Another way to solve this issue, if you don't want to sort of "wrap" your value with indirection, is to use Object.getNotifier() which allows you to emit notifications manually and keep a local variable that isn't a member of your object.

If you use the notifier you can get around having to have object properties that won't actually be used. If you use the wrapping method, you will have both _testValue and testValue on the object. Using the notifier, you will only have testValue.

Consider the code change:

function TestClass() {
    var testValue, notifier;
    /* 
     * The new locally scoped varible which will
     * be captured by the getter/setter closure
     */
    testValue = 0;

    /*
     * Create a notifier for the object
     * which we can use to trigger 
     * Object.observe events manually
     */
    notifier = Object.getNotifier(this);

    Object.defineProperty(this, "testValue", {
        get: function () {
            return testValue;
        },
        set: function (value) {
            /*
             * Use the notifier to trigger 
             * the observe()
             */
            notifier.notify({
                type: "update",
                name: "testValue",
                oldValue: testValue
            });
            testValue = value;
        },
        enumerable: true,
        configurable: false
    });
}
zero298
  • 20,481
  • 7
  • 52
  • 83