Javascript scope creed

by sandersn 27. August 2008 09:02
I wrote this when re-learning Javascript. It is quite dense but I have added illustrations. I learned part of it from Doug Crockford's useful but rather prescriptive site (specifically, he prescribes semicolons, which I hate). The description is probably only as useful as your grasp of lexical scope, because that's what is at the core of Javascript. Credo:
  1. There is no built-in object-oriented scope. Only lexical scope and hash lookup, called object scope below.
  2. Lexical scope never uses the dot operator.
  3. Object scope always uses the dot operator.
  4. Using these two scopes, you can create private and public fields, and private, public and privileged methods.
  5. Private fields are created using lexical scope. As such, they are only accessible inside the prototype function, to private and privileged functions. They are not inherited. They do not use 'this.' prefixes.
  6. Public fields are created using object scope. As such, they are accessible to any code. They are inherited. They always use 'this.' prefixes.
  7. Private methods are created using lexical scope: the var keyword. As such, they are only accessible inside the prototype function, are not inherited, and do not require 'this.' prefixes. They can access private variables.
  8. Privileged methods are created using object scope: assignment to 'this'. As such, they are accessible anywhere and require 'this.' prefixes. However, because they are attached to individual objects during construction, they are not inherited either. They can access private variables because of lexical scope, and because they are unique to each object.
  9. Public methods are created using object scope: assign to the prototype. As such, they are accessible anywhere and require 'this.' prefixes. They can access only public variables because, even if they are created inside the lexical scope of a particular object, those private variables are the variables of only one object, the latest one constructed. (this must lead to a way to make public static variables, if you want them)
  10. Public methods require the most boilerplate, because they can only access public variables, which must be explicitly created from constructor arguments, and because they must be assigned to the prototype, not 'this', and because all accesses of methods and variables must be prefixed with 'this'. They are also the least secure; anyone can add a public method anywhere. However, they are also the most efficient, since they are not copied to each instance.
  11. Use of only public methods and public variables gives you the same object system as Python. This is probably why it's the recommended style. Simple, explicit, and dynamic.
Illustrations:
  1. Lexical scope:
    var x = 1
    function f() {
      var x = 2
      function g() {
        print(x)
      }
      g()
    }
    f()
    // prints 2
  2. Object scope:
    var o = {x : 1, y : 2}
    function f() {
      print o.x
    }
    f()
    // prints 1
  3. How nice!
  4. Private fields:
    function Example(x) {
      var y = 2
    }
    var e = new Example(1)
    // e.x and e.y are both undefined from the outside
    //private static:
    var Example = (function() {                          
      var privateStatic = 1
      return function(x) {   
        var y = 1
        this.f = function() { print(privateStatic++) }
      }
    })();
    var e = new Example(1)
    var f = new Example(1001)
    e.f(); f.f(); e.f() // prints 1 2 3
  5. Public fields:
    function Example(x) {
      this.y = 2
    }
    var e = new Example(1)
    e.y // is now available from the outside
  6. Private methods:
    function Example(x) {
      var y = 2
      var f = function() { print(y++) }
    }
    var e = new Example(1)
    e.f() // throws TypeError: e.f is not a function
    
  7. Privileged methods:
    function Example(x) {
      var y = 2
      this.f = function() { print(y++) }
    }
    var e = new Example(1)
    e.f(); e.f() // prints 2 3
    
  8. Public methods:
    function Example(x) {
      this.y = 2 // y must also be public
      this.x = x // copy constructor argument to public field
    }
    Example.prototype.f = function() { print(this.x + this.y++) }
    var e = new Example(1)
    e.f(); e.f() // prints 3 4
    
Note: I wanted to link to a concise tutorial on lexical scope, but I don't know one. If you have time, SICP will certainly teach you lexical scope, along with a million other wild and wonderful things. If you don't like Scheme syntax (an acquired taste), follow along with translations to other languages.

Tags:

General

Comments (2) -

Harley Laue
Harley Laue
12/22/2009 11:26:37 AM #

I know this is over a year old post, but I'd just like to comment on: "specifically, he prescribes semicolons, which I hate."<br />
There are very good reasons to use semicolons in Javascript/ECMAScript (which Crockford explains in several places.) One of the reasons for it is so you can minify JS code (not as big a deal for small scripts.) If you leave out the semicolons, JS will only do semicolor insertion on new lines. Which is why your code above in 9 wouldn't work if it had left out semicolons on the last line like so:<br />
e.f() e.f()<br />
another reason semicolon insertion sucks is because something like this is perfectly legitimate JS but doesn't do what's intuitive:<br />
return<br />
{<br />
    x: 1<br />
}<br />
<br />
and when semicolon insertion happens to the above it becomes (which is still legitimate JS I might add):<br />
return;<br />
{<br />
    x: 1;<br />
}<br />
<br />
So unless you the prescribed style closely, and don't minify (or trust the minifier to "do the right thing") you'll be fine most of the time with letting JS handle semicolon insertion. You're also going out on a limb by assuming the next person who uses/maintains/reads/etc understands JS's idiosyncrasies enough to know where JS will insert semicolons for them.<br />
<br />
Personally, I think it makes code more maintainable with strict use of semicolons because, like I said before, the next person(s) to come along and maintain the code may not have as thorough a grasp on JS as they should. Basically it boils down to a little extra typing can save hours of debugging down the road (for you, or the next person) trying to find exactly where JS decided to insert a semicolon which produced unexpected results.<br />
<br />
Anyways, that's just my 2¢'s to add to the discussion.

Nathan Sanders
Nathan Sanders
1/3/2010 8:13:42 PM #

Thanks for your comment. I hate semicolons instinctively, so it's an aside more than anything, because there are good arguments for using them in Javascript. That's a flaw of the language, though, because you could minify Ruby or Haskell perfectly well and let the language insert the semicolons.<br />
<br />
Javascript's optional semicolons are like C's optional brackets around a single-statement block. The way it's designed, you can't use the feature: you should always put in the brackets just to be safe. I do, when I'm writing C that will touched by a lot of people years on down the road. But besides those circumstances, given the choice, I'll leave the brackets off every time.<br />
<br />
Besides, there are good editor-based reasons that semicolons slow you down every time you edit a line of code (vi is the only editor I know that can easily delete a statement without taking the semicolon so that you can paste it as an expression in another statement). If you can specify the language in such a way that you can minify unambiguously, avoid maintenance problems AND leave out non-essential semicolons, that's the ideal.

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading