0

The title is probably confusing but I will use a code snippet so hopefully you can explain what's going on.

My constructor

function Person(name, age){
    this.name = name;
    this.age = age;
    this.yearsToRetire = function(){
    console.log(this.age); //undefined
    return 65-this.age;
 }();
}
var joe = new Person ("joe",26);
console.log(joe.yearsToRetire); //NaN

My question is how come it doesn't know the value of this.age when I have already passed it and it should be 26 at the time of execution of yearsToRetire? Is it possible to achieve the effect of getting the years until retirement as the return value rather than a function to execute?(i.e is there a way that I use joe.yearsToRetire to get 39 rather than joe.yearsToRetire())

Thanks

ribarcheto94
  • 436
  • 11
  • 25
  • 4
    `this` is not what you think it is. – SLaks May 15 '17 at 21:48
  • Why are you using an IIFE in the constructor (did you even do that on purpose)? Either move the `()` down after the `joe.yearsToRetire` access, or replace them by `.call(this)`. – Bergi May 15 '17 at 21:50

4 Answers4

1

this within the function refers to the global window object or undefined in strict mode, not the Person. One solution is to use an arrow function with lexical this:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.yearsToRetire = (() => {
    return 65 - this.age;
  })();
}
var joe = new Person("joe", 26);
console.log(joe.yearsToRetire); // 39

Of course, the simpler solution here is to get rid of the immediately invoked function expression (IIFE) and write this.yearsToRetire = 65 - this.age;

Alternatively, add yearsToRetire as a function to the prototype:

Person.prototype.yearsToRetire = function() {
  return 65 - this.age;
}

Or just remove the immediate invokation, as suggested by @ScottMargus - depending on what you want to achieve.

Community
  • 1
  • 1
le_m
  • 19,302
  • 9
  • 64
  • 74
  • 1
    Uh, the much better solution would of course be not to use a function at all. – Bergi May 15 '17 at 21:51
  • 1
    @Bergi Yes, but that wouldn't help OP's to understand and answer his own question: "how come it doesn't know the value of this.age when I have already passed it" – le_m May 15 '17 at 21:52
  • Actually, `this` within the function can mean different things. It all depends on the function's invocation context. The way the OP is calling the function, `this` is bound to the object instance that is being created (not the constructor function object) and that object is stored in the `joe` variable. The problems with the code are not related to `this`. – Scott Marcus May 15 '17 at 21:55
  • 1
    Why? Your entire answer is about `this`, when that is not the problem. The OP is using `this` correctly. – Scott Marcus May 15 '17 at 21:58
  • @ScottMarcus "OP is using this correctly" - there are different solutions to OP's issue. Fixing `this` while keeping the IIFE is one of them - not the best, of course - which however helps OP understand the `this` keyword better and which doesn't change the type of the `yearsToRetire` property. I included the other solutions as well, though, referring to your good answer. – le_m May 15 '17 at 22:23
  • Hmm I'm still learning JS, so I haven't covered prototypes and arrow functions yet, but I guess the problem is that I thought of this as Java and assumed "this" always referred to the instance. Anyways, thanks for pointing that out. Also, there's an answer below by @Jordan S, claiming that "the function is being invoked within the declaration". Maybe I'm misunderstanding what he is trying to say, but my understanding is that the code within the constructor will be executed only when I create a new instance. Can someone clarify this for me? Thanks – ribarcheto94 May 15 '17 at 22:35
  • @ribarcheto94 Unless you use `Function.bind` and such, you can expect `this` within a traditional function to always refer to the object that a function belongs to. The unnamed anonymous function within your Person constructor doesn't belong to anything, so `this` is either `window` (or `undefined` in strict mode). Regarding your last sentence, your understanding is correct. – le_m May 15 '17 at 22:40
  • After thinking about it for a bit and trying out a few things, I think I understand what you mean when you say "the unnamed anonymous function within your Person constructor doesn't belong to anything." It's more like I'm assigning the return value of a function that has not been defined explicitly in a closure/object and so by default it's executed in the global scope, thus "this" inside it, refers to window/undefined because it's technically declared globally although in code, it's inside the Person object. see next comment.. – ribarcheto94 May 15 '17 at 23:21
  • Now if I remove the parenthesis after the anon. function definition, then I'm technically declaring a function inside Person, and so it's now part of that object, right? – ribarcheto94 May 15 '17 at 23:21
  • @ribarcheto94 There is a minor difference between 'function declaration' and 'function expression' (which Bergi and Scott Marcus were fighting about below). After removing the parenthesis, you assign the value produced by that function expression (the resulting `function` object) to the currently instantiated `Person`'s `yearsToRetire` property. So when you then call `joe.yearsToRetire()`, `this` is `joe`. You could now do `var f = joe.yearsToRetire` and then call `f()` where `this` would, again, be undefined, as `f()` doesn't have anything before the dot '.' - doesn't belong to anything. – le_m May 15 '17 at 23:31
0

There are multiple problems. First you are invoking your function when you declare it which is putting the value of yearsToRetire as NaN. So remove the () at the end of your yearsToRetire function declaration. Second, you then need to call your function as a function: joe.yearsToRetire() instead of joe.yearsToRetire.

function Person(name, age){
    this.name = name;
    this.age = age;
    this.yearsToRetire = function(){
      return 65-this.age;
    };
}
var joe = new Person ("joe",26);
console.log(joe.yearsToRetire());
Jordan Soltman
  • 3,795
  • 17
  • 31
  • 1
    "First you are invoking your function when you declare it which is putting the value of yearsToRetire as NaN" - Yes but that's only after I create a new instance, so since JS is synchronous, the age should have already been set by the time I call the function, right?. – ribarcheto94 May 15 '17 at 22:06
  • 1
    No, it's invoking within the declaration – Jordan Soltman May 15 '17 at 22:08
  • Why? I thought named functions were simply function definitions which you can call anywhere in your code at anytime since unlike anonymous functions, named functions are defined before anything runs and are thus available to use. If what you are saying is true, then that means if I don't call a named function, but just define it, when the script runs, the function will be called automatically which is not the case. – ribarcheto94 May 15 '17 at 22:16
  • 1
    The invocation should only happen after I call new Person(..) – ribarcheto94 May 15 '17 at 22:18
  • @JordanS "*it's invoking within the declaration*" should be "it's invoked within the **execution** of `new Person()`". Indeed the `age` property is already created by then, so I'm not sure whether you are confused yourself or just expressed it wrong. – Bergi May 15 '17 at 22:27
  • Thank you for clarifying this @Bergi! – ribarcheto94 May 15 '17 at 22:39
0

You can call the function in the Person's scope using call or apply or simply save a reference to the person object:

    function Person(name, age){
        var self = this;
        this.name = name;
        this.age = age;
        this.yearsToRetire = (function(){
            console.log(self.age);
            return 65-self.age;
        })();
    }
    var joe = new Person ("joe",26);
    console.log(joe.yearsToRetire);
Peter Grundmann
  • 115
  • 1
  • 10
  • `call` and `apply` are discouraged in favor of `bind`. But really, your answer just patches the code and doesn't address the two syntax issues with the original code. – Scott Marcus May 15 '17 at 22:03
  • you are right. My edit was not applied. There was some braces missing – Peter Grundmann May 15 '17 at 22:08
  • @ScottMarcus There's nothing wrong with using `.call()` when you want to *call* the function, instead of creating another function to call later (like `bind`). – Bergi May 15 '17 at 22:30
-1

You have three issues:

  1. You were self-invoking the function
  2. You weren't calling for the yearsToRetire function to run, you were just referencing it.
  3. Methods generally are added to an object's prototype so that each new instance doesn't have to store the same functionality over and over again.

Now, as for #1 (above), function declarations are hoisted to the top of their enclosing scope and are read into memory at the start of that scope's processing. When the function is read, if it includes other function invocations, those functions will be invoked and that return values of those functions will be used as the values for the function being read. That was your main problem. You thought that the self-invocation of yearsToRetire wouldn't happen until a new instance of Person was made, but in fact, that invocation was happening immediately.

This is not the pattern to use when creating constructor functions.

function Person(name, age){
    this.name = name;
    this.age = age;
}  

// By adding the function to the prototype, each Person instance
// will inherit the method.
Person.prototype.yearsToRetire = function(){
      console.log("The passed age was: " + this.age);
      return 65-this.age;
};  // <-- You were self-invoking the function here


var joe = new Person ("joe",26);
console.log(joe.yearsToRetire());  // <-- You didn't include () after function name here
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
  • And, why the down vote? Correct answer with example and documentation? – Scott Marcus May 15 '17 at 22:09
  • The second paragraph is nonsense. There isn't any function declaration in the code, and there's no hoisting of invocations. – Bergi May 15 '17 at 22:23
  • Apart from that your solution is fine, however we don't know whether the OP intended `yearsToRetire` to be a method or a property. – Bergi May 15 '17 at 22:24
  • @Bergi `function Person` is a function declaration that will be hoisted to the top of its scope and read into memory. I did not say invocations are hoisted. – Scott Marcus May 15 '17 at 22:24
  • Yes, `Person` is a hoisted declaration, but that has absolutely nothing to do with the problem(s) in the OPs code. Specifically, I could not make any sense of the sentence "*When the function is read, if it includes other function invocations, those functions will be invoked and that return values of those functions will be used as the values for the function being read.*" – Bergi May 15 '17 at 22:32
  • Also you wrote "*as for #1 (above), function declarations are hoisted*", however #1 dealt with the immediately-invoked function expression. Not the `Person` function declaration. – Bergi May 15 '17 at 22:35