views:

87

answers:

4

I've been trying to see if I can build my JavaScript objects as intuitively as possible while making sure it's as 'correct' as possible. I've been running a bunch of different scenarios through Crockford's JSLint.com and haven't had much luck. I seem to fix one error, then another pops up because of the change. Below is about as good as I can get. Anyone have another take on this?

This is a typical way I structure an object:

function gizmo(id) {

  /* private variables */

  var myId = id;

  /* private methods */

  var init = function () {
    if (myId < 1) {
      setId(1);
    }
  };

  var setId = function (newId) {
    myId = newId;
  };

  // run 'constructor'
  init();

  /* public methods */

  return {
    getId: function () {
      return myId;
    },
    setId: function (newId) {
      setId(newId);
    },
    incrementId: function (inc) {
      setId(myId + inc);
    }
  };
}

// creating an instance of gizmo

var myGizmo = gizmo(-2);
console.log(myGizmo.getId()); // outputs 1

myGizmo.setId(5);
console.log(myGizmo.getId()); // outputs 5

myGizmo.incrementId(2);
console.log(myGizmo.getId()); /// outputs 7

This seems to work well. However, when I run this through JSLint, it gives me an error stating that my two private functions are 'Implied Globals.'

The best I can come up with is to declare my functions at the top with the variables like this:

function gizmo(id) {

  /* private variables */

  var myId = id,
      init,
      setId;

  /* private methods */

  init = function () {
    if (myId < 1) {
      setId(1);
    }
  };

  setId = function (newId) {
    myId = newId;
  };

  // run 'constructor'
  init();

  /* public methods */

  return {
    getId: function () {
      return myId;
    },
    setId: function (newId) {
      setId(newId);
    },
    incrementId: function (inc) {
      setId(myId + inc);
    }
  };
}
A: 

I don't know about JSlint, but after reading "javascript the good parts", I always declare objects as literals.

eg:

Mogwai={
  has_crazy_thoughts:True,
  reacts_to_water:True,
  name: 'Gizmo',
  eat:function(food){
    // code
  },
  become_gremlin:function(){
    // code
  },
  cause_havoc:function(){
    // code
  }
}

You're not actually declaring any objects above. Just a function. Inner functions don't really exist in Javascript - it's not like Java at all.

EDIT: I highly recommend the book mentioned above (without affiliation): http://oreilly.com/catalog/9780596517748 ... it is written by Douglas Crockford, who brought us JSlint.

Rich
I own the book. And yes, it's good. My example above creates private variables and function using closures, which is something Crockford goes over in that book as well.With your example, just using object literals, I have no way of creating private variables or functions.
Ken Earley
Actually, you are declaring an object. You're declaring an object, as well as three functions, two booleans, and a string, all stuffed into properties of the object.
icktoofay
Also, `True` and `False` are lowercase in JavaScript.
icktoofay
@icktoofay Yes I know I am declaring an object, I was referring to the OP. You are right about true/false though, I've been writing Python all day :-)
Rich
+2  A: 

JSLint expects setId to be defined before it is referenced in init.

This passes JSLint.

function gizmo(id) {

  /* private variables */

  var myId = id;

  /* private methods */

  var setId = function (newId) {
    myId = newId;
  };

  var init = function () {
    if (myId < 1) {
      setId(1);
    }
  };

  // run 'constructor'
  init();

  /* public methods */

  return {
    getId: function () {
      return myId;
    },
    setId: function (newId) {
      setId(newId);
    },
    incrementId: function (inc) {
      setId(myId + inc);
    }
  };
}
gradbot
it makes no difference if I define my function using function setId() I get the same Implied Global error in JSLint.
Ken Earley
I just ran this through JSLint and it passed. Try it on their website. http://www.jslint.com/
gradbot
`setId` never refers to a global. Due to hoisting, it always refers to the local variable. It's true that the value of this local variable is initially undefined, but that's irrelevant because it's defined when `init` is called.
Matthew Flaschen
Yeah. That works. So it seems to be an ordering issues. It's all based on making sure you don't call a function before it's declared. This would be a problem if you have any recursion going on where a function you call calls the original one again.
Ken Earley
@Mathew sorry I didn't explain it very good. Init doesn't call a global. It is trying to capture a reference to an undefined variable since init is a closure. The global warning is caused by looking up the scope chain and not finding setid in the global scope.
gradbot
No. All variable declarations are hoisted to the top. You can't call a variable if it isn't currently defined to hold a function. However, declaration order is irrelevant. And I don't see any recursive scenario where this causes problems. A function is closed into itself, so it can call itself..
Matthew Flaschen
@gradbot, that's what `JSLint` thinks, but it's wrong. `init` captures the local variable (which is hoisted). This is defined by the spec, and you can also test the code in my answer. It's initially undefined, but never refers to the global.
Matthew Flaschen
@Matthew - I meant as far as JSLint was concerned. I agree with your assessment, but JSLint is expecting a specific order.
Ken Earley
To be clear - I was pretty sure I understood what was going on in my code. I was just surprised by the errors thrown by JSLint. I was curious if others thought it was a bug or if there was some other way of coding that I was unaware. Thanks.
Ken Earley
I understand now Mathew. Has this always been the case in earlier versions of EMCAScript?
gradbot
@grad, yes as far as I know. See this [blog post](http://blogs.msdn.com/b/ericlippert/archive/2004/06/18/159378.aspx) by Eric Lippert, which implies it was resolved in 1996 (for ECMAScript 1).
Matthew Flaschen
@Matthew, thanks for all your help. I assumed I knew JavaScript pretty good. I'm going to spend more time diving into the spec.
gradbot
A: 

It has been a long time (several years) since I've programmed JavaScript seriously, so my memory on the details of best practice is quite gone. What I've done is gone back and dug out a few resources that may help you make an informed decision.

First, the way you are creating your objects seems very reminiscent of the Module Pattern. As far as I recall, the article I've linked to is a pretty good read about that. Second, maybe you would prefer a different way to instantiate your objects. That article gives you a slightly different take.

TNi
Cool. Reading Resig's post on that now. Thanks.
Ken Earley
+1  A: 

I'm pretty sure it's a bug in JSLint. It hasn't seen setId yet, so it assumes it's global. But in reality, it makes no difference, because all vars are hoisted, per ECMAScript 5 10.5. This means your first example and the second are the same semantically. A local variable declaration anywhere in the function is processed immediately, and the binding is initially set to have undefined value. But by the time the function (e.g. init) actually runs, the closed-in value is no longer undefined.

To see that setId is initially undefined, but never refers to a global, do this test:

function setId()
{
  alert("Global setId");
}
function f()
{
  var init = function()
  {
    setId();
  }
  alert(typeof(setId));
  init();
  var setId = function()
  {

  }
}

It will alert undefined, then throw a TypeError error.

Matthew Flaschen
+1, I didn't know about hoisting. I just always assumed you had to declare things in order.
gradbot
You have to declare them in order if you want them to work cross-browser.
lawnsea
@lawnsea, what browser behaves differently, and how?
Matthew Flaschen
@Matthew Doh! Ignore my comment. I wasn't paying close enough attention to what was going on.
lawnsea