supersuraccoon
supersuraccoon

Reputation: 1681

Object.defineProperty get set return wrong value

Say I have an object instance like this :

var objectA = {"a": 1, "b": 2, "c" : 3};

and in my code I access the property like this:

cc.log(objectA.a); // output 1

now I want to add a get/set for this object to provide some simple encrypt/decrypt feature:

hookSetGet: function (someObject) {
    for (var key in someObject) {
        cc.log("key: " + key);

        // store the origin value before Object.defineProperty
        var pureValue = someObject[key];

        // add a property to store the encrypted value
        var hiddenValueKey = "__" + key;
        someObject[hiddenValueKey] = undefined;

        Object.defineProperty (
            someObject,
            key, 
            {
                set: function (val) {
                    // simulate encrypt
                    this.hiddenValueKey = val + 1;
                    cc.log("hooked set: " + val + " - " + this.hiddenValueKey);
                },
                get: function () {
                    cc.log("hooked get: " + this.hiddenValueKey + " - " + (this.hiddenValueKey - 1));
                    // simulate decrypt
                    return this.hiddenValueKey - 1;
                }
            }
        );

        // trigger set to encrypt
        someObject[key] = pureValue;
    }
}

but when I test the function like this:

var objectA = {"a": 1, "b": 2, "c" : 3};
this.hookSetGet(objectA);

cc.log(objectA.a);
cc.log(objectA.b);
cc.log(objectA.c);

I do not get the result I want :

key: a
hooked set: 1 - 2
key: b
hooked set: 2 - 3
key: c
hooked set: 3 - 4

hooked get: 4 - 3
3
hooked get: 4 - 3
3
hooked get: 4 - 3
3

It seems like even when I call

objectA.a

I will get the value of

objectA.c

The problem seems quite simple but I just can not figure out where is wrong.

Any suggestion will be appreciated, thanks :)

UPDATE:

I tried the following code without change the code of hookSetGet :

cc.log(objectA.__a);
cc.log(objectA.__b);
cc.log(objectA.__c);

and get:

undefined
undefined
undefined

Then I changed the hookSetGet function:

set: function (val) {
    // simulate encrypt
    someObject[hiddenValueKey] = val + 1;
    cc.log("hooked set: " + val + " - " + someObject[hiddenValueKey]);
},
get: function () {
    cc.log("hooked get: " + someObject[hiddenValueKey] + " - " + (someObject[hiddenValueKey] - 1));
    // simulate decrypt
    return someObject[hiddenValueKey] - 1;
}

I changed all the this.hiddenValueKey to someObject[hiddenValueKey].

and the output is :

cc.log(objectA.__a);  // 2   good
cc.log(objectA.__b);  // 3   good
cc.log(objectA.__c);  // 4   good

cc.log(objectA.a);    // hooked get: 4 - 3   still wrong
cc.log(objectA.b);    // hooked get: 4 - 3   still wrong
cc.log(objectA.c);    // hooked get: 4 - 3   still wrong

Upvotes: 1

Views: 147

Answers (2)

Siderite Zackwehdex
Siderite Zackwehdex

Reputation: 6560

There are several issues with your code. One of them is that key and hiddenValueKey are set in the scope of the hookGetSet function. Therefore whenever you use them, you use the last value in the loop (3 and __c). You can fix this in two ways:

  • use let instead of var to define key and hiddenValueKey within the loop scope, but that only works in ES6
  • use a closure to scope the inside of the loop

The other problem is that inside the properties you use this.hiddenValueKey, which is the same as this['hiddenValueKey'], not this[hiddenValueKey] as I assume you intended.

Here is code that works (EcmaScript6):

hookSetGet : function (someObject) {
    for (let key in someObject) {
        cc.log("key: " + key);

        // store the origin value before Object.defineProperty
        var pureValue = someObject[key];

        // add a property to store the encrypted value
        let hiddenValueKey = "__" + key;
        someObject[hiddenValueKey] = undefined;

        Object.defineProperty(
            someObject,
            key, {
            set : function (val) {
                // simulate encrypt
                this[hiddenValueKey] = val + 1000;
                cc.log("hooked set: " + val + " - " + this[hiddenValueKey]);
            },
            get : function () {
                // simulate decrypt
                var result = this[hiddenValueKey] - 1000;
                cc.log("hooked get: " + this[hiddenValueKey] + " - " + result);
                return result;
            }
        });

        // trigger set to encrypt
        someObject[key] = pureValue;
    }
}

and here is same code for classic ES5 Javascript:

hookSetGet : function (someObject) {
    for (var k in someObject) {
        (function () {
            var key = k;
            cc.log("key: " + key);

            // store the origin value before Object.defineProperty
            var pureValue = someObject[key];

            // add a property to store the encrypted value
            var hiddenValueKey = "__" + key;
            someObject[hiddenValueKey] = undefined;

            Object.defineProperty(
                someObject,
                key, {
                set : function (val) {
                    // simulate encrypt
                    this[hiddenValueKey] = val + 1000;
                    cc.log("hooked set: " + val + " - " + this[hiddenValueKey]);
                },
                get : function () {
                    // simulate decrypt
                    var result = this[hiddenValueKey] - 1000;
                    cc.log("hooked get: " + this[hiddenValueKey] + " - " + result);
                    return result;
                }
            });

            // trigger set to encrypt
            someObject[key] = pureValue;
        })();
    }
}

Upvotes: 0

XCS
XCS

Reputation: 28137

So, you wrote this:

   Object.defineProperty (
        someObject,
        key, 
        {
            set: function (val) {
                // simulate encrypt
                this.hiddenValueKey = val + 1;
                cc.log("hooked set: " + val + " - " + this.hiddenValueKey);
            },
            get: function () {
                cc.log("hooked get: " + this.hiddenValueKey + " - " + (this.hiddenValueKey - 1));
                // simulate decrypt
                return this.hiddenValueKey - 1;
            }
        }
    );

In your getter and setter this from this.hiddenValueKey refers to your objectA Object in all cases, not to each property. So when you want to set a value for each property you're actually over-writing objectA.hiddenValueKey. This is why when you try to get back the values you only get the last value which was set.

Even though you set hiddenValueKey to be unique, in the getter and setter you acess the same property. This is because this.hiddenValueKey is the same as writing this['hiddenValueKey']. Did you mean to write this[hiddenValueKey] ? Even if you do it, you might have some scoping issues with the hiddenValueKey always having the latest key value after you exit the loop.

So, you can try this:

   Object.defineProperty (
        someObject,
        key, 
        {
            set: function (val) {
                // simulate encrypt
                this[hiddenValueKey] = val + 1;
                cc.log("hooked set: " + val + " - " + this[hiddenValueKey]);
            },
            get: function () {
                cc.log("hooked get: " + this[hiddenValueKey] + " - " + (this[hiddenValueKey] - 1));
                // simulate decrypt
                return this[hiddenValueKey] - 1;
            }
        }
    );

But, as I said, you might have to create a closure for the hiddenValueKey variable so it will be unique for each property getter and setter.

You can create a closure like this:

   (function(hiddenValueKey) {
     Object.defineProperty (
        someObject,
        key, 
        {
            set: function (val) {
                // simulate encrypt
                this[hiddenValueKey] = val + 1;
                cc.log("hooked set: " + val + " - " + this[hiddenValueKey]);
            },
            get: function () {
                cc.log("hooked get: " + this[hiddenValueKey] + " - " + (this[hiddenValueKey] - 1));
                // simulate decrypt
                return this[hiddenValueKey] - 1;
            }
        }
  );
  }(hiddenValueKey));

Upvotes: 1

Related Questions