Privileged Methods - how to get values of properties that are passed to the function?

53 views Asked by At

(example is from the book but I don't seem to get it)

    function User (properties){
       for( var i in properties){
           (function(){
               this["get"+i] = function () { return properties[i];};
               this["set"+i] = function (valueOne) { properties[i] = valueOne; }; 
           }) ();
        }// END for
    }// END User

var userOne = new User ({ name: "Billy", age: 35 });
userOne.getname(); 

When I run this, User does not have getname method. How can I make the privileged method works?

4

There are 4 answers

14
George Mauer On BEST ANSWER

This is because you used an immediately invoked function

    for( var i in properties){
       (function(){  //<--- This
           this["get"+i] = function () { return properties[i];};
           this["set"+i] = function (valueOne) { properties[i] = valueOne; }; 
       }) ();
    }

Remove it and it will still not work, but your methods will be there. To get it fully working you should preserve i

    for( var i in properties){
       (function(i){  //<--- This
           this["get"+i] = function () { return properties[i];};
           this["set"+i] = function (valueOne) { properties[i] = valueOne; }; 
       }) (i); //<--- and this
    }

The latter issue is not as interesting (though related to) the first one.

Javascript has only what is known as "function scope" meaning that the only thing that limits function scope is...well...a function. Therefore, a common pattern is to use IIFEs like this inside of for loops or in many places where you don't want variables to leak.

However, the this parameter in javascript is weird. Understand the following and it will save you a ton of hassle: this in javascript is no different from any other parameter.

Let me explain.

There are four ways to invoke a function in javascript.

myFn.call("this param", "param 1", "param 2"); //this is "this param"
myFn.apply("this param", ["param 1", "param 2"]); //this is "this param"
myFn("param 1", "param 2"); 
//javascript takes a guess at what `this` should be - 
//usually it is set to the global `window`.
new myFn("param 1", "param 2"); 
//`this` is a new function with its' prototype set to myFn.prototype

If you were always to use the .call form all ambiguity would be gone and you can see that this is exactly like every other parameter. However that's extra syntax and people prefer using the simpler form which means you have to consider the rules for what is "this".

Therefore what you are doing in your example is placing getters and setters on the global window object.

I'm going to make a statement here that your book probably doesn't agree with but that I've picked up from years of learning, working with, and teaching javascript:

Don't use the new and this keywords.

These two keywords introduce a ton of concepts into JS that are confusing and really - unless you're making something very performance sensitive (you're not, I know you think you are, but you're not) - not necessary. Instead create new objects simply like this:

var user = { name: "Billy", age: 35 };

if you absolutely must have getters and setters this will do it:

function createObjectWithProps (properties){
   var obj = {};
   var state = {}[
   for( var k in properties){
       (function(key) {
           obj["get"+key] = function () { return state[key];};
           obj["set"+key] = function (valueOne) { state[key] = valueOne; }; 
        })(k)
    }
    return obj;
}

var userOne = createObjectWithProps ({ name: "Billy", age: 35 });
userOne.getname(); 

Although I'll go even further to state that getters and setters are not terribly useful in js, and when you DO use them it is standard to follow a pattern similar to what knockout does.

1
Chris Charles On

The problem is the "this" keyword.

Because you are using it inside an immediately invoked function it is pointing to the global scope.

try this:

function User (properties){

   for( var i in properties){
       (function(self,i){
           self["get"+i] = function () { return properties[i];};
           self["set"+i] = function (valueOne) { properties[i] = valueOne; }; 
       }) (this,i);
    }// END for
}// END User
0
rid On

You need both this and i captured in the closure:

function User (properties){
   for( var i in properties){
       (function(t, i){
           t["get"+i] = function () { return properties[i];};
           t["set"+i] = function (valueOne) { properties[i] = valueOne; }; 
       }) (this, i);
    }// END for
}// END User
0
Tibos On

this is not who you think it is. Since you invoked the IIFE on nothing, this will be the global scope, so the window will get the methods getname, etc, which is not what you expected.

To fix it, if you want to keep the IIFE, you need to call it on the right context:

function User (properties){
       for( var i in properties){
           (function(key){
               this["get"+key] = function () { return properties[key];};
               this["set"+key] = function (valueOne) { properties[key] = valueOne; }; 
           }).call(this, i);
        }// END for
    }// END User

var userOne = new User ({ name: "Billy", age: 35 });
userOne.getname(); 

Note that you also forgot to pass the i argument to the function and to interpret it as parameter. Otherwise all the functions would get bound to the same key, thus userOne.getname would return 35.