69

I'm trying to calculate a proportional height (while excluding a static height element) from a width that gets passed in via a request (defaults to 560).

However, wF.h evaluates to NaN. If I replace this.w with 560 it works, but not when trying to reference the w property of wF.

var wF = {
       w : 560,
       h : (312 - 42) / (560 / this.w) + 42
};

What gives?

I refuse to use two plain vars in succession, because I'm trying to get nice code out of JS.

Update:

Thanks to everyone who helped explain and solve my problem. I guess i'll just have to get used to that. I'll be setting the object up in stages to get on with the project, even though it still annoys me slightly ;). I found and read a nice article on the topic for anyone who stumbles upon similar issues: http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/

mhe
  • 693
  • 1
  • 6
  • 7
  • what version of ecmascript? i dont think this is javascript – naveen Aug 12 '11 at 16:51
  • var wF = {---} does the same thing. – mhe Aug 12 '11 at 16:53
  • i am happy that you noticed. any initialization as object needs `new` (your constructor). and so your object declaration is wrong – naveen Aug 12 '11 at 17:00
  • while you are technically right it doesn't really relate to my problem. – mhe Aug 12 '11 at 17:06
  • i thought you said *nice* code :) – naveen Aug 12 '11 at 17:07
  • yeah, i've updated it to simplify the issue. i want nice code, but so far from the answers and my own research it looks like i can either do it procedually or encapsulate the properties somewhat messy. i'm rather fresh to JS obviously, but this is awfully daunting :( – mhe Aug 12 '11 at 17:10
  • @mhe: It's just a consequence of using a basic object notation. Its not optimised for properties at declaration to be functions of one another. – Lightness Races in Orbit Aug 12 '11 at 17:18

5 Answers5

95
// Your code
var wF = {
       w : 560,
       h : (312 - 42) / (560 / this.w) + 42
};

this isn't what you think it is

Javascript has no block scope, only function scope: this inside the definition for wF does not refer to wF.

(And so this.w, whatever this is, is likely undefined. Dividing by undefined yields NaN.)

So then you might try:

// Let's not use `this`
var wF = {
       w : 560,
       h : (312 - 42) / (560 / wF.w) + 42
};

You haven't finished defining the object yet

However, you're still defining the object where you attempt to use wF.w: it's not ready for that yet.


Solution

So, yes, you will have to use two variables... or set up the object in stages:

// We can't even use `wF`; split up the property definitions
var wF = {};
wF.w = 560;
wF.h = (312 - 42) / (560 / wF.w) + 42;
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • But this is also not a solution, because If you will change the old var to let or const, you will receive the error, that you are using variable, which is not initialized yet :-( – klaucode Sep 07 '22 at 07:57
  • ...the solution is to use for example class, but really I must use this complicated way? class X { public static w = 560; public static h = (312 - 42) / (560 / X.w) + 42; } – klaucode Sep 07 '22 at 08:01
48

Hi just redefine your second property as a function object and it will work. I think it is possible to access the context of the calling object from within a function

var wF = {
    w : 560,
    h : function() { return (312 - 42) / (560 / this.w) + 42; }
};

alert(wF.h())
houss
  • 670
  • 5
  • 9
  • 4
    I could not get this to work with arrow function, but this did work! – Kent Robin Dec 18 '17 at 13:36
  • 10
    It will not work with arrow function, as the arrow function does not have its own `this` - it uses `this` from parent function context. – holmicz Mar 01 '18 at 16:03
  • You actually can get the arrow function to work: { matches: { played: 5, won: 4, draw: 0, lost: 1 }, points: () => (this.matches.won * 3) + (this.matches.draw * 1) } . This works inside an array of objects, this isn't evaluated until you call the function. – thatgibbyguy Sep 21 '18 at 02:57
  • @thatgibbyguy. I haven't tried with an array member. thanks for sharing! I learned something :-) – houss Sep 22 '18 at 03:31
  • @thatgibbyguy I don't think that works at all. Arrow functions do not have their own this => see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions#Arrow_functions_used_as_methods In your ex the this.matches.won and this.matches.draw will be taken from window/global object. So, say you have: var test = { matches: { played: 5, won: 4, draw: 0, lost: 1 }, points: () => (this.matches.won * 3) + (this.matches.draw * 1) } , calling test.points() throws error: "Uncaught TypeError: Cannot read property 'won' of undefined" – Rui Carvalho Sep 26 '18 at 12:12
  • change it to `get h() { ... }` and you may still use `alert(wF.h)` – Endless Aug 10 '20 at 14:34
3

The this keyword refers to the calling context, not an object.

You need to do this in two steps like so:

var wF = { w: 560 };
wF.h = (312 - 42) / (560 / wF.w) + 42;
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Paul Perigny
  • 973
  • 5
  • 12
  • you are right of course, and at least it will build object properties instead of plain variables, but it's got me really frustrated that i have to do this procedually :D – mhe Aug 12 '11 at 17:18
  • 1
    If you prefer the OO way, then create an object with getters and setters. This way you can use this.getW() in the definition of the getH() function. – Paul Perigny Aug 12 '11 at 17:22
  • And vastly overcomplicate your code. Potentially. – Lightness Races in Orbit Aug 12 '11 at 17:32
2

You don't need to wrap the {...} in an Object(). It is already an object literal.

this doesn't operate inside the object literal, it will point to the object that the function is currently running in so:

function fn() {
   var wF = { w : 560, h : (312 - 42) / (560 / this.w) + 42 };
}

fn();

will cause this to point to the window object.

EDIT: Previous code was not intended to be an answer, just a demonstration of what this is. Another possiblity would be to use a function that takes the width as an argument:

function getObject(width) {
    width = width || 560; //Default value to 560
    return { w: width, h : (312 - 42) / (560 / width) + 42 };
}

var wF = getObject(); //To get the default value, or specify a width otherwise.
Dennis
  • 32,200
  • 11
  • 64
  • 79
-1

When you use this in a JSON object declaration, it points to the current instance.

You can try this code in your console to understand it better:

Object({ w : 560, h : (312 - 42) / (560 / function(e){console.log(e);return e.w;}(this)) + 42 });
Adilson de Almeida Jr
  • 2,761
  • 21
  • 37